836 lines
24 KiB
Go
836 lines
24 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
// c.Action("agentic.dispatch").Run(ctx, options)
|
|
// c.Actions()
|
|
|
|
package agentic
|
|
|
|
import (
|
|
"context"
|
|
|
|
"dappco.re/go/agent/pkg/lib"
|
|
"dappco.re/go/agent/pkg/messages"
|
|
core "dappco.re/go/core"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
// result := c.Action("agentic.dispatch").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "repo", Value: "go-io"},
|
|
// core.Option{Key: "task", Value: "Fix tests"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleDispatch(ctx context.Context, options core.Options) core.Result {
|
|
if s.Core() != nil {
|
|
entitlement := s.Core().Entitled("agentic.concurrency", 1)
|
|
if !entitlement.Allowed {
|
|
reason := core.Trim(entitlement.Reason)
|
|
if reason == "" {
|
|
reason = "dispatch concurrency not available"
|
|
}
|
|
return core.Result{Value: core.E("agentic.dispatch", reason, nil), OK: false}
|
|
}
|
|
}
|
|
|
|
input := dispatchInputFromOptions(options)
|
|
_, out, err := s.dispatch(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
if s.Core() != nil {
|
|
s.Core().RecordUsage("agentic.dispatch")
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.prep").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "repo", Value: "go-io"},
|
|
// core.Option{Key: "issue", Value: 42},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handlePrep(ctx context.Context, options core.Options) core.Result {
|
|
input := prepInputFromOptions(options)
|
|
_, out, err := s.prepWorkspace(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.status").Run(ctx, core.NewOptions())
|
|
func (s *PrepSubsystem) handleStatus(ctx context.Context, options core.Options) core.Result {
|
|
input := StatusInput{
|
|
Workspace: options.String("workspace"),
|
|
Limit: options.Int("limit"),
|
|
Status: options.String("status"),
|
|
}
|
|
_, out, err := s.status(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.resume").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleResume(ctx context.Context, options core.Options) core.Result {
|
|
input := resumeInputFromOptions(options)
|
|
_, out, err := s.resume(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.scan").Run(ctx, core.NewOptions())
|
|
func (s *PrepSubsystem) handleScan(ctx context.Context, options core.Options) core.Result {
|
|
input := scanInputFromOptions(options)
|
|
_, out, err := s.scan(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.watch").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleWatch(ctx context.Context, options core.Options) core.Result {
|
|
input := watchInputFromOptions(options)
|
|
_, out, err := s.watch(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "slug", Value: "coding"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handlePrompt(_ context.Context, options core.Options) core.Result {
|
|
return lib.Prompt(options.String("slug"))
|
|
}
|
|
|
|
// result := c.Action("agentic.task").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "slug", Value: "bug-fix"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleTask(_ context.Context, options core.Options) core.Result {
|
|
return lib.Task(options.String("slug"))
|
|
}
|
|
|
|
// result := c.Action("agentic.flow").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "slug", Value: "go"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleFlow(_ context.Context, options core.Options) core.Result {
|
|
return lib.Flow(options.String("slug"))
|
|
}
|
|
|
|
// result := c.Action("agentic.persona").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "path", Value: "code/backend-architect"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handlePersona(_ context.Context, options core.Options) core.Result {
|
|
return lib.Persona(options.String("path"))
|
|
}
|
|
|
|
// result := c.Action("agentic.complete").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "/srv/.core/workspace/core/go-io/task-42"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleComplete(ctx context.Context, options core.Options) core.Result {
|
|
return s.Core().Task("agent.completion").Run(ctx, s.Core(), options)
|
|
}
|
|
|
|
// input := agentic.CompleteInput{Workspace: "/srv/.core/workspace/core/go-io/task-42"}
|
|
type CompleteInput struct {
|
|
Workspace string `json:"workspace"`
|
|
}
|
|
|
|
// out := agentic.CompleteOutput{Success: true, Workspace: "core/go-io/task-42"}
|
|
type CompleteOutput struct {
|
|
Success bool `json:"success"`
|
|
Workspace string `json:"workspace"`
|
|
}
|
|
|
|
func (s *PrepSubsystem) completeTool(ctx context.Context, _ *mcp.CallToolRequest, input CompleteInput) (*mcp.CallToolResult, CompleteOutput, error) {
|
|
if input.Workspace == "" {
|
|
return nil, CompleteOutput{}, core.E("agentic.complete", "workspace is required", nil)
|
|
}
|
|
|
|
result := s.handleComplete(ctx, core.NewOptions(core.Option{Key: "workspace", Value: input.Workspace}))
|
|
if !result.OK {
|
|
return nil, CompleteOutput{}, resultErrorValue("agentic.complete", result)
|
|
}
|
|
|
|
return nil, CompleteOutput{
|
|
Success: true,
|
|
Workspace: input.Workspace,
|
|
}, nil
|
|
}
|
|
|
|
// result := c.Action("agentic.qa").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core.Result {
|
|
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-qa") {
|
|
return core.Result{Value: true, OK: true}
|
|
}
|
|
workspaceDir := options.String("workspace")
|
|
if workspaceDir == "" {
|
|
return core.Result{Value: core.E("agentic.qa", "workspace is required", nil), OK: false}
|
|
}
|
|
passed := s.runQA(workspaceDir)
|
|
if !passed {
|
|
if result := ReadStatusResult(workspaceDir); result.OK {
|
|
workspaceStatus, ok := workspaceStatusValue(result)
|
|
if ok {
|
|
workspaceStatus.Status = "failed"
|
|
workspaceStatus.Question = "QA check failed — build or tests did not pass"
|
|
writeStatusResult(workspaceDir, workspaceStatus)
|
|
}
|
|
}
|
|
}
|
|
if s.ServiceRuntime != nil {
|
|
result := ReadStatusResult(workspaceDir)
|
|
workspaceStatus, ok := workspaceStatusValue(result)
|
|
repo := ""
|
|
if ok {
|
|
repo = workspaceStatus.Repo
|
|
}
|
|
s.Core().ACTION(messages.QAResult{
|
|
Workspace: WorkspaceName(workspaceDir),
|
|
Repo: repo,
|
|
Passed: passed,
|
|
})
|
|
}
|
|
return core.Result{Value: passed, OK: passed}
|
|
}
|
|
|
|
// result := c.Action("agentic.auto-pr").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options) core.Result {
|
|
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-pr") {
|
|
return core.Result{OK: true}
|
|
}
|
|
workspaceDir := options.String("workspace")
|
|
if workspaceDir == "" {
|
|
return core.Result{Value: core.E("agentic.auto-pr", "workspace is required", nil), OK: false}
|
|
}
|
|
s.autoCreatePR(workspaceDir)
|
|
|
|
if s.ServiceRuntime != nil {
|
|
result := ReadStatusResult(workspaceDir)
|
|
workspaceStatus, ok := workspaceStatusValue(result)
|
|
if ok && workspaceStatus.PRURL != "" {
|
|
s.Core().ACTION(messages.PRCreated{
|
|
Repo: workspaceStatus.Repo,
|
|
Branch: workspaceStatus.Branch,
|
|
PRURL: workspaceStatus.PRURL,
|
|
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
|
|
})
|
|
}
|
|
}
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.verify").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options) core.Result {
|
|
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-merge") {
|
|
return core.Result{OK: true}
|
|
}
|
|
workspaceDir := options.String("workspace")
|
|
if workspaceDir == "" {
|
|
return core.Result{Value: core.E("agentic.verify", "workspace is required", nil), OK: false}
|
|
}
|
|
s.autoVerifyAndMerge(workspaceDir)
|
|
|
|
if s.ServiceRuntime != nil {
|
|
result := ReadStatusResult(workspaceDir)
|
|
workspaceStatus, ok := workspaceStatusValue(result)
|
|
if ok {
|
|
if workspaceStatus.Status == "merged" {
|
|
s.Core().ACTION(messages.PRMerged{
|
|
Repo: workspaceStatus.Repo,
|
|
PRURL: workspaceStatus.PRURL,
|
|
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
|
|
})
|
|
} else if workspaceStatus.Question != "" {
|
|
s.Core().ACTION(messages.PRNeedsReview{
|
|
Repo: workspaceStatus.Repo,
|
|
PRURL: workspaceStatus.PRURL,
|
|
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
|
|
Reason: workspaceStatus.Question,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.ingest").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleIngest(ctx context.Context, options core.Options) core.Result {
|
|
workspaceDir := options.String("workspace")
|
|
if workspaceDir == "" {
|
|
return core.Result{Value: core.E("agentic.ingest", "workspace is required", nil), OK: false}
|
|
}
|
|
s.ingestFindings(workspaceDir)
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.poke").Run(ctx, core.NewOptions())
|
|
func (s *PrepSubsystem) handlePoke(ctx context.Context, _ core.Options) core.Result {
|
|
if s.ServiceRuntime != nil && s.Core().Action("runner.poke").Exists() {
|
|
return s.Core().Action("runner.poke").Run(ctx, core.NewOptions())
|
|
}
|
|
s.Poke()
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "repo", Value: "go-io"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleMirror(ctx context.Context, options core.Options) core.Result {
|
|
input := mirrorInputFromOptions(options)
|
|
_, out, err := s.mirror(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.issue.get").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "repo", Value: "go-io"},
|
|
// core.Option{Key: "number", Value: "42"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleIssueGet(ctx context.Context, options core.Options) core.Result {
|
|
return s.cmdIssueGet(normaliseForgeActionOptions(options))
|
|
}
|
|
|
|
// result := c.Action("agentic.issue.list").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "_arg", Value: "go-io"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleIssueList(ctx context.Context, options core.Options) core.Result {
|
|
return s.cmdIssueList(normaliseForgeActionOptions(options))
|
|
}
|
|
|
|
// result := c.Action("agentic.issue.create").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "_arg", Value: "go-io"},
|
|
// core.Option{Key: "title", Value: "Bug report"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleIssueCreate(ctx context.Context, options core.Options) core.Result {
|
|
return s.cmdIssueCreate(normaliseForgeActionOptions(options))
|
|
}
|
|
|
|
// result := c.Action("agentic.pr.get").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "_arg", Value: "go-io"},
|
|
// core.Option{Key: "number", Value: "12"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handlePRGet(ctx context.Context, options core.Options) core.Result {
|
|
return s.cmdPRGet(normaliseForgeActionOptions(options))
|
|
}
|
|
|
|
// result := c.Action("agentic.pr.list").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "_arg", Value: "go-io"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handlePRList(ctx context.Context, options core.Options) core.Result {
|
|
return s.cmdPRList(normaliseForgeActionOptions(options))
|
|
}
|
|
|
|
// result := c.Action("agentic.pr.merge").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "_arg", Value: "go-io"},
|
|
// core.Option{Key: "number", Value: "12"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handlePRMerge(ctx context.Context, options core.Options) core.Result {
|
|
return s.cmdPRMerge(normaliseForgeActionOptions(options))
|
|
}
|
|
|
|
// result := c.Action("agentic.pr.close").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "_arg", Value: "go-io"},
|
|
// core.Option{Key: "number", Value: "12"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handlePRClose(ctx context.Context, options core.Options) core.Result {
|
|
return s.cmdPRClose(normaliseForgeActionOptions(options))
|
|
}
|
|
|
|
// result := c.Action("agentic.review-queue").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, options core.Options) core.Result {
|
|
input := reviewQueueInputFromOptions(options)
|
|
_, out, err := s.reviewQueue(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Action("agentic.epic").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "task", Value: "Update all repos to v0.8.0"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) handleEpic(ctx context.Context, options core.Options) core.Result {
|
|
input := epicInputFromOptions(options)
|
|
_, out, err := s.createEpic(ctx, nil, input)
|
|
if err != nil {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: out, OK: true}
|
|
}
|
|
|
|
// result := c.Command("epic").Run(core.NewOptions(
|
|
//
|
|
// core.Option{Key: "repo", Value: "go-io"},
|
|
// core.Option{Key: "title", Value: "Stabilise agent dispatch"},
|
|
// core.Option{Key: "tasks", Value: []string{"Fix the queue race", "Add regression tests"}},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) cmdEpic(options core.Options) core.Result {
|
|
return s.handleEpic(s.commandContext(), options)
|
|
}
|
|
|
|
func dispatchInputFromOptions(options core.Options) DispatchInput {
|
|
return DispatchInput{
|
|
Repo: optionStringValue(options, "repo"),
|
|
Org: optionStringValue(options, "org"),
|
|
Task: optionStringValue(options, "task"),
|
|
Agent: optionStringValue(options, "agent"),
|
|
Template: optionStringValue(options, "template"),
|
|
PlanTemplate: optionStringValue(options, "plan_template", "plan-template"),
|
|
Variables: optionStringMapValue(options, "variables"),
|
|
Persona: optionStringValue(options, "persona"),
|
|
Issue: optionIntValue(options, "issue"),
|
|
PR: optionIntValue(options, "pr"),
|
|
Branch: optionStringValue(options, "branch"),
|
|
Tag: optionStringValue(options, "tag"),
|
|
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
|
|
}
|
|
}
|
|
|
|
func prepInputFromOptions(options core.Options) PrepInput {
|
|
return PrepInput{
|
|
Repo: optionStringValue(options, "repo"),
|
|
Org: optionStringValue(options, "org"),
|
|
Task: optionStringValue(options, "task"),
|
|
Agent: optionStringValue(options, "agent"),
|
|
Issue: optionIntValue(options, "issue"),
|
|
PR: optionIntValue(options, "pr"),
|
|
Branch: optionStringValue(options, "branch"),
|
|
Tag: optionStringValue(options, "tag"),
|
|
Template: optionStringValue(options, "template"),
|
|
PlanTemplate: optionStringValue(options, "plan_template", "plan-template"),
|
|
Variables: optionStringMapValue(options, "variables"),
|
|
Persona: optionStringValue(options, "persona"),
|
|
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
|
|
}
|
|
}
|
|
|
|
func resumeInputFromOptions(options core.Options) ResumeInput {
|
|
return ResumeInput{
|
|
Workspace: optionStringValue(options, "workspace"),
|
|
Answer: optionStringValue(options, "answer"),
|
|
Agent: optionStringValue(options, "agent"),
|
|
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
|
|
}
|
|
}
|
|
|
|
func scanInputFromOptions(options core.Options) ScanInput {
|
|
return ScanInput{
|
|
Org: optionStringValue(options, "org"),
|
|
Labels: optionStringSliceValue(options, "labels"),
|
|
Limit: optionIntValue(options, "limit"),
|
|
}
|
|
}
|
|
|
|
func watchInputFromOptions(options core.Options) WatchInput {
|
|
workspaces := optionStringSliceValue(options, "workspaces")
|
|
if len(workspaces) == 0 {
|
|
if workspace := optionStringValue(options, "workspace"); workspace != "" {
|
|
workspaces = []string{workspace}
|
|
}
|
|
}
|
|
return WatchInput{
|
|
Workspaces: workspaces,
|
|
PollInterval: optionIntValue(options, "poll_interval", "poll-interval"),
|
|
Timeout: optionIntValue(options, "timeout"),
|
|
}
|
|
}
|
|
|
|
func mirrorInputFromOptions(options core.Options) MirrorInput {
|
|
return MirrorInput{
|
|
Repo: optionStringValue(options, "repo"),
|
|
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
|
|
MaxFiles: optionIntValue(options, "max_files", "max-files"),
|
|
}
|
|
}
|
|
|
|
func reviewQueueInputFromOptions(options core.Options) ReviewQueueInput {
|
|
return ReviewQueueInput{
|
|
Limit: optionIntValue(options, "limit"),
|
|
Reviewer: optionStringValue(options, "reviewer"),
|
|
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
|
|
LocalOnly: optionBoolValue(options, "local_only", "local-only"),
|
|
}
|
|
}
|
|
|
|
func epicInputFromOptions(options core.Options) EpicInput {
|
|
return EpicInput{
|
|
Repo: optionStringValue(options, "repo"),
|
|
Org: optionStringValue(options, "org"),
|
|
Title: optionStringValue(options, "title"),
|
|
Body: optionStringValue(options, "body"),
|
|
Tasks: optionStringSliceValue(options, "tasks"),
|
|
Labels: optionStringSliceValue(options, "labels"),
|
|
Dispatch: optionBoolValue(options, "dispatch"),
|
|
Agent: optionStringValue(options, "agent"),
|
|
Template: optionStringValue(options, "template"),
|
|
}
|
|
}
|
|
|
|
func normaliseForgeActionOptions(options core.Options) core.Options {
|
|
normalised := core.NewOptions(options.Items()...)
|
|
if normalised.String("_arg") == "" {
|
|
if repo := optionStringValue(options, "repo"); repo != "" {
|
|
normalised.Set("_arg", repo)
|
|
}
|
|
}
|
|
if number := optionStringValue(options, "number"); number != "" {
|
|
normalised.Set("number", number)
|
|
}
|
|
return normalised
|
|
}
|
|
|
|
func optionStringValue(options core.Options, keys ...string) string {
|
|
for _, key := range keys {
|
|
result := options.Get(key)
|
|
if !result.OK {
|
|
continue
|
|
}
|
|
if value := stringValue(result.Value); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func optionIntValue(options core.Options, keys ...string) int {
|
|
for _, key := range keys {
|
|
result := options.Get(key)
|
|
if !result.OK {
|
|
continue
|
|
}
|
|
switch value := result.Value.(type) {
|
|
case int:
|
|
return value
|
|
case int64:
|
|
return int(value)
|
|
case float64:
|
|
return int(value)
|
|
case string:
|
|
parsed := parseInt(value)
|
|
if parsed != 0 || core.Trim(value) == "0" {
|
|
return parsed
|
|
}
|
|
return parseIntString(value)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func optionBoolValue(options core.Options, keys ...string) bool {
|
|
for _, key := range keys {
|
|
result := options.Get(key)
|
|
if !result.OK {
|
|
continue
|
|
}
|
|
switch value := result.Value.(type) {
|
|
case bool:
|
|
return value
|
|
case string:
|
|
switch core.Lower(core.Trim(value)) {
|
|
case "1", "true", "yes", "on":
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func optionStringSliceValue(options core.Options, keys ...string) []string {
|
|
for _, key := range keys {
|
|
result := options.Get(key)
|
|
if !result.OK {
|
|
continue
|
|
}
|
|
values := stringSliceValue(result.Value)
|
|
if len(values) > 0 {
|
|
return values
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func optionStringMapValue(options core.Options, keys ...string) map[string]string {
|
|
for _, key := range keys {
|
|
result := options.Get(key)
|
|
if !result.OK {
|
|
continue
|
|
}
|
|
values := stringMapValue(result.Value)
|
|
if len(values) > 0 {
|
|
return values
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func optionAnyValue(options core.Options, keys ...string) any {
|
|
for _, key := range keys {
|
|
result := options.Get(key)
|
|
if !result.OK {
|
|
continue
|
|
}
|
|
return normaliseOptionValue(result.Value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func stringValue(value any) string {
|
|
switch typed := value.(type) {
|
|
case string:
|
|
return typed
|
|
case int:
|
|
return core.Sprint(typed)
|
|
case int64:
|
|
return core.Sprint(typed)
|
|
case float64:
|
|
return core.Sprint(int(typed))
|
|
case bool:
|
|
return core.Sprint(typed)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func stringSliceValue(value any) []string {
|
|
switch typed := value.(type) {
|
|
case []string:
|
|
return cleanStrings(typed)
|
|
case []any:
|
|
var values []string
|
|
for _, item := range typed {
|
|
if text := stringValue(item); text != "" {
|
|
values = append(values, text)
|
|
}
|
|
}
|
|
return cleanStrings(values)
|
|
case string:
|
|
trimmed := core.Trim(typed)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
if core.HasPrefix(trimmed, "[") {
|
|
var values []string
|
|
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
|
|
return cleanStrings(values)
|
|
}
|
|
var generic []any
|
|
if result := core.JSONUnmarshalString(trimmed, &generic); result.OK {
|
|
return stringSliceValue(generic)
|
|
}
|
|
}
|
|
return cleanStrings(core.Split(trimmed, ","))
|
|
default:
|
|
if text := stringValue(value); text != "" {
|
|
return []string{text}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normaliseOptionValue(value any) any {
|
|
switch typed := value.(type) {
|
|
case string:
|
|
trimmed := core.Trim(typed)
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
if core.HasPrefix(trimmed, "{") {
|
|
var values map[string]any
|
|
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
|
|
return values
|
|
}
|
|
}
|
|
if core.HasPrefix(trimmed, "[") {
|
|
var values []any
|
|
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
|
|
return values
|
|
}
|
|
}
|
|
switch core.Lower(trimmed) {
|
|
case "true":
|
|
return true
|
|
case "false":
|
|
return false
|
|
}
|
|
if parsed := parseInt(trimmed); parsed != 0 || trimmed == "0" {
|
|
return parsed
|
|
}
|
|
return typed
|
|
default:
|
|
return value
|
|
}
|
|
}
|
|
|
|
func stringMapValue(value any) map[string]string {
|
|
switch typed := value.(type) {
|
|
case map[string]string:
|
|
out := make(map[string]string, len(typed))
|
|
for key, val := range typed {
|
|
if text := core.Trim(val); text != "" {
|
|
out[key] = text
|
|
}
|
|
}
|
|
return out
|
|
case map[string]any:
|
|
out := make(map[string]string, len(typed))
|
|
for key, val := range typed {
|
|
if text := stringValue(val); text != "" {
|
|
out[key] = text
|
|
}
|
|
}
|
|
return out
|
|
case []string:
|
|
out := make(map[string]string, len(typed))
|
|
for _, item := range typed {
|
|
mergeStringMapEntry(out, item)
|
|
}
|
|
return out
|
|
case []any:
|
|
out := make(map[string]string, len(typed))
|
|
for _, item := range typed {
|
|
mergeStringMapEntry(out, stringValue(item))
|
|
}
|
|
return out
|
|
case string:
|
|
trimmed := core.Trim(typed)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
if core.HasPrefix(trimmed, "{") {
|
|
var values map[string]string
|
|
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
|
|
return stringMapValue(values)
|
|
}
|
|
var generic map[string]any
|
|
if result := core.JSONUnmarshalString(trimmed, &generic); result.OK {
|
|
return stringMapValue(generic)
|
|
}
|
|
}
|
|
out := make(map[string]string)
|
|
for _, pair := range core.Split(trimmed, ",") {
|
|
mergeStringMapEntry(out, pair)
|
|
}
|
|
if len(out) > 0 {
|
|
return out
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mergeStringMapEntry(values map[string]string, entry string) {
|
|
trimmed := core.Trim(entry)
|
|
if trimmed == "" {
|
|
return
|
|
}
|
|
|
|
parts := core.SplitN(trimmed, "=", 2)
|
|
if len(parts) != 2 {
|
|
return
|
|
}
|
|
|
|
key := core.Trim(parts[0])
|
|
value := core.Trim(parts[1])
|
|
if key == "" || value == "" {
|
|
return
|
|
}
|
|
|
|
values[key] = value
|
|
}
|
|
|
|
func cleanStrings(values []string) []string {
|
|
var cleaned []string
|
|
for _, value := range values {
|
|
trimmed := core.Trim(value)
|
|
if trimmed != "" {
|
|
cleaned = append(cleaned, trimmed)
|
|
}
|
|
}
|
|
return cleaned
|
|
}
|
|
|
|
// result := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-42"})
|
|
// result := c.QUERY(agentic.WorkspaceQuery{Status: "blocked"})
|
|
func (s *PrepSubsystem) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Result {
|
|
workspaceQuery, ok := query.(WorkspaceQuery)
|
|
if !ok {
|
|
return core.Result{}
|
|
}
|
|
if workspaceQuery.Name != "" {
|
|
return s.workspaces.Get(workspaceQuery.Name)
|
|
}
|
|
if workspaceQuery.Status != "" {
|
|
var names []string
|
|
s.workspaces.Each(func(name string, workspaceStatus *WorkspaceStatus) {
|
|
if workspaceStatus.Status == workspaceQuery.Status {
|
|
names = append(names, name)
|
|
}
|
|
})
|
|
return core.Result{Value: names, OK: true}
|
|
}
|
|
return core.Result{Value: s.workspaces, OK: true}
|
|
}
|