1332 lines
49 KiB
Go
1332 lines
49 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
// c := core.New(core.WithOption("name", "core-agent"))
|
|
// registerApplicationCommands(c)
|
|
|
|
package agentic
|
|
|
|
import (
|
|
"context"
|
|
|
|
"dappco.re/go/agent/pkg/lib"
|
|
core "dappco.re/go/core"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// c.Command("run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTask})
|
|
// c.Command("prep", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep})
|
|
func (s *PrepSubsystem) registerCommands(ctx context.Context) {
|
|
s.startupContext = ctx
|
|
c := s.Core()
|
|
c.Command("run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTask})
|
|
c.Command("run/flow", core.Command{Description: "Show a flow definition from disk or the embedded library", Action: s.cmdRunFlow})
|
|
c.Command("flow/preview", core.Command{Description: "Preview a flow definition with optional variable substitution", Action: s.cmdFlowPreview})
|
|
c.Command("agentic:flow/preview", core.Command{Description: "Preview a flow definition with optional variable substitution", Action: s.cmdFlowPreview})
|
|
c.Command("dispatch/sync", core.Command{Description: "Dispatch a single task synchronously and block until it completes", Action: s.cmdDispatchSync})
|
|
c.Command("run/orchestrator", core.Command{Description: "Run the queue orchestrator (standalone, no MCP)", Action: s.cmdOrchestrator})
|
|
c.Command("dispatch", core.Command{Description: "Dispatch queued agents", Action: s.cmdDispatch})
|
|
c.Command("agentic:dispatch", core.Command{Description: "Dispatch queued agents", Action: s.cmdDispatch})
|
|
c.Command("dispatch/start", core.Command{Description: "Start the dispatch queue runner", Action: s.cmdDispatchStart})
|
|
c.Command("agentic:dispatch/start", core.Command{Description: "Start the dispatch queue runner", Action: s.cmdDispatchStart})
|
|
c.Command("dispatch/shutdown", core.Command{Description: "Freeze the dispatch queue gracefully", Action: s.cmdDispatchShutdown})
|
|
c.Command("agentic:dispatch/shutdown", core.Command{Description: "Freeze the dispatch queue gracefully", Action: s.cmdDispatchShutdown})
|
|
c.Command("dispatch/shutdown-now", core.Command{Description: "Hard stop the dispatch queue and kill running agents", Action: s.cmdDispatchShutdownNow})
|
|
c.Command("agentic:dispatch/shutdown-now", core.Command{Description: "Hard stop the dispatch queue and kill running agents", Action: s.cmdDispatchShutdownNow})
|
|
c.Command("poke", core.Command{Description: "Drain the dispatch queue immediately", Action: s.cmdPoke})
|
|
c.Command("agentic:poke", core.Command{Description: "Drain the dispatch queue immediately", Action: s.cmdPoke})
|
|
c.Command("prep", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep})
|
|
c.Command("prep-workspace", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep})
|
|
c.Command("agentic:prep-workspace", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep})
|
|
c.Command("resume", core.Command{Description: "Resume a blocked or completed workspace", Action: s.cmdResume})
|
|
c.Command("agentic:resume", core.Command{Description: "Resume a blocked or completed workspace", Action: s.cmdResume})
|
|
c.Command("generate", core.Command{Description: "Generate content from a prompt using the platform content pipeline", Action: s.cmdGenerate})
|
|
c.Command("agentic:generate", core.Command{Description: "Generate content from a prompt using the platform content pipeline", Action: s.cmdGenerate})
|
|
c.Command("content/generate", core.Command{Description: "Generate content from a prompt using the platform content pipeline", Action: s.cmdGenerate})
|
|
c.Command("agentic:content/generate", core.Command{Description: "Generate content from a prompt using the platform content pipeline", Action: s.cmdGenerate})
|
|
c.Command("content/schema/generate", core.Command{Description: "Generate SEO schema JSON-LD for article, FAQ, or how-to content", Action: s.cmdContentSchemaGenerate})
|
|
c.Command("agentic:content/schema/generate", core.Command{Description: "Generate SEO schema JSON-LD for article, FAQ, or how-to content", Action: s.cmdContentSchemaGenerate})
|
|
c.Command("complete", core.Command{Description: "Run the completion pipeline (QA → PR → Verify → Commit → Ingest → Poke)", Action: s.cmdComplete})
|
|
c.Command("agentic:complete", core.Command{Description: "Run the completion pipeline (QA → PR → Verify → Commit → Ingest → Poke)", Action: s.cmdComplete})
|
|
c.Command("scan", core.Command{Description: "Scan Forge repos for actionable issues", Action: s.cmdScan})
|
|
c.Command("agentic:scan", core.Command{Description: "Scan Forge repos for actionable issues", Action: s.cmdScan})
|
|
c.Command("mirror", core.Command{Description: "Mirror Forge repos to GitHub", Action: s.cmdMirror})
|
|
c.Command("agentic:mirror", core.Command{Description: "Mirror Forge repos to GitHub", Action: s.cmdMirror})
|
|
c.Command("brain/ingest", core.Command{Description: "Bulk ingest memories into OpenBrain", Action: s.cmdBrainIngest})
|
|
c.Command("brain:ingest", core.Command{Description: "Bulk ingest memories into OpenBrain", Action: s.cmdBrainIngest})
|
|
c.Command("brain/recall", core.Command{Description: "Recall memories from OpenBrain", Action: s.cmdBrainRecall})
|
|
c.Command("brain:recall", core.Command{Description: "Recall memories from OpenBrain", Action: s.cmdBrainRecall})
|
|
c.Command("brain/remember", core.Command{Description: "Store a memory in OpenBrain", Action: s.cmdBrainRemember})
|
|
c.Command("brain:remember", core.Command{Description: "Store a memory in OpenBrain", Action: s.cmdBrainRemember})
|
|
c.Command("brain/seed-memory", core.Command{Description: "Import markdown memories into OpenBrain from a project memory file or directory", Action: s.cmdBrainSeedMemory})
|
|
c.Command("brain:seed-memory", core.Command{Description: "Import markdown memories into OpenBrain from a project memory file or directory", Action: s.cmdBrainSeedMemory})
|
|
c.Command("brain/list", core.Command{Description: "List memories in OpenBrain", Action: s.cmdBrainList})
|
|
c.Command("brain:list", core.Command{Description: "List memories in OpenBrain", Action: s.cmdBrainList})
|
|
c.Command("brain/forget", core.Command{Description: "Forget a memory in OpenBrain", Action: s.cmdBrainForget})
|
|
c.Command("brain:forget", core.Command{Description: "Forget a memory in OpenBrain", Action: s.cmdBrainForget})
|
|
c.Command("lang/detect", core.Command{Description: "Detect the primary language for a repository or workspace", Action: s.cmdLangDetect})
|
|
c.Command("lang/list", core.Command{Description: "List supported language identifiers", Action: s.cmdLangList})
|
|
c.Command("epic", core.Command{Description: "Create sub-issues from an epic plan", Action: s.cmdEpic})
|
|
c.Command("agentic:epic", core.Command{Description: "Create sub-issues from an epic plan", Action: s.cmdEpic})
|
|
c.Command("plan-cleanup", core.Command{Description: "Archive old completed plans and delete stale archives past the retention period", Action: s.cmdPlanCleanup})
|
|
c.Command("agentic:plan-cleanup", core.Command{Description: "Archive old completed plans and delete stale archives past the retention period", Action: s.cmdPlanCleanup})
|
|
c.Command("pr-manage", core.Command{Description: "Manage open PRs (merge, close, review)", Action: s.cmdPRManage})
|
|
c.Command("agentic:pr-manage", core.Command{Description: "Manage open PRs (merge, close, review)", Action: s.cmdPRManage})
|
|
c.Command("review-queue", core.Command{Description: "Process the CodeRabbit review queue", Action: s.cmdReviewQueue})
|
|
c.Command("agentic:review-queue", core.Command{Description: "Process the CodeRabbit review queue", Action: s.cmdReviewQueue})
|
|
c.Command("status", core.Command{Description: "List agent workspace statuses", Action: s.cmdStatus})
|
|
c.Command("agentic:status", core.Command{Description: "List agent workspace statuses", Action: s.cmdStatus})
|
|
c.Command("prompt", core.Command{Description: "Build and display an agent prompt for a repo", Action: s.cmdPrompt})
|
|
c.Command("agentic:prompt", core.Command{Description: "Build and display an agent prompt for a repo", Action: s.cmdPrompt})
|
|
c.Command("prompt_version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion})
|
|
c.Command("agentic:prompt_version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion})
|
|
c.Command("prompt/version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion})
|
|
c.Command("agentic:prompt/version", core.Command{Description: "Read the current prompt snapshot for a workspace", Action: s.cmdPromptVersion})
|
|
c.Command("extract", core.Command{Description: "Extract data from agent output or scaffold a workspace template", Action: s.cmdExtract})
|
|
s.registerPlanCommands()
|
|
s.registerCommitCommands()
|
|
s.registerSessionCommands()
|
|
s.registerTaskCommands()
|
|
s.registerSprintCommands()
|
|
s.registerStateCommands()
|
|
s.registerLanguageCommands()
|
|
s.registerSetupCommands()
|
|
}
|
|
|
|
// ctx := s.commandContext()
|
|
func (s *PrepSubsystem) commandContext() context.Context {
|
|
if s.startupContext != nil {
|
|
return s.startupContext
|
|
}
|
|
return context.Background()
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdRunTask(options core.Options) core.Result {
|
|
return s.runDispatchSync(s.commandContext(), options, "run task", "agentic.runTask")
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdRunFlow(options core.Options) core.Result {
|
|
return s.runFlowCommand(options, "run flow")
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdFlowPreview(options core.Options) core.Result {
|
|
return s.runFlowCommand(options, "flow preview")
|
|
}
|
|
|
|
func (s *PrepSubsystem) runFlowCommand(options core.Options, commandLabel string) core.Result {
|
|
flowPath := optionStringValue(options, "_arg", "path", "slug")
|
|
if flowPath == "" {
|
|
core.Print(nil, "usage: core-agent %s <path-or-slug> [--dry-run] [--var=key=value] [--vars='{\"key\":\"value\"}'] [--variables='{\"key\":\"value\"}']", commandLabel)
|
|
return core.Result{Value: core.E("agentic.cmdRunFlow", "flow path or slug is required", nil), OK: false}
|
|
}
|
|
|
|
dryRun := optionBoolValue(options, "dry_run", "dry-run")
|
|
variables := optionStringMapValue(options, "var", "vars", "variables")
|
|
|
|
flowResult := readFlowDocument(flowPath, variables)
|
|
if !flowResult.OK {
|
|
core.Print(nil, "error: %v", flowResult.Value)
|
|
return core.Result{Value: flowResult.Value, OK: false}
|
|
}
|
|
|
|
document, ok := flowResult.Value.(flowRunDocument)
|
|
if !ok {
|
|
err := core.E("agentic.cmdRunFlow", "invalid flow definition", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
output := FlowRunOutput{
|
|
Success: true,
|
|
Source: document.Source,
|
|
Name: document.Definition.Name,
|
|
Description: document.Definition.Description,
|
|
Steps: len(document.Definition.Steps),
|
|
Parsed: document.Parsed,
|
|
}
|
|
|
|
core.Print(nil, "flow: %s", document.Source)
|
|
if dryRun {
|
|
core.Print(nil, "dry-run: true")
|
|
}
|
|
if len(variables) > 0 {
|
|
core.Print(nil, "vars: %d", len(variables))
|
|
}
|
|
if document.Parsed {
|
|
if document.Definition.Name != "" {
|
|
core.Print(nil, "name: %s", document.Definition.Name)
|
|
}
|
|
if document.Definition.Description != "" {
|
|
core.Print(nil, "desc: %s", document.Definition.Description)
|
|
}
|
|
if len(document.Definition.Steps) == 0 {
|
|
core.Print(nil, "steps: 0")
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
core.Print(nil, "steps: %d", len(document.Definition.Steps))
|
|
resolvedSteps := s.printFlowSteps(document, "", variables, map[string]bool{document.Source: true})
|
|
output.ResolvedSteps = resolvedSteps
|
|
if resolvedSteps != len(document.Definition.Steps) {
|
|
core.Print(nil, "resolved steps: %d", resolvedSteps)
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
if document.Content != "" {
|
|
core.Print(nil, "content: %d chars", len(document.Content))
|
|
}
|
|
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdDispatchSync(options core.Options) core.Result {
|
|
return s.runDispatchSync(s.commandContext(), options, "dispatch sync", "agentic.runDispatchSync")
|
|
}
|
|
|
|
func (s *PrepSubsystem) runDispatchSync(ctx context.Context, options core.Options, commandLabel, errorName string) core.Result {
|
|
repo := options.String("repo")
|
|
agent := options.String("agent")
|
|
task := options.String("task")
|
|
issueValue := options.String("issue")
|
|
org := options.String("org")
|
|
|
|
if repo == "" || task == "" {
|
|
core.Print(nil, "usage: core-agent %s --repo=<repo> --task=\"...\" --agent=codex [--issue=N] [--org=core]", commandLabel)
|
|
return core.Result{Value: core.E(errorName, "repo and task are required", nil), OK: false}
|
|
}
|
|
if agent == "" {
|
|
agent = "codex"
|
|
}
|
|
if org == "" {
|
|
org = "core"
|
|
}
|
|
|
|
issue := parseIntString(issueValue)
|
|
|
|
core.Print(nil, "core-agent %s", commandLabel)
|
|
core.Print(nil, " repo: %s/%s", org, repo)
|
|
core.Print(nil, " agent: %s", agent)
|
|
if issue > 0 {
|
|
core.Print(nil, " issue: #%d", issue)
|
|
}
|
|
core.Print(nil, " task: %s", task)
|
|
core.Print(nil, "")
|
|
|
|
result := s.DispatchSync(ctx, DispatchSyncInput{
|
|
Org: org, Repo: repo, Agent: agent, Task: task, Issue: issue,
|
|
})
|
|
|
|
if !result.OK {
|
|
failureError := result.Error
|
|
if failureError == nil {
|
|
failureError = core.E(errorName, "dispatch failed", nil)
|
|
}
|
|
core.Print(nil, "FAILED: %v", failureError)
|
|
return core.Result{Value: failureError, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "DONE: %s", result.Status)
|
|
if result.PRURL != "" {
|
|
core.Print(nil, " PR: %s", result.PRURL)
|
|
}
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdOrchestrator(_ core.Options) core.Result {
|
|
return s.runDispatchLoop("orchestrator")
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdDispatch(_ core.Options) core.Result {
|
|
return s.runDispatchLoop("dispatch")
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdDispatchStart(_ core.Options) core.Result {
|
|
_, output, err := s.dispatchStart(s.commandContext(), nil, ShutdownInput{})
|
|
if err != nil {
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
if output.Message != "" {
|
|
core.Print(nil, "%s", output.Message)
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdDispatchShutdown(_ core.Options) core.Result {
|
|
_, output, err := s.shutdownGraceful(s.commandContext(), nil, ShutdownInput{})
|
|
if err != nil {
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
if output.Message != "" {
|
|
core.Print(nil, "%s", output.Message)
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdDispatchShutdownNow(_ core.Options) core.Result {
|
|
_, output, err := s.shutdownNow(s.commandContext(), nil, ShutdownInput{})
|
|
if err != nil {
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
if output.Message != "" {
|
|
core.Print(nil, "%s", output.Message)
|
|
}
|
|
if output.Running > 0 || output.Queued > 0 {
|
|
core.Print(nil, "running: %d", output.Running)
|
|
core.Print(nil, "queued: %d", output.Queued)
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdPoke(_ core.Options) core.Result {
|
|
s.Poke()
|
|
core.Print(nil, "queue poke requested")
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) runDispatchLoop(label string) core.Result {
|
|
ctx := s.commandContext()
|
|
core.Print(nil, "core-agent %s running (pid %s)", label, core.Env("PID"))
|
|
core.Print(nil, " workspace: %s", WorkspaceRoot())
|
|
core.Print(nil, " watching queue, draining on 30s tick + completion poke")
|
|
|
|
<-ctx.Done()
|
|
core.Print(nil, "%s shutting down", label)
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdPrep(options core.Options) core.Result {
|
|
repo := options.String("_arg")
|
|
if repo == "" {
|
|
core.Print(nil, "usage: core-agent prep <repo> --issue=N|--pr=N|--branch=X --task=\"...\" [--plan-template=bug-fix] [--variables='{\"key\":\"value\"}']")
|
|
return core.Result{Value: core.E("agentic.cmdPrep", "repo is required", nil), OK: false}
|
|
}
|
|
|
|
prepInput := prepInputFromCommandOptions(options)
|
|
prepInput.Repo = repo
|
|
|
|
if prepInput.Issue == 0 && prepInput.PR == 0 && prepInput.Branch == "" && prepInput.Tag == "" {
|
|
prepInput.Branch = "dev"
|
|
}
|
|
|
|
_, prepOutput, err := s.PrepareWorkspace(context.Background(), prepInput)
|
|
if err != nil {
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "workspace: %s", prepOutput.WorkspaceDir)
|
|
core.Print(nil, "repo: %s", prepOutput.RepoDir)
|
|
core.Print(nil, "branch: %s", prepOutput.Branch)
|
|
if prepOutput.PromptVersion != "" {
|
|
core.Print(nil, "prompt: %s", prepOutput.PromptVersion)
|
|
}
|
|
core.Print(nil, "resumed: %v", prepOutput.Resumed)
|
|
core.Print(nil, "memories: %d", prepOutput.Memories)
|
|
core.Print(nil, "consumers: %d", prepOutput.Consumers)
|
|
if prepOutput.Prompt != "" {
|
|
core.Print(nil, "")
|
|
core.Print(nil, "--- prompt (%d chars) ---", len(prepOutput.Prompt))
|
|
core.Print(nil, "%s", prepOutput.Prompt)
|
|
}
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdResume(options core.Options) core.Result {
|
|
workspace := optionStringValue(options, "workspace", "_arg")
|
|
if workspace == "" {
|
|
core.Print(nil, "usage: core-agent resume <workspace> [--answer=\"...\"] [--agent=codex] [--dry-run]")
|
|
return core.Result{Value: core.E("agentic.cmdResume", "workspace is required", nil), OK: false}
|
|
}
|
|
|
|
_, output, err := s.resume(s.commandContext(), nil, ResumeInput{
|
|
Workspace: workspace,
|
|
Answer: optionStringValue(options, "answer"),
|
|
Agent: optionStringValue(options, "agent"),
|
|
DryRun: optionBoolValue(options, "dry_run", "dry-run"),
|
|
})
|
|
if err != nil {
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "workspace: %s", output.Workspace)
|
|
core.Print(nil, "agent: %s", output.Agent)
|
|
if output.PID > 0 {
|
|
core.Print(nil, "pid: %d", output.PID)
|
|
}
|
|
if output.OutputFile != "" {
|
|
core.Print(nil, "output: %s", output.OutputFile)
|
|
}
|
|
if output.Prompt != "" {
|
|
core.Print(nil, "")
|
|
core.Print(nil, "--- prompt (%d chars) ---", len(output.Prompt))
|
|
core.Print(nil, "%s", output.Prompt)
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdGenerate(options core.Options) core.Result {
|
|
prompt := optionStringValue(options, "prompt", "_arg")
|
|
briefID := optionStringValue(options, "brief_id", "brief-id")
|
|
template := optionStringValue(options, "template")
|
|
if prompt == "" && (briefID == "" || template == "") {
|
|
core.Print(nil, "usage: core-agent generate --prompt=\"Draft a release note\" [--brief-id=brief_1 --template=help-article] [--provider=claude] [--config='{\"max_tokens\":4000}']")
|
|
return core.Result{Value: core.E("agentic.cmdGenerate", "prompt or brief-id/template is required", nil), OK: false}
|
|
}
|
|
|
|
result := s.handleContentGenerate(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "prompt", Value: prompt},
|
|
core.Option{Key: "brief_id", Value: briefID},
|
|
core.Option{Key: "template", Value: template},
|
|
core.Option{Key: "provider", Value: options.String("provider")},
|
|
core.Option{Key: "config", Value: options.String("config")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdGenerate", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
output, ok := result.Value.(ContentGenerateOutput)
|
|
if !ok {
|
|
err := core.E("agentic.cmdGenerate", "invalid content generate output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
if output.Result.Provider != "" {
|
|
core.Print(nil, "provider: %s", output.Result.Provider)
|
|
}
|
|
if output.Result.Model != "" {
|
|
core.Print(nil, "model: %s", output.Result.Model)
|
|
}
|
|
if output.Result.Status != "" {
|
|
core.Print(nil, "status: %s", output.Result.Status)
|
|
}
|
|
if output.Result.Content != "" {
|
|
core.Print(nil, "content: %s", output.Result.Content)
|
|
}
|
|
if output.Result.InputTokens > 0 || output.Result.OutputTokens > 0 {
|
|
core.Print(nil, "tokens: %d in / %d out", output.Result.InputTokens, output.Result.OutputTokens)
|
|
}
|
|
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdContentSchemaGenerate(options core.Options) core.Result {
|
|
schemaType := optionStringValue(options, "schema_type", "schema-type", "type", "kind")
|
|
title := optionStringValue(options, "title", "headline")
|
|
if schemaType == "" || title == "" {
|
|
core.Print(nil, "usage: core-agent content schema generate --type=howto --title=\"Set up the workspace\" [--description=\"...\"] [--url=\"https://example.test/setup\"] [--author=\"Virgil\"] [--questions='[{\"question\":\"...\",\"answer\":\"...\"}]'] [--steps='[{\"name\":\"...\",\"text\":\"...\"}]']")
|
|
return core.Result{Value: core.E("agentic.cmdContentSchemaGenerate", "schema type and title are required", nil), OK: false}
|
|
}
|
|
|
|
result := s.handleContentSchemaGenerate(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "schema_type", Value: schemaType},
|
|
core.Option{Key: "title", Value: title},
|
|
core.Option{Key: "description", Value: optionStringValue(options, "description")},
|
|
core.Option{Key: "url", Value: optionStringValue(options, "url", "link")},
|
|
core.Option{Key: "author", Value: optionStringValue(options, "author")},
|
|
core.Option{Key: "published_at", Value: optionStringValue(options, "published_at", "published-at", "date_published")},
|
|
core.Option{Key: "modified_at", Value: optionStringValue(options, "modified_at", "modified-at", "date_modified")},
|
|
core.Option{Key: "language", Value: optionStringValue(options, "language", "in_language", "in-language")},
|
|
core.Option{Key: "image", Value: optionStringValue(options, "image", "image_url", "image-url")},
|
|
core.Option{Key: "questions", Value: optionAnyValue(options, "questions", "faq")},
|
|
core.Option{Key: "steps", Value: optionAnyValue(options, "steps")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdContentSchemaGenerate", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
output, ok := result.Value.(ContentSchemaOutput)
|
|
if !ok {
|
|
err := core.E("agentic.cmdContentSchemaGenerate", "invalid content schema output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "schema type: %s", output.SchemaType)
|
|
core.Print(nil, "schema json: %s", output.SchemaJSON)
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdComplete(options core.Options) core.Result {
|
|
result := s.handleComplete(s.commandContext(), options)
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdComplete", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdScan(options core.Options) core.Result {
|
|
result := s.handleScan(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "org", Value: optionStringValue(options, "org")},
|
|
core.Option{Key: "labels", Value: optionStringSliceValue(options, "labels")},
|
|
core.Option{Key: "limit", Value: optionIntValue(options, "limit")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdScan", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
output, ok := result.Value.(ScanOutput)
|
|
if !ok {
|
|
err := core.E("agentic.cmdScan", "invalid scan output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "count: %d", output.Count)
|
|
for _, issue := range output.Issues {
|
|
if len(issue.Labels) > 0 {
|
|
core.Print(nil, " %s#%d %s [%s]", issue.Repo, issue.Number, issue.Title, core.Join(",", issue.Labels...))
|
|
continue
|
|
}
|
|
core.Print(nil, " %s#%d %s", issue.Repo, issue.Number, issue.Title)
|
|
}
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdMirror(options core.Options) core.Result {
|
|
result := s.handleMirror(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "repo", Value: optionStringValue(options, "repo", "_arg")},
|
|
core.Option{Key: "dry_run", Value: optionBoolValue(options, "dry_run", "dry-run")},
|
|
core.Option{Key: "max_files", Value: optionIntValue(options, "max_files", "max-files")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdMirror", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
output, ok := result.Value.(MirrorOutput)
|
|
if !ok {
|
|
err := core.E("agentic.cmdMirror", "invalid mirror output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "count: %d", output.Count)
|
|
for _, item := range output.Synced {
|
|
core.Print(nil, " %s commits=%d files=%d", item.Repo, item.CommitsAhead, item.FilesChanged)
|
|
if item.PRURL != "" {
|
|
core.Print(nil, " pr: %s", item.PRURL)
|
|
}
|
|
if item.Skipped != "" {
|
|
core.Print(nil, " %s", item.Skipped)
|
|
}
|
|
}
|
|
for _, skipped := range output.Skipped {
|
|
core.Print(nil, "skipped: %s", skipped)
|
|
}
|
|
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdBrainList(options core.Options) core.Result {
|
|
result := s.Core().Action("brain.list").Run(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "project", Value: optionStringValue(options, "project")},
|
|
core.Option{Key: "type", Value: optionStringValue(options, "type")},
|
|
core.Option{Key: "agent_id", Value: optionStringValue(options, "agent_id", "agent")},
|
|
core.Option{Key: "limit", Value: optionIntValue(options, "limit")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdBrainList", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
payload, ok := result.Value.(map[string]any)
|
|
if !ok {
|
|
jsonResult := core.JSONMarshalString(result.Value)
|
|
if jsonResult == "" {
|
|
err := core.E("agentic.cmdBrainList", "invalid brain list output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
var decoded any
|
|
if parseResult := core.JSONUnmarshalString(jsonResult, &decoded); !parseResult.OK {
|
|
err, _ := parseResult.Value.(error)
|
|
if err == nil {
|
|
err = core.E("agentic.cmdBrainList", "invalid brain list output", nil)
|
|
}
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
payload, ok = decoded.(map[string]any)
|
|
if !ok {
|
|
err := core.E("agentic.cmdBrainList", "invalid brain list output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
}
|
|
|
|
output := brainListOutputFromPayload(payload)
|
|
core.Print(nil, "count: %d", output.Count)
|
|
if len(output.Memories) == 0 {
|
|
core.Print(nil, "no memories")
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
for _, memory := range output.Memories {
|
|
if memory.Project != "" || memory.AgentID != "" || memory.Confidence != 0 {
|
|
core.Print(nil, " %s %-12s %s %s %.2f", memory.ID, memory.Type, memory.Project, memory.AgentID, memory.Confidence)
|
|
} else {
|
|
core.Print(nil, " %s %-12s", memory.ID, memory.Type)
|
|
}
|
|
if memory.SupersedesCount > 0 {
|
|
core.Print(nil, " supersedes: %d", memory.SupersedesCount)
|
|
}
|
|
if memory.DeletedAt != "" {
|
|
core.Print(nil, " deleted_at: %s", memory.DeletedAt)
|
|
}
|
|
if memory.Content != "" {
|
|
core.Print(nil, " %s", memory.Content)
|
|
}
|
|
}
|
|
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Command("brain/remember").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "content", Value: "Use named actions."},
|
|
// core.Option{Key: "type", Value: "convention"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) cmdBrainRemember(options core.Options) core.Result {
|
|
content := optionStringValue(options, "content", "_arg")
|
|
memoryType := optionStringValue(options, "type")
|
|
if core.Trim(content) == "" || memoryType == "" {
|
|
core.Print(nil, "usage: core-agent brain remember <content> --type=observation [--tags=architecture,convention] [--project=agent] [--confidence=0.8] [--supersedes=mem-123] [--expires-in=24]")
|
|
return core.Result{Value: core.E("agentic.cmdBrainRemember", "content and type are required", nil), OK: false}
|
|
}
|
|
|
|
result := s.Core().Action("brain.remember").Run(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "content", Value: content},
|
|
core.Option{Key: "type", Value: memoryType},
|
|
core.Option{Key: "tags", Value: optionStringSliceValue(options, "tags")},
|
|
core.Option{Key: "project", Value: optionStringValue(options, "project")},
|
|
core.Option{Key: "confidence", Value: optionStringValue(options, "confidence")},
|
|
core.Option{Key: "supersedes", Value: optionStringValue(options, "supersedes")},
|
|
core.Option{Key: "expires_in", Value: optionIntValue(options, "expires_in", "expires-in")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdBrainRemember", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
jsonResult := core.JSONMarshalString(result.Value)
|
|
if jsonResult == "" {
|
|
err := core.E("agentic.cmdBrainRemember", "invalid brain remember output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
var decoded map[string]any
|
|
if parseResult := core.JSONUnmarshalString(jsonResult, &decoded); !parseResult.OK {
|
|
err := core.E("agentic.cmdBrainRemember", "invalid brain remember output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
memoryID := stringValue(decoded["memoryId"])
|
|
if memoryID == "" {
|
|
memoryID = stringValue(decoded["memory_id"])
|
|
}
|
|
core.Print(nil, "remembered: %s", memoryID)
|
|
if timestamp := stringValue(decoded["timestamp"]); timestamp != "" {
|
|
core.Print(nil, "timestamp: %s", timestamp)
|
|
}
|
|
return core.Result{Value: decoded, OK: true}
|
|
}
|
|
|
|
// result := c.Command("brain/recall").Run(ctx, core.NewOptions(
|
|
//
|
|
// core.Option{Key: "query", Value: "workspace handoff context"},
|
|
//
|
|
// ))
|
|
func (s *PrepSubsystem) cmdBrainRecall(options core.Options) core.Result {
|
|
query := optionStringValue(options, "query", "_arg")
|
|
if query == "" {
|
|
core.Print(nil, "usage: core-agent brain recall <query> [--top-k=10] [--project=agent] [--type=architecture] [--agent=virgil] [--min-confidence=0.7]")
|
|
return core.Result{Value: core.E("agentic.cmdBrainRecall", "query is required", nil), OK: false}
|
|
}
|
|
|
|
result := s.Core().Action("brain.recall").Run(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "query", Value: query},
|
|
core.Option{Key: "top_k", Value: optionIntValue(options, "top_k", "top-k")},
|
|
core.Option{Key: "project", Value: optionStringValue(options, "project")},
|
|
core.Option{Key: "type", Value: optionStringValue(options, "type")},
|
|
core.Option{Key: "agent_id", Value: optionStringValue(options, "agent_id", "agent")},
|
|
core.Option{Key: "min_confidence", Value: optionStringValue(options, "min_confidence", "min-confidence")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdBrainRecall", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
output, ok := brainRecallOutputFromResult(result.Value)
|
|
if !ok {
|
|
err := core.E("agentic.cmdBrainRecall", "invalid brain recall output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "count: %d", output.Count)
|
|
if len(output.Memories) == 0 {
|
|
core.Print(nil, "no memories")
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
for _, memory := range output.Memories {
|
|
if memory.Project != "" || memory.AgentID != "" || memory.Confidence != 0 {
|
|
core.Print(nil, " %s %-12s %s %s %.2f", memory.ID, memory.Type, memory.Project, memory.AgentID, memory.Confidence)
|
|
} else {
|
|
core.Print(nil, " %s %-12s", memory.ID, memory.Type)
|
|
}
|
|
if memory.Content != "" {
|
|
core.Print(nil, " %s", memory.Content)
|
|
}
|
|
}
|
|
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
// result := c.Command("brain/forget").Run(ctx, core.NewOptions(core.Option{Key: "_arg", Value: "mem-1"}))
|
|
func (s *PrepSubsystem) cmdBrainForget(options core.Options) core.Result {
|
|
id := optionStringValue(options, "id", "_arg")
|
|
if id == "" {
|
|
core.Print(nil, "usage: core-agent brain forget <memory-id> [--reason=\"superseded\"]")
|
|
return core.Result{Value: core.E("agentic.cmdBrainForget", "memory id is required", nil), OK: false}
|
|
}
|
|
|
|
result := s.Core().Action("brain.forget").Run(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "id", Value: id},
|
|
core.Option{Key: "reason", Value: optionStringValue(options, "reason")},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdBrainForget", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "forgotten: %s", id)
|
|
if reason := optionStringValue(options, "reason"); reason != "" {
|
|
core.Print(nil, "reason: %s", reason)
|
|
}
|
|
return core.Result{Value: result.Value, OK: true}
|
|
}
|
|
|
|
type brainRecallOutput struct {
|
|
Count int `json:"count"`
|
|
Memories []brainRecallMemory `json:"memories"`
|
|
}
|
|
|
|
type brainRecallMemory struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Content string `json:"content"`
|
|
Project string `json:"project"`
|
|
AgentID string `json:"agent_id"`
|
|
Confidence float64 `json:"confidence"`
|
|
DeletedAt string `json:"deleted_at,omitempty"`
|
|
Tags []string `json:"tags"`
|
|
}
|
|
|
|
func brainRecallOutputFromResult(value any) (brainRecallOutput, bool) {
|
|
switch typed := value.(type) {
|
|
case brainRecallOutput:
|
|
return typed, true
|
|
case *brainRecallOutput:
|
|
if typed == nil {
|
|
return brainRecallOutput{}, false
|
|
}
|
|
return *typed, true
|
|
default:
|
|
jsonResult := core.JSONMarshalString(value)
|
|
if jsonResult == "" {
|
|
return brainRecallOutput{}, false
|
|
}
|
|
var output brainRecallOutput
|
|
if parseResult := core.JSONUnmarshalString(jsonResult, &output); !parseResult.OK {
|
|
return brainRecallOutput{}, false
|
|
}
|
|
return output, true
|
|
}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdStatus(options core.Options) core.Result {
|
|
workspaceRoot := WorkspaceRoot()
|
|
filesystem := s.Core().Fs()
|
|
listResult := filesystem.List(workspaceRoot)
|
|
if !listResult.OK {
|
|
core.Print(nil, "no workspaces found at %s", workspaceRoot)
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
statusFiles := WorkspaceStatusPaths()
|
|
if len(statusFiles) == 0 {
|
|
core.Print(nil, "no workspaces")
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
requestedWorkspace := optionStringValue(options, "workspace", "_arg")
|
|
requestedStatus := optionStringValue(options, "status")
|
|
limit := optionIntValue(options, "limit")
|
|
matched := 0
|
|
|
|
for _, sf := range statusFiles {
|
|
workspaceDir := core.PathDir(sf)
|
|
workspaceName := WorkspaceName(workspaceDir)
|
|
if !statusInputMatchesWorkspace(requestedWorkspace, workspaceDir, workspaceName) {
|
|
continue
|
|
}
|
|
|
|
result := ReadStatusResult(workspaceDir)
|
|
workspaceStatus, ok := workspaceStatusValue(result)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if !statusInputMatchesStatus(requestedStatus, workspaceStatus.Status) {
|
|
continue
|
|
}
|
|
|
|
core.Print(nil, " %-8s %-8s %-10s %s", workspaceStatus.Status, workspaceStatus.Agent, workspaceStatus.Repo, workspaceName)
|
|
if workspaceStatus.Question != "" {
|
|
core.Print(nil, " question: %s", workspaceStatus.Question)
|
|
}
|
|
matched++
|
|
if limit > 0 && matched >= limit {
|
|
break
|
|
}
|
|
}
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdPrompt(options core.Options) core.Result {
|
|
repo := options.String("_arg")
|
|
if repo == "" {
|
|
core.Print(nil, "usage: core-agent prompt <repo> --task=\"...\" [--plan-template=bug-fix] [--variables='{\"key\":\"value\"}']")
|
|
return core.Result{Value: core.E("agentic.cmdPrompt", "repo is required", nil), OK: false}
|
|
}
|
|
|
|
prepInput := prepInputFromCommandOptions(options)
|
|
prepInput.Repo = repo
|
|
if prepInput.Org == "" {
|
|
prepInput.Org = "core"
|
|
}
|
|
if prepInput.Task == "" {
|
|
prepInput.Task = "Review and report findings"
|
|
}
|
|
|
|
repoPath := core.JoinPath(HomeDir(), "Code", prepInput.Org, prepInput.Repo)
|
|
|
|
prompt, memories, consumers := s.BuildPrompt(context.Background(), prepInput, "dev", repoPath)
|
|
core.Print(nil, "memories: %d", memories)
|
|
core.Print(nil, "consumers: %d", consumers)
|
|
core.Print(nil, "")
|
|
core.Print(nil, "%s", prompt)
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func prepInputFromCommandOptions(options core.Options) PrepInput {
|
|
commandOptions := core.NewOptions(options.Items()...)
|
|
if commandOptions.String("repo") == "" {
|
|
if repo := optionStringValue(options, "_arg"); repo != "" {
|
|
commandOptions.Set("repo", repo)
|
|
}
|
|
}
|
|
return prepInputFromOptions(commandOptions)
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdPromptVersion(options core.Options) core.Result {
|
|
workspace := optionStringValue(options, "workspace", "_arg")
|
|
if workspace == "" {
|
|
core.Print(nil, "usage: core-agent prompt version <workspace>")
|
|
return core.Result{Value: core.E("agentic.cmdPromptVersion", "workspace is required", nil), OK: false}
|
|
}
|
|
|
|
result := s.handlePromptVersion(s.commandContext(), core.NewOptions(
|
|
core.Option{Key: "workspace", Value: workspace},
|
|
))
|
|
if !result.OK {
|
|
err := commandResultError("agentic.cmdPromptVersion", result)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
output, ok := result.Value.(PromptVersionOutput)
|
|
if !ok {
|
|
err := core.E("agentic.cmdPromptVersion", "invalid prompt version output", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
core.Print(nil, "workspace: %s", output.Workspace)
|
|
core.Print(nil, "hash: %s", output.Snapshot.Hash)
|
|
if output.Snapshot.CreatedAt != "" {
|
|
core.Print(nil, "created: %s", output.Snapshot.CreatedAt)
|
|
}
|
|
core.Print(nil, "chars: %d", len(output.Snapshot.Content))
|
|
return core.Result{Value: output, OK: true}
|
|
}
|
|
|
|
func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result {
|
|
sourcePath := optionStringValue(options, "source", "input", "file")
|
|
templateName := options.String("_arg")
|
|
if templateName == "" {
|
|
templateName = "default"
|
|
}
|
|
target := options.String("target")
|
|
|
|
if sourcePath == "" && fs.Exists(templateName) && fs.IsFile(templateName) {
|
|
sourcePath = templateName
|
|
templateName = ""
|
|
}
|
|
|
|
if sourcePath != "" {
|
|
readResult := fs.Read(sourcePath)
|
|
if !readResult.OK {
|
|
err, _ := readResult.Value.(error)
|
|
if err == nil {
|
|
err = core.E("agentic.cmdExtract", core.Concat("read agent output ", sourcePath), nil)
|
|
}
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
extracted := extractAgentOutputContent(readResult.Value.(string))
|
|
if extracted == "" {
|
|
err := core.E("agentic.cmdExtract", "agent output did not contain extractable content", nil)
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
|
|
if target != "" {
|
|
if writeResult := fs.WriteMode(target, extracted, 0644); !writeResult.OK {
|
|
err, _ := writeResult.Value.(error)
|
|
if err == nil {
|
|
err = core.E("agentic.cmdExtract", core.Concat("write extracted output ", target), nil)
|
|
}
|
|
core.Print(nil, "error: %v", err)
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
core.Print(nil, "written: %s", target)
|
|
} else {
|
|
core.Print(nil, "%s", extracted)
|
|
}
|
|
|
|
return core.Result{Value: extracted, OK: true}
|
|
}
|
|
|
|
if target == "" {
|
|
target = core.JoinPath(WorkspaceRoot(), "test-extract")
|
|
}
|
|
|
|
workspaceData := &lib.WorkspaceData{
|
|
Repo: "test-repo",
|
|
Branch: "dev",
|
|
Task: "test extraction",
|
|
Agent: "codex",
|
|
}
|
|
|
|
core.Print(nil, "extracting template %q to %s", templateName, target)
|
|
if result := lib.ExtractWorkspace(templateName, target, workspaceData); !result.OK {
|
|
if err, ok := result.Value.(error); ok {
|
|
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", templateName), err), OK: false}
|
|
}
|
|
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", templateName), nil), OK: false}
|
|
}
|
|
|
|
filesystem := s.Core().Fs()
|
|
paths := core.PathGlob(core.JoinPath(target, "*"))
|
|
for _, p := range paths {
|
|
name := core.PathBase(p)
|
|
marker := " "
|
|
if filesystem.IsDir(p) {
|
|
marker = "/"
|
|
}
|
|
core.Print(nil, " %s%s", name, marker)
|
|
}
|
|
|
|
core.Print(nil, "done")
|
|
return core.Result{OK: true}
|
|
}
|
|
|
|
func extractAgentOutputContent(content string) string {
|
|
trimmed := core.Trim(content)
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
|
|
if core.HasPrefix(trimmed, "{") || core.HasPrefix(trimmed, "[") {
|
|
return trimmed
|
|
}
|
|
|
|
blocks := core.Split(content, "```")
|
|
for index := 1; index < len(blocks); index += 2 {
|
|
block := core.Trim(blocks[index])
|
|
if block == "" {
|
|
continue
|
|
}
|
|
|
|
lines := core.SplitN(block, "\n", 2)
|
|
if len(lines) == 2 {
|
|
language := core.Trim(lines[0])
|
|
body := core.Trim(lines[1])
|
|
if language != "" && !core.Contains(language, " ") && body != "" {
|
|
block = body
|
|
}
|
|
}
|
|
|
|
block = core.Trim(block)
|
|
if block != "" {
|
|
return block
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// parseIntString("issue-42") // 42
|
|
func parseIntString(s string) int {
|
|
n := 0
|
|
for _, ch := range s {
|
|
if ch >= '0' && ch <= '9' {
|
|
n = n*10 + int(ch-'0')
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
type FlowRunOutput struct {
|
|
Success bool `json:"success"`
|
|
Source string `json:"source,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Steps int `json:"steps,omitempty"`
|
|
ResolvedSteps int `json:"resolved_steps,omitempty"`
|
|
Parsed bool `json:"parsed,omitempty"`
|
|
}
|
|
|
|
type flowDefinition struct {
|
|
Name string `yaml:"name"`
|
|
Description string `yaml:"description"`
|
|
Steps []flowDefinitionStep `yaml:"steps"`
|
|
}
|
|
|
|
type flowDefinitionStep struct {
|
|
Name string `yaml:"name"`
|
|
Run string `yaml:"run"`
|
|
Flow string `yaml:"flow"`
|
|
Agent string `yaml:"agent"`
|
|
Prompt string `yaml:"prompt"`
|
|
Template string `yaml:"template"`
|
|
Timeout string `yaml:"timeout"`
|
|
When string `yaml:"when"`
|
|
Output string `yaml:"output"`
|
|
Gate string `yaml:"gate"`
|
|
Parallel []flowDefinitionStep `yaml:"parallel"`
|
|
}
|
|
|
|
type flowRunDocument struct {
|
|
Source string
|
|
Content string
|
|
Parsed bool
|
|
Definition flowDefinition
|
|
}
|
|
|
|
func readFlowDocument(path string, variables map[string]string) core.Result {
|
|
if readResult := fs.Read(path); readResult.OK {
|
|
content := applyTemplateVariables(readResult.Value.(string), variables)
|
|
definition, err := parseFlowDefinition(content)
|
|
if err != nil {
|
|
if flowInputLooksYaml(path) {
|
|
return core.Result{Value: err, OK: false}
|
|
}
|
|
return core.Result{Value: flowRunDocument{
|
|
Source: path,
|
|
Content: content,
|
|
Parsed: false,
|
|
}, OK: true}
|
|
}
|
|
return core.Result{Value: flowRunDocument{
|
|
Source: path,
|
|
Content: content,
|
|
Parsed: true,
|
|
Definition: definition,
|
|
}, OK: true}
|
|
}
|
|
|
|
flowResult := lib.Flow(flowSlugFromPath(path))
|
|
if !flowResult.OK {
|
|
if err, ok := flowResult.Value.(error); ok {
|
|
return core.Result{Value: core.E("agentic.cmdRunFlow", core.Concat("flow not found: ", path), err), OK: false}
|
|
}
|
|
return core.Result{Value: core.E("agentic.cmdRunFlow", core.Concat("flow not found: ", path), nil), OK: false}
|
|
}
|
|
|
|
content := applyTemplateVariables(flowResult.Value.(string), variables)
|
|
definition, err := parseFlowDefinition(content)
|
|
if err != nil {
|
|
return core.Result{Value: flowRunDocument{
|
|
Source: core.Concat("embedded:", flowSlugFromPath(path)),
|
|
Content: content,
|
|
Parsed: false,
|
|
}, OK: true}
|
|
}
|
|
return core.Result{Value: flowRunDocument{
|
|
Source: core.Concat("embedded:", flowSlugFromPath(path)),
|
|
Content: content,
|
|
Parsed: true,
|
|
Definition: definition,
|
|
}, OK: true}
|
|
}
|
|
|
|
func parseFlowDefinition(content string) (flowDefinition, error) {
|
|
var definition flowDefinition
|
|
if err := yaml.Unmarshal([]byte(content), &definition); err != nil {
|
|
return flowDefinition{}, core.E("agentic.parseFlowDefinition", "invalid flow definition", err)
|
|
}
|
|
if definition.Name == "" || len(definition.Steps) == 0 {
|
|
return flowDefinition{}, core.E("agentic.parseFlowDefinition", "invalid flow definition", nil)
|
|
}
|
|
return definition, nil
|
|
}
|
|
|
|
func flowInputLooksYaml(path string) bool {
|
|
return core.HasSuffix(path, ".yaml") || core.HasSuffix(path, ".yml")
|
|
}
|
|
|
|
func flowSlugFromPath(path string) string {
|
|
slug := core.PathBase(path)
|
|
for _, suffix := range []string{".yaml", ".yml", ".md"} {
|
|
slug = core.TrimSuffix(slug, suffix)
|
|
}
|
|
return slug
|
|
}
|
|
|
|
func flowStepSummary(step flowDefinitionStep) string {
|
|
label := core.Trim(step.Name)
|
|
if label == "" {
|
|
label = core.Trim(step.Flow)
|
|
}
|
|
if label == "" {
|
|
label = core.Trim(step.Agent)
|
|
}
|
|
if label == "" {
|
|
label = core.Trim(step.Run)
|
|
}
|
|
if label == "" {
|
|
label = "step"
|
|
}
|
|
|
|
switch {
|
|
case step.Flow != "":
|
|
return core.Concat(label, ": flow ", step.Flow)
|
|
case step.Agent != "":
|
|
return core.Concat(label, ": agent ", step.Agent)
|
|
case step.Run != "":
|
|
return core.Concat(label, ": run ", step.Run)
|
|
case step.Gate != "":
|
|
return core.Concat(label, ": gate ", step.Gate)
|
|
default:
|
|
return label
|
|
}
|
|
}
|
|
|
|
func (s *PrepSubsystem) printFlowSteps(document flowRunDocument, indent string, variables map[string]string, visited map[string]bool) int {
|
|
total := 0
|
|
for index, step := range document.Definition.Steps {
|
|
core.Print(nil, "%s%d. %s", indent, index+1, flowStepSummary(step))
|
|
total++
|
|
|
|
if step.Flow != "" {
|
|
resolved := s.resolveFlowReference(document.Source, step.Flow, variables)
|
|
if !resolved.OK {
|
|
continue
|
|
}
|
|
|
|
nested, ok := resolved.Value.(flowRunDocument)
|
|
if !ok || nested.Source == "" {
|
|
continue
|
|
}
|
|
if visited[nested.Source] {
|
|
core.Print(nil, "%s cycle: %s", indent, nested.Source)
|
|
continue
|
|
}
|
|
|
|
core.Print(nil, "%s resolved: %s", indent, nested.Source)
|
|
visited[nested.Source] = true
|
|
total += s.printFlowSteps(nested, core.Concat(indent, " "), variables, visited)
|
|
delete(visited, nested.Source)
|
|
}
|
|
|
|
if len(step.Parallel) > 0 {
|
|
core.Print(nil, "%s parallel:", indent)
|
|
for parallelIndex, parallelStep := range step.Parallel {
|
|
core.Print(nil, "%s %d. %s", indent, parallelIndex+1, flowStepSummary(parallelStep))
|
|
}
|
|
}
|
|
}
|
|
|
|
return total
|
|
}
|
|
|
|
func (s *PrepSubsystem) resolveFlowReference(baseSource, reference string, variables map[string]string) core.Result {
|
|
trimmedReference := core.Trim(reference)
|
|
if trimmedReference == "" {
|
|
return core.Result{Value: core.E("agentic.resolveFlowReference", "flow reference is required", nil), OK: false}
|
|
}
|
|
|
|
candidates := []string{trimmedReference}
|
|
|
|
if root := flowRootPath(baseSource); root != "" {
|
|
candidate := core.JoinPath(root, trimmedReference)
|
|
if candidate != trimmedReference {
|
|
candidates = append(candidates, candidate)
|
|
}
|
|
}
|
|
|
|
repoCandidate := core.JoinPath("pkg", "lib", "flow", trimmedReference)
|
|
if repoCandidate != trimmedReference {
|
|
candidates = append(candidates, repoCandidate)
|
|
}
|
|
|
|
for _, candidate := range candidates {
|
|
result := readFlowDocument(candidate, variables)
|
|
if result.OK {
|
|
return result
|
|
}
|
|
|
|
err, ok := result.Value.(error)
|
|
if !ok || !core.Contains(err.Error(), "flow not found:") {
|
|
return result
|
|
}
|
|
}
|
|
|
|
return core.Result{Value: core.E("agentic.resolveFlowReference", core.Concat("flow not found: ", trimmedReference), nil), OK: false}
|
|
}
|
|
|
|
func flowRootPath(source string) string {
|
|
trimmed := core.Trim(core.Replace(source, "\\", "/"))
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
|
|
segments := core.Split(trimmed, "/")
|
|
for index := 0; index+2 < len(segments); index++ {
|
|
if segments[index] == "pkg" && segments[index+1] == "lib" && segments[index+2] == "flow" {
|
|
return core.JoinPath(segments[:index+3]...)
|
|
}
|
|
}
|
|
|
|
dir := core.PathDir(trimmed)
|
|
if dir != "" {
|
|
return dir
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
type brainListOutput struct {
|
|
Count int `json:"count"`
|
|
Memories []brainListOutputEntry `json:"memories"`
|
|
}
|
|
|
|
type brainListOutputEntry struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Content string `json:"content"`
|
|
Project string `json:"project"`
|
|
AgentID string `json:"agent_id"`
|
|
Confidence float64 `json:"confidence"`
|
|
SupersedesCount int `json:"supersedes_count,omitempty"`
|
|
DeletedAt string `json:"deleted_at,omitempty"`
|
|
Tags []string `json:"tags"`
|
|
}
|
|
|
|
func brainListOutputFromPayload(payload map[string]any) brainListOutput {
|
|
output := brainListOutput{}
|
|
switch count := payload["count"].(type) {
|
|
case float64:
|
|
output.Count = int(count)
|
|
case int:
|
|
output.Count = count
|
|
}
|
|
if memories, ok := payload["memories"].([]any); ok {
|
|
for _, item := range memories {
|
|
entryMap, ok := item.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
entry := brainListOutputEntry{
|
|
ID: brainListStringValue(entryMap["id"]),
|
|
Type: brainListStringValue(entryMap["type"]),
|
|
Content: brainListStringValue(entryMap["content"]),
|
|
Project: brainListStringValue(entryMap["project"]),
|
|
AgentID: brainListStringValue(entryMap["agent_id"]),
|
|
}
|
|
switch confidence := entryMap["confidence"].(type) {
|
|
case float64:
|
|
entry.Confidence = confidence
|
|
case int:
|
|
entry.Confidence = float64(confidence)
|
|
}
|
|
if entry.Confidence == 0 {
|
|
switch confidence := entryMap["score"].(type) {
|
|
case float64:
|
|
entry.Confidence = confidence
|
|
case int:
|
|
entry.Confidence = float64(confidence)
|
|
}
|
|
}
|
|
switch supersedesCount := entryMap["supersedes_count"].(type) {
|
|
case float64:
|
|
entry.SupersedesCount = int(supersedesCount)
|
|
case int:
|
|
entry.SupersedesCount = supersedesCount
|
|
}
|
|
entry.DeletedAt = brainListStringValue(entryMap["deleted_at"])
|
|
if tags, ok := entryMap["tags"].([]any); ok {
|
|
for _, tag := range tags {
|
|
entry.Tags = append(entry.Tags, brainListStringValue(tag))
|
|
}
|
|
}
|
|
output.Memories = append(output.Memories, entry)
|
|
}
|
|
}
|
|
if output.Count == 0 {
|
|
output.Count = len(output.Memories)
|
|
}
|
|
return output
|
|
}
|
|
|
|
func brainListStringValue(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(typed)
|
|
}
|
|
return ""
|
|
}
|