fix(ax): align code comments with AX principles
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
877de43257
commit
bd12c0a31a
26 changed files with 239 additions and 537 deletions
|
|
@ -17,14 +17,12 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// --- Dispatch & Workspace ---
|
||||
|
||||
// handleDispatch dispatches a subagent to work on a repo task.
|
||||
// result := c.Action("agentic.dispatch").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.dispatch").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
// core.Option{Key: "task", Value: "Fix tests"},
|
||||
// ))
|
||||
// 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 {
|
||||
input := DispatchInput{
|
||||
Repo: options.String("repo"),
|
||||
|
|
@ -39,12 +37,12 @@ func (s *PrepSubsystem) handleDispatch(ctx context.Context, options core.Options
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// handlePrep prepares a workspace without dispatching an agent.
|
||||
// result := c.Action("agentic.prep").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.prep").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
// core.Option{Key: "issue", Value: 42},
|
||||
// ))
|
||||
// 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 := PrepInput{
|
||||
Repo: options.String("repo"),
|
||||
|
|
@ -58,9 +56,7 @@ func (s *PrepSubsystem) handlePrep(ctx context.Context, options core.Options) co
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// handleStatus lists workspace statuses.
|
||||
//
|
||||
// result := c.Action("agentic.status").Run(ctx, core.NewOptions())
|
||||
// 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"),
|
||||
|
|
@ -74,11 +70,11 @@ func (s *PrepSubsystem) handleStatus(ctx context.Context, options core.Options)
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// handleResume resumes a blocked workspace.
|
||||
// result := c.Action("agentic.resume").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.resume").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
||||
// ))
|
||||
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleResume(ctx context.Context, options core.Options) core.Result {
|
||||
input := ResumeInput{
|
||||
Workspace: options.String("workspace"),
|
||||
|
|
@ -91,9 +87,7 @@ func (s *PrepSubsystem) handleResume(ctx context.Context, options core.Options)
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// handleScan scans forge repos for actionable issues.
|
||||
//
|
||||
// result := c.Action("agentic.scan").Run(ctx, core.NewOptions())
|
||||
// result := c.Action("agentic.scan").Run(ctx, core.NewOptions())
|
||||
func (s *PrepSubsystem) handleScan(ctx context.Context, options core.Options) core.Result {
|
||||
input := ScanInput{
|
||||
Org: options.String("org"),
|
||||
|
|
@ -106,11 +100,11 @@ func (s *PrepSubsystem) handleScan(ctx context.Context, options core.Options) co
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// handleWatch watches a workspace for completion.
|
||||
// result := c.Action("agentic.watch").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.watch").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
||||
// ))
|
||||
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleWatch(ctx context.Context, options core.Options) core.Result {
|
||||
input := WatchInput{
|
||||
PollInterval: options.Int("poll_interval"),
|
||||
|
|
@ -126,58 +120,56 @@ func (s *PrepSubsystem) handleWatch(ctx context.Context, options core.Options) c
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// handlePrompt reads an embedded prompt by slug.
|
||||
// result := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "slug", Value: "coding"},
|
||||
// ))
|
||||
// core.Option{Key: "slug", Value: "coding"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePrompt(_ context.Context, options core.Options) core.Result {
|
||||
return lib.Prompt(options.String("slug"))
|
||||
}
|
||||
|
||||
// handleTask reads an embedded task plan by slug.
|
||||
// result := c.Action("agentic.task").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.task").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "slug", Value: "bug-fix"},
|
||||
// ))
|
||||
// core.Option{Key: "slug", Value: "bug-fix"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleTask(_ context.Context, options core.Options) core.Result {
|
||||
return lib.Task(options.String("slug"))
|
||||
}
|
||||
|
||||
// handleFlow reads an embedded flow by slug.
|
||||
// result := c.Action("agentic.flow").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.flow").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "slug", Value: "go"},
|
||||
// ))
|
||||
// core.Option{Key: "slug", Value: "go"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleFlow(_ context.Context, options core.Options) core.Result {
|
||||
return lib.Flow(options.String("slug"))
|
||||
}
|
||||
|
||||
// handlePersona reads an embedded persona by path.
|
||||
// result := c.Action("agentic.persona").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.persona").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "path", Value: "code/backend-architect"},
|
||||
// ))
|
||||
// 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"))
|
||||
}
|
||||
|
||||
// --- Pipeline ---
|
||||
|
||||
// handleComplete runs the named completion task.
|
||||
// result := c.Action("agentic.complete").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.complete").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "/srv/.core/workspace/core/go-io/task-42"},
|
||||
// ))
|
||||
// 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)
|
||||
}
|
||||
|
||||
// handleQA runs build+test on a completed workspace.
|
||||
// result := c.Action("agentic.qa").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.qa").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
||||
// ))
|
||||
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core.Result {
|
||||
// Feature flag gate — skip QA if disabled
|
||||
if s.ServiceRuntime != nil && !s.Config().Enabled("auto-qa") {
|
||||
|
|
@ -215,11 +207,11 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core
|
|||
return core.Result{Value: passed, OK: passed}
|
||||
}
|
||||
|
||||
// handleAutoPR creates a PR for a completed workspace.
|
||||
// result := c.Action("agentic.auto-pr").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.auto-pr").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
||||
// ))
|
||||
// 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}
|
||||
|
|
@ -246,11 +238,11 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options)
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// handleVerify verifies and auto-merges a PR.
|
||||
// result := c.Action("agentic.verify").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.verify").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
||||
// ))
|
||||
// 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}
|
||||
|
|
@ -285,11 +277,11 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options)
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// handleIngest creates issues from agent findings.
|
||||
// result := c.Action("agentic.ingest").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.ingest").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "/path/to/workspace"},
|
||||
// ))
|
||||
// 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 == "" {
|
||||
|
|
@ -307,11 +299,11 @@ func (s *PrepSubsystem) handlePoke(ctx context.Context, _ core.Options) core.Res
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// handleMirror mirrors agent branches to GitHub.
|
||||
// result := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
// ))
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleMirror(ctx context.Context, options core.Options) core.Result {
|
||||
input := MirrorInput{
|
||||
Repo: options.String("repo"),
|
||||
|
|
@ -323,8 +315,6 @@ func (s *PrepSubsystem) handleMirror(ctx context.Context, options core.Options)
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// --- Forge ---
|
||||
|
||||
// handleIssueGet retrieves a forge issue.
|
||||
//
|
||||
// result := c.Action("agentic.issue.get").Run(ctx, core.NewOptions(
|
||||
|
|
@ -335,21 +325,21 @@ func (s *PrepSubsystem) handleIssueGet(ctx context.Context, options core.Options
|
|||
return s.cmdIssueGet(options)
|
||||
}
|
||||
|
||||
// handleIssueList lists forge issues.
|
||||
// result := c.Action("agentic.issue.list").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.issue.list").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
// ))
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleIssueList(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdIssueList(options)
|
||||
}
|
||||
|
||||
// handleIssueCreate creates a forge issue.
|
||||
// result := c.Action("agentic.issue.create").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.issue.create").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
// core.Option{Key: "title", Value: "Bug report"},
|
||||
// ))
|
||||
// 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(options)
|
||||
}
|
||||
|
|
@ -364,11 +354,11 @@ func (s *PrepSubsystem) handlePRGet(ctx context.Context, options core.Options) c
|
|||
return s.cmdPRGet(options)
|
||||
}
|
||||
|
||||
// handlePRList lists forge PRs.
|
||||
// result := c.Action("agentic.pr.list").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.pr.list").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
// ))
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePRList(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdPRList(options)
|
||||
}
|
||||
|
|
@ -383,13 +373,11 @@ func (s *PrepSubsystem) handlePRMerge(ctx context.Context, options core.Options)
|
|||
return s.cmdPRMerge(options)
|
||||
}
|
||||
|
||||
// --- Review ---
|
||||
|
||||
// handleReviewQueue runs CodeRabbit review on a workspace.
|
||||
// result := c.Action("agentic.review-queue").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.review-queue").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
||||
// ))
|
||||
// core.Option{Key: "workspace", Value: "core/go-io/task-5"},
|
||||
//
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, options core.Options) core.Result {
|
||||
input := ReviewQueueInput{
|
||||
Limit: options.Int("limit"),
|
||||
|
|
@ -403,13 +391,11 @@ func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, options core.Opti
|
|||
return core.Result{Value: out, OK: true}
|
||||
}
|
||||
|
||||
// --- Epic ---
|
||||
|
||||
// handleEpic creates an epic (multi-repo task breakdown).
|
||||
// result := c.Action("agentic.epic").Run(ctx, core.NewOptions(
|
||||
//
|
||||
// result := c.Action("agentic.epic").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "task", Value: "Update all repos to v0.8.0"},
|
||||
// ))
|
||||
// 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 := EpicInput{
|
||||
Repo: options.String("repo"),
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// autoCreatePR pushes the agent's branch and creates a PR on Forge
|
||||
// if the agent made any commits beyond the initial clone.
|
||||
// s.autoCreatePR("/srv/.core/workspace/core/go-io/task-5")
|
||||
func (s *PrepSubsystem) autoCreatePR(workspaceDir string) {
|
||||
result := ReadStatusResult(workspaceDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
|
|
|
|||
|
|
@ -71,7 +71,13 @@ func pullRequestAuthor(pr pullRequestView) string {
|
|||
return pr.User.Login
|
||||
}
|
||||
|
||||
// parseForgeArgs extracts org and repo from options.
|
||||
// org, repo, num := parseForgeArgs(core.NewOptions(
|
||||
//
|
||||
// core.Option{Key: "org", Value: "core"},
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
// core.Option{Key: "number", Value: "42"},
|
||||
//
|
||||
// ))
|
||||
func parseForgeArgs(options core.Options) (org, repo string, num int64) {
|
||||
org = options.String("org")
|
||||
if org == "" {
|
||||
|
|
|
|||
|
|
@ -13,11 +13,7 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// cloneWorkspaceDeps clones Core ecosystem dependencies into the workspace.
|
||||
// After this, the workspace go.work includes ./repo and all ./dep-* dirs,
|
||||
// giving the agent everything needed to build and test.
|
||||
//
|
||||
// s.cloneWorkspaceDeps(ctx, workspaceDir, repoDir, "core")
|
||||
// s.cloneWorkspaceDeps(ctx, workspaceDir, repoDir, "core")
|
||||
func (s *PrepSubsystem) cloneWorkspaceDeps(ctx context.Context, workspaceDir, repoDir, org string) {
|
||||
goModPath := core.JoinPath(repoDir, "go.mod")
|
||||
r := fs.Read(goModPath)
|
||||
|
|
@ -80,11 +76,8 @@ type coreDep struct {
|
|||
dir string // e.g. "core-go" (workspace subdir)
|
||||
}
|
||||
|
||||
// parseCoreDeps extracts direct Core ecosystem dependencies from go.mod content.
|
||||
// Skips indirect deps — only clones what the repo directly imports.
|
||||
//
|
||||
// deps := parseCoreDeps(goMod)
|
||||
// if len(deps) > 0 { core.Println(deps[0].repo) }
|
||||
// deps := parseCoreDeps(goMod)
|
||||
// if len(deps) > 0 { core.Println(deps[0].repo) }
|
||||
func parseCoreDeps(gomod string) []coreDep {
|
||||
var deps []coreDep
|
||||
seen := make(map[string]bool)
|
||||
|
|
|
|||
|
|
@ -226,16 +226,13 @@ func containerCommand(command string, args []string, repoDir, metaDir string) (s
|
|||
return "docker", dockerArgs
|
||||
}
|
||||
|
||||
// --- spawnAgent: decomposed into testable steps ---
|
||||
|
||||
// agentOutputFile returns the log file path for an agent's output.
|
||||
// outputFile := agentOutputFile(workspaceDir, "codex")
|
||||
func agentOutputFile(workspaceDir, agent string) string {
|
||||
agentBase := core.SplitN(agent, ":", 2)[0]
|
||||
return core.JoinPath(WorkspaceMetaDir(workspaceDir), core.Sprintf("agent-%s.log", agentBase))
|
||||
}
|
||||
|
||||
// detectFinalStatus reads workspace state after agent exit to determine outcome.
|
||||
// Returns (status, question) — "completed", "blocked", or "failed".
|
||||
// status, question := detectFinalStatus(repoDir, 0, "completed")
|
||||
func detectFinalStatus(repoDir string, exitCode int, processStatus string) (string, string) {
|
||||
blockedPath := core.JoinPath(repoDir, "BLOCKED.md")
|
||||
if blockedResult := fs.Read(blockedPath); blockedResult.OK && core.Trim(blockedResult.Value.(string)) != "" {
|
||||
|
|
@ -342,8 +339,6 @@ func (s *PrepSubsystem) broadcastComplete(agent, workspaceDir, finalStatus strin
|
|||
}
|
||||
}
|
||||
|
||||
// onAgentComplete handles all post-completion logic for a spawned agent.
|
||||
// Called from the monitoring goroutine after the process exits.
|
||||
func (s *PrepSubsystem) onAgentComplete(agent, workspaceDir, outputFile string, exitCode int, processStatus, output string) {
|
||||
// Save output
|
||||
if output != "" {
|
||||
|
|
@ -384,9 +379,7 @@ func (s *PrepSubsystem) onAgentComplete(agent, workspaceDir, outputFile string,
|
|||
}
|
||||
}
|
||||
|
||||
// spawnAgent launches an agent inside a Docker container.
|
||||
// The repo/ directory is mounted at /workspace, agent runs sandboxed.
|
||||
// Output is captured and written to .meta/agent-{agent}.log on completion.
|
||||
// pid, processID, outputFile, err := s.spawnAgent(agent, prompt, workspaceDir)
|
||||
func (s *PrepSubsystem) spawnAgent(agent, prompt, workspaceDir string) (int, string, string, error) {
|
||||
command, args, err := agentCommand(agent, prompt)
|
||||
if err != nil {
|
||||
|
|
@ -472,8 +465,7 @@ func (m *agentCompletionMonitor) run(_ context.Context, _ core.Options) core.Res
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// runQA runs build + test checks on the repo after agent completion.
|
||||
// Returns true if QA passes, false if build or tests fail.
|
||||
// passed := s.runQA(workspaceDir)
|
||||
func (s *PrepSubsystem) runQA(workspaceDir string) bool {
|
||||
ctx := context.Background()
|
||||
repoDir := WorkspaceRepoDir(workspaceDir)
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// --- agentic_create_epic ---
|
||||
|
||||
// EpicInput is the input for agentic_create_epic.
|
||||
//
|
||||
// input := agentic.EpicInput{Repo: "go-scm", Title: "Port agentic plans", Tasks: []string{"Read PHP flow", "Implement Go MCP tools"}}
|
||||
// input := agentic.EpicInput{Repo: "go-scm", Title: "Port agentic plans", Tasks: []string{"Read PHP flow", "Implement Go MCP tools"}}
|
||||
type EpicInput struct {
|
||||
Repo string `json:"repo"` // Target repo (e.g. "go-scm")
|
||||
Org string `json:"org,omitempty"` // Forge org (default "core")
|
||||
|
|
@ -26,9 +22,7 @@ type EpicInput struct {
|
|||
Template string `json:"template,omitempty"` // Prompt template for dispatch (default "coding")
|
||||
}
|
||||
|
||||
// EpicOutput is the output for agentic_create_epic.
|
||||
//
|
||||
// out := agentic.EpicOutput{Success: true, EpicNumber: 42, EpicURL: "https://forge.example/core/go-scm/issues/42"}
|
||||
// out := agentic.EpicOutput{Success: true, EpicNumber: 42, EpicURL: "https://forge.example/core/go-scm/issues/42"}
|
||||
type EpicOutput struct {
|
||||
Success bool `json:"success"`
|
||||
EpicNumber int `json:"epic_number"`
|
||||
|
|
@ -37,9 +31,7 @@ type EpicOutput struct {
|
|||
Dispatched int `json:"dispatched,omitempty"`
|
||||
}
|
||||
|
||||
// ChildRef references a child issue.
|
||||
//
|
||||
// child := agentic.ChildRef{Number: 43, Title: "Implement plan list", URL: "https://forge.example/core/go-scm/issues/43"}
|
||||
// child := agentic.ChildRef{Number: 43, Title: "Implement plan list", URL: "https://forge.example/core/go-scm/issues/43"}
|
||||
type ChildRef struct {
|
||||
Number int `json:"number"`
|
||||
Title string `json:"title"`
|
||||
|
|
@ -144,7 +136,7 @@ func (s *PrepSubsystem) createEpic(ctx context.Context, callRequest *mcp.CallToo
|
|||
return nil, out, nil
|
||||
}
|
||||
|
||||
// createIssue creates a single issue on Forge and returns its reference.
|
||||
// child, err := s.createIssue(ctx, "core", "go-scm", "Port agentic plans", "", nil)
|
||||
func (s *PrepSubsystem) createIssue(ctx context.Context, org, repo, title, body string, labelIDs []int64) (ChildRef, error) {
|
||||
payload := map[string]any{
|
||||
"title": title,
|
||||
|
|
@ -176,7 +168,7 @@ func (s *PrepSubsystem) createIssue(ctx context.Context, org, repo, title, body
|
|||
}, nil
|
||||
}
|
||||
|
||||
// resolveLabelIDs looks up label IDs by name, creating labels that don't exist.
|
||||
// labelIDs := s.resolveLabelIDs(ctx, "core", "go-scm", []string{"agentic", "epic"})
|
||||
func (s *PrepSubsystem) resolveLabelIDs(ctx context.Context, org, repo string, names []string) []int64 {
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
|
|
@ -216,7 +208,7 @@ func (s *PrepSubsystem) resolveLabelIDs(ctx context.Context, org, repo string, n
|
|||
return ids
|
||||
}
|
||||
|
||||
// createLabel creates a label on Forge and returns its ID.
|
||||
// id := s.createLabel(ctx, "core", "go-scm", "agentic")
|
||||
func (s *PrepSubsystem) createLabel(ctx context.Context, org, repo, name string) int64 {
|
||||
colours := map[string]string{
|
||||
"agentic": "#7c3aed",
|
||||
|
|
|
|||
|
|
@ -9,20 +9,14 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// --- agentic_mirror tool ---
|
||||
|
||||
// MirrorInput is the input for agentic_mirror.
|
||||
//
|
||||
// input := agentic.MirrorInput{Repo: "go-io", DryRun: true, MaxFiles: 50}
|
||||
// input := agentic.MirrorInput{Repo: "go-io", DryRun: true, MaxFiles: 50}
|
||||
type MirrorInput struct {
|
||||
Repo string `json:"repo,omitempty"` // Specific repo, or empty for all
|
||||
DryRun bool `json:"dry_run,omitempty"` // Preview without pushing
|
||||
MaxFiles int `json:"max_files,omitempty"` // Max files per PR (default 50, CodeRabbit limit)
|
||||
}
|
||||
|
||||
// MirrorOutput is the output for agentic_mirror.
|
||||
//
|
||||
// out := agentic.MirrorOutput{Success: true, Count: 1, Synced: []agentic.MirrorSync{{Repo: "go-io"}}}
|
||||
// out := agentic.MirrorOutput{Success: true, Count: 1, Synced: []agentic.MirrorSync{{Repo: "go-io"}}}
|
||||
type MirrorOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Synced []MirrorSync `json:"synced"`
|
||||
|
|
@ -30,9 +24,7 @@ type MirrorOutput struct {
|
|||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// MirrorSync records one repo sync.
|
||||
//
|
||||
// sync := agentic.MirrorSync{Repo: "go-io", CommitsAhead: 3, FilesChanged: 12}
|
||||
// sync := agentic.MirrorSync{Repo: "go-io", CommitsAhead: 3, FilesChanged: 12}
|
||||
type MirrorSync struct {
|
||||
Repo string `json:"repo"`
|
||||
CommitsAhead int `json:"commits_ahead"`
|
||||
|
|
@ -146,7 +138,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
}, nil
|
||||
}
|
||||
|
||||
// createGitHubPR creates a PR from dev → main using the gh CLI.
|
||||
// url, err := s.createGitHubPR(ctx, repoDir, "go-io", 3, 12)
|
||||
func (s *PrepSubsystem) createGitHubPR(ctx context.Context, repoDir, repo string, commits, files int) (string, error) {
|
||||
ghRepo := core.Sprintf("%s/%s", GitHubOrg(), repo)
|
||||
process := s.Core().Process()
|
||||
|
|
@ -183,17 +175,14 @@ func (s *PrepSubsystem) createGitHubPR(ctx context.Context, repoDir, repo string
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// ensureDevBranch creates the dev branch on GitHub if it doesn't exist.
|
||||
func (s *PrepSubsystem) ensureDevBranch(repoDir string) {
|
||||
s.Core().Process().RunIn(context.Background(), repoDir, "git", "push", "github", "HEAD:refs/heads/dev")
|
||||
}
|
||||
|
||||
// hasRemote checks if a git remote exists.
|
||||
func (s *PrepSubsystem) hasRemote(repoDir, name string) bool {
|
||||
return s.Core().Process().RunIn(context.Background(), repoDir, "git", "remote", "get-url", name).OK
|
||||
}
|
||||
|
||||
// commitsAhead returns how many commits HEAD is ahead of the ref.
|
||||
func (s *PrepSubsystem) commitsAhead(repoDir, base, head string) int {
|
||||
r := s.Core().Process().RunIn(context.Background(), repoDir, "git", "rev-list", core.Concat(base, "..", head), "--count")
|
||||
if !r.OK {
|
||||
|
|
@ -203,7 +192,6 @@ func (s *PrepSubsystem) commitsAhead(repoDir, base, head string) int {
|
|||
return parseInt(out)
|
||||
}
|
||||
|
||||
// filesChanged returns the number of files changed between two refs.
|
||||
func (s *PrepSubsystem) filesChanged(repoDir, base, head string) int {
|
||||
r := s.Core().Process().RunIn(context.Background(), repoDir, "git", "diff", "--name-only", core.Concat(base, "..", head))
|
||||
if !r.OK {
|
||||
|
|
@ -216,7 +204,6 @@ func (s *PrepSubsystem) filesChanged(repoDir, base, head string) int {
|
|||
return len(core.Split(out, "\n"))
|
||||
}
|
||||
|
||||
// listLocalRepos returns repo names that exist as directories in basePath.
|
||||
func (s *PrepSubsystem) listLocalRepos(basePath string) []string {
|
||||
paths := core.PathGlob(core.JoinPath(basePath, "*"))
|
||||
var repos []string
|
||||
|
|
@ -233,7 +220,6 @@ func (s *PrepSubsystem) listLocalRepos(basePath string) []string {
|
|||
return repos
|
||||
}
|
||||
|
||||
// extractJSONField extracts a simple string field from JSON array output.
|
||||
func extractJSONField(jsonStr, field string) string {
|
||||
if jsonStr == "" || field == "" {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -11,44 +11,30 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// fs provides unrestricted filesystem access (root "/" = no sandbox).
|
||||
//
|
||||
// r := fs.Read("/etc/hostname")
|
||||
// if r.OK { core.Print(nil, "%s", r.Value.(string)) }
|
||||
// r := fs.Read("/etc/hostname")
|
||||
// if r.OK { core.Print(nil, "%s", r.Value.(string)) }
|
||||
var fs = (&core.Fs{}).NewUnrestricted()
|
||||
|
||||
// LocalFs returns an unrestricted filesystem instance for use by other packages.
|
||||
//
|
||||
// f := agentic.LocalFs()
|
||||
// r := f.Read("/tmp/agent-status.json")
|
||||
// f := agentic.LocalFs()
|
||||
// r := f.Read("/tmp/agent-status.json")
|
||||
func LocalFs() *core.Fs { return fs }
|
||||
|
||||
// WorkspaceRoot returns the root directory for agent workspaces.
|
||||
// Checks CORE_WORKSPACE env var first, falls back to HomeDir()/Code/.core/workspace.
|
||||
//
|
||||
// workspaceDir := core.JoinPath(agentic.WorkspaceRoot(), "core", "go-io", "task-42")
|
||||
// workspaceDir := core.JoinPath(agentic.WorkspaceRoot(), "core", "go-io", "task-42")
|
||||
func WorkspaceRoot() string {
|
||||
return core.JoinPath(CoreRoot(), "workspace")
|
||||
}
|
||||
|
||||
// WorkspaceStatusPaths returns all workspace status files across supported layouts.
|
||||
//
|
||||
// paths := agentic.WorkspaceStatusPaths()
|
||||
// paths := agentic.WorkspaceStatusPaths()
|
||||
func WorkspaceStatusPaths() []string {
|
||||
return workspaceStatusPaths(WorkspaceRoot())
|
||||
}
|
||||
|
||||
// WorkspaceStatusPath returns the status file for a workspace directory.
|
||||
//
|
||||
// path := agentic.WorkspaceStatusPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
// path := agentic.WorkspaceStatusPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceStatusPath(workspaceDir string) string {
|
||||
return core.JoinPath(workspaceDir, "status.json")
|
||||
}
|
||||
|
||||
// WorkspaceName extracts the unique workspace name from a full path.
|
||||
// Given /Users/snider/Code/.core/workspace/core/go-io/dev → core/go-io/dev
|
||||
//
|
||||
// name := agentic.WorkspaceName("/Users/snider/Code/.core/workspace/core/go-io/dev")
|
||||
// name := agentic.WorkspaceName("/Users/snider/Code/.core/workspace/core/go-io/dev")
|
||||
func WorkspaceName(workspaceDir string) string {
|
||||
root := WorkspaceRoot()
|
||||
name := core.TrimPrefix(workspaceDir, root)
|
||||
|
|
@ -59,10 +45,7 @@ func WorkspaceName(workspaceDir string) string {
|
|||
return name
|
||||
}
|
||||
|
||||
// CoreRoot returns the root directory for core ecosystem files.
|
||||
// Checks CORE_WORKSPACE env var first, falls back to HomeDir()/Code/.core.
|
||||
//
|
||||
// root := agentic.CoreRoot()
|
||||
// root := agentic.CoreRoot()
|
||||
func CoreRoot() string {
|
||||
if root := core.Env("CORE_WORKSPACE"); root != "" {
|
||||
return root
|
||||
|
|
@ -70,9 +53,7 @@ func CoreRoot() string {
|
|||
return core.JoinPath(HomeDir(), "Code", ".core")
|
||||
}
|
||||
|
||||
// HomeDir returns the user home directory used by agentic path helpers.
|
||||
//
|
||||
// home := agentic.HomeDir()
|
||||
// home := agentic.HomeDir()
|
||||
func HomeDir() string {
|
||||
if home := core.Env("CORE_HOME"); home != "" {
|
||||
return home
|
||||
|
|
@ -127,9 +108,7 @@ func workspaceStatusPaths(workspaceRoot string) []string {
|
|||
return paths
|
||||
}
|
||||
|
||||
// WorkspaceRepoDir returns the checked-out repo directory for a workspace.
|
||||
//
|
||||
// repoDir := agentic.WorkspaceRepoDir("/srv/.core/workspace/core/go-io/task-5")
|
||||
// repoDir := agentic.WorkspaceRepoDir("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceRepoDir(workspaceDir string) string {
|
||||
return core.JoinPath(workspaceDir, "repo")
|
||||
}
|
||||
|
|
@ -138,9 +117,7 @@ func workspaceRepoDir(workspaceDir string) string {
|
|||
return WorkspaceRepoDir(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceMetaDir returns the metadata directory for a workspace.
|
||||
//
|
||||
// metaDir := agentic.WorkspaceMetaDir("/srv/.core/workspace/core/go-io/task-5")
|
||||
// metaDir := agentic.WorkspaceMetaDir("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceMetaDir(workspaceDir string) string {
|
||||
return core.JoinPath(workspaceDir, ".meta")
|
||||
}
|
||||
|
|
@ -149,9 +126,7 @@ func workspaceMetaDir(workspaceDir string) string {
|
|||
return WorkspaceMetaDir(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceBlockedPath returns the BLOCKED.md path for a workspace.
|
||||
//
|
||||
// blocked := agentic.WorkspaceBlockedPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
// blocked := agentic.WorkspaceBlockedPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceBlockedPath(workspaceDir string) string {
|
||||
return core.JoinPath(WorkspaceRepoDir(workspaceDir), "BLOCKED.md")
|
||||
}
|
||||
|
|
@ -160,9 +135,7 @@ func workspaceBlockedPath(workspaceDir string) string {
|
|||
return WorkspaceBlockedPath(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceAnswerPath returns the ANSWER.md path for a workspace.
|
||||
//
|
||||
// answer := agentic.WorkspaceAnswerPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
// answer := agentic.WorkspaceAnswerPath("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceAnswerPath(workspaceDir string) string {
|
||||
return core.JoinPath(WorkspaceRepoDir(workspaceDir), "ANSWER.md")
|
||||
}
|
||||
|
|
@ -171,9 +144,7 @@ func workspaceAnswerPath(workspaceDir string) string {
|
|||
return WorkspaceAnswerPath(workspaceDir)
|
||||
}
|
||||
|
||||
// WorkspaceLogFiles returns captured agent log files for a workspace.
|
||||
//
|
||||
// logs := agentic.WorkspaceLogFiles("/srv/.core/workspace/core/go-io/task-5")
|
||||
// logs := agentic.WorkspaceLogFiles("/srv/.core/workspace/core/go-io/task-5")
|
||||
func WorkspaceLogFiles(workspaceDir string) []string {
|
||||
return core.PathGlob(core.JoinPath(WorkspaceMetaDir(workspaceDir), "agent-*.log"))
|
||||
}
|
||||
|
|
@ -182,17 +153,12 @@ func workspaceLogFiles(workspaceDir string) []string {
|
|||
return WorkspaceLogFiles(workspaceDir)
|
||||
}
|
||||
|
||||
// PlansRoot returns the root directory for agent plans.
|
||||
//
|
||||
// plansDir := agentic.PlansRoot()
|
||||
// plansDir := agentic.PlansRoot()
|
||||
func PlansRoot() string {
|
||||
return core.JoinPath(CoreRoot(), "plans")
|
||||
}
|
||||
|
||||
// AgentName returns the name of this agent based on hostname.
|
||||
// Checks AGENT_NAME env var first.
|
||||
//
|
||||
// name := agentic.AgentName() // "cladius" on Snider's Mac, "charon" elsewhere
|
||||
// name := agentic.AgentName() // "cladius" on Snider's Mac, "charon" elsewhere
|
||||
func AgentName() string {
|
||||
if name := core.Env("AGENT_NAME"); name != "" {
|
||||
return name
|
||||
|
|
@ -204,9 +170,7 @@ func AgentName() string {
|
|||
return "charon"
|
||||
}
|
||||
|
||||
// DefaultBranch detects the default branch of a repo (main, master, etc.).
|
||||
//
|
||||
// base := s.DefaultBranch("./src")
|
||||
// base := s.DefaultBranch("/srv/Code/core/go-io/repo")
|
||||
func (s *PrepSubsystem) DefaultBranch(repoDir string) string {
|
||||
ctx := context.Background()
|
||||
process := s.Core().Process()
|
||||
|
|
@ -225,9 +189,7 @@ func (s *PrepSubsystem) DefaultBranch(repoDir string) string {
|
|||
return "main"
|
||||
}
|
||||
|
||||
// GitHubOrg returns the GitHub org for mirror operations.
|
||||
//
|
||||
// org := agentic.GitHubOrg() // "dAppCore"
|
||||
// org := agentic.GitHubOrg() // "dAppCore"
|
||||
func GitHubOrg() string {
|
||||
if org := core.Env("GITHUB_ORG"); org != "" {
|
||||
return org
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ import (
|
|||
"dappco.re/go/core/process"
|
||||
)
|
||||
|
||||
// ProcessAlive checks whether a managed process is still running.
|
||||
//
|
||||
// alive := agentic.ProcessAlive(c, proc.ID, proc.Info().PID)
|
||||
// alive := agentic.ProcessAlive(c, "", 12345) // legacy PID fallback
|
||||
// alive := agentic.ProcessAlive(c, proc.ID, proc.Info().PID)
|
||||
// alive := agentic.ProcessAlive(c, "", 12345) // legacy PID fallback
|
||||
func ProcessAlive(c *core.Core, processID string, pid int) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -40,11 +40,7 @@ type Phase struct {
|
|||
Notes string `json:"notes,omitempty"`
|
||||
}
|
||||
|
||||
// --- Input/Output types ---
|
||||
|
||||
// PlanCreateInput is the input for agentic_plan_create.
|
||||
//
|
||||
// input := agentic.PlanCreateInput{Title: "Migrate pkg/agentic", Objective: "Use Core primitives everywhere"}
|
||||
// input := agentic.PlanCreateInput{Title: "Migrate pkg/agentic", Objective: "Use Core primitives everywhere"}
|
||||
type PlanCreateInput struct {
|
||||
Title string `json:"title"`
|
||||
Objective string `json:"objective"`
|
||||
|
|
@ -54,33 +50,25 @@ type PlanCreateInput struct {
|
|||
Notes string `json:"notes,omitempty"`
|
||||
}
|
||||
|
||||
// PlanCreateOutput is the output for agentic_plan_create.
|
||||
//
|
||||
// out := agentic.PlanCreateOutput{Success: true, ID: "id-1-a3f2b1"}
|
||||
// out := agentic.PlanCreateOutput{Success: true, ID: "id-1-a3f2b1"}
|
||||
type PlanCreateOutput struct {
|
||||
Success bool `json:"success"`
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// PlanReadInput is the input for agentic_plan_read.
|
||||
//
|
||||
// input := agentic.PlanReadInput{ID: "id-1-a3f2b1"}
|
||||
// input := agentic.PlanReadInput{ID: "id-1-a3f2b1"}
|
||||
type PlanReadInput struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// PlanReadOutput is the output for agentic_plan_read.
|
||||
//
|
||||
// out := agentic.PlanReadOutput{Success: true, Plan: agentic.Plan{ID: "id-1-a3f2b1"}}
|
||||
// out := agentic.PlanReadOutput{Success: true, Plan: agentic.Plan{ID: "id-1-a3f2b1"}}
|
||||
type PlanReadOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Plan Plan `json:"plan"`
|
||||
}
|
||||
|
||||
// PlanUpdateInput is the input for agentic_plan_update.
|
||||
//
|
||||
// input := agentic.PlanUpdateInput{ID: "id-1-a3f2b1", Status: "verified"}
|
||||
// input := agentic.PlanUpdateInput{ID: "id-1-a3f2b1", Status: "verified"}
|
||||
type PlanUpdateInput struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status,omitempty"`
|
||||
|
|
@ -91,48 +79,36 @@ type PlanUpdateInput struct {
|
|||
Agent string `json:"agent,omitempty"`
|
||||
}
|
||||
|
||||
// PlanUpdateOutput is the output for agentic_plan_update.
|
||||
//
|
||||
// out := agentic.PlanUpdateOutput{Success: true, Plan: agentic.Plan{Status: "verified"}}
|
||||
// out := agentic.PlanUpdateOutput{Success: true, Plan: agentic.Plan{Status: "verified"}}
|
||||
type PlanUpdateOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Plan Plan `json:"plan"`
|
||||
}
|
||||
|
||||
// PlanDeleteInput is the input for agentic_plan_delete.
|
||||
//
|
||||
// input := agentic.PlanDeleteInput{ID: "id-1-a3f2b1"}
|
||||
// input := agentic.PlanDeleteInput{ID: "id-1-a3f2b1"}
|
||||
type PlanDeleteInput struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// PlanDeleteOutput is the output for agentic_plan_delete.
|
||||
//
|
||||
// out := agentic.PlanDeleteOutput{Success: true, Deleted: "id-1-a3f2b1"}
|
||||
// out := agentic.PlanDeleteOutput{Success: true, Deleted: "id-1-a3f2b1"}
|
||||
type PlanDeleteOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Deleted string `json:"deleted"`
|
||||
}
|
||||
|
||||
// PlanListInput is the input for agentic_plan_list.
|
||||
//
|
||||
// input := agentic.PlanListInput{Repo: "go-io", Status: "ready"}
|
||||
// input := agentic.PlanListInput{Repo: "go-io", Status: "ready"}
|
||||
type PlanListInput struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Repo string `json:"repo,omitempty"`
|
||||
}
|
||||
|
||||
// PlanListOutput is the output for agentic_plan_list.
|
||||
//
|
||||
// out := agentic.PlanListOutput{Success: true, Count: 2, Plans: []agentic.Plan{{ID: "id-1-a3f2b1"}}}
|
||||
// out := agentic.PlanListOutput{Success: true, Count: 2, Plans: []agentic.Plan{{ID: "id-1-a3f2b1"}}}
|
||||
type PlanListOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Count int `json:"count"`
|
||||
Plans []Plan `json:"plans"`
|
||||
}
|
||||
|
||||
// --- Registration ---
|
||||
|
||||
func (s *PrepSubsystem) registerPlanTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "agentic_plan_create",
|
||||
|
|
@ -160,8 +136,6 @@ func (s *PrepSubsystem) registerPlanTools(server *mcp.Server) {
|
|||
}, s.planList)
|
||||
}
|
||||
|
||||
// --- Handlers ---
|
||||
|
||||
func (s *PrepSubsystem) planCreate(_ context.Context, _ *mcp.CallToolRequest, input PlanCreateInput) (*mcp.CallToolResult, PlanCreateOutput, error) {
|
||||
if input.Title == "" {
|
||||
return nil, PlanCreateOutput{}, core.E("planCreate", "title is required", nil)
|
||||
|
|
@ -356,17 +330,13 @@ func (s *PrepSubsystem) planList(_ context.Context, _ *mcp.CallToolRequest, inpu
|
|||
}, nil
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func planPath(dir, id string) string {
|
||||
safe := core.SanitisePath(id)
|
||||
return core.JoinPath(dir, core.Concat(safe, ".json"))
|
||||
}
|
||||
|
||||
// readPlanResult reads and decodes a plan file as core.Result.
|
||||
//
|
||||
// result := readPlanResult(PlansRoot(), "plan-id")
|
||||
// if result.OK { plan := result.Value.(*Plan) }
|
||||
// result := readPlanResult(PlansRoot(), "plan-id")
|
||||
// if result.OK { plan := result.Value.(*Plan) }
|
||||
func readPlanResult(dir, id string) core.Result {
|
||||
r := fs.Read(planPath(dir, id))
|
||||
if !r.OK {
|
||||
|
|
@ -405,10 +375,8 @@ func readPlan(dir, id string) (*Plan, error) {
|
|||
return plan, nil
|
||||
}
|
||||
|
||||
// writePlanResult writes a plan file and returns core.Result.
|
||||
//
|
||||
// result := writePlanResult(PlansRoot(), plan)
|
||||
// if result.OK { path := result.Value.(string) }
|
||||
// result := writePlanResult(PlansRoot(), plan)
|
||||
// if result.OK { path := result.Value.(string) }
|
||||
func writePlanResult(dir string, plan *Plan) core.Result {
|
||||
if plan == nil {
|
||||
return core.Result{Value: core.E("writePlan", "plan is required", nil), OK: false}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// --- agentic_create_pr ---
|
||||
|
||||
// CreatePRInput is the input for agentic_create_pr.
|
||||
//
|
||||
// input := agentic.CreatePRInput{Workspace: "core/go-io/task-42", Title: "Fix watcher panic"}
|
||||
|
|
@ -179,8 +177,6 @@ func (s *PrepSubsystem) commentOnIssue(ctx context.Context, org, repo string, is
|
|||
s.forge.Issues.CreateComment(ctx, org, repo, int64(issue), comment)
|
||||
}
|
||||
|
||||
// --- agentic_list_prs ---
|
||||
|
||||
// ListPRsInput is the input for agentic_list_prs.
|
||||
//
|
||||
// input := agentic.ListPRsInput{Org: "core", Repo: "go-io", State: "open", Limit: 10}
|
||||
|
|
|
|||
|
|
@ -287,8 +287,6 @@ func (s *PrepSubsystem) RegisterTools(server *mcp.Server) {
|
|||
// _ = subsystem.Shutdown(context.Background())
|
||||
func (s *PrepSubsystem) Shutdown(_ context.Context) error { return nil }
|
||||
|
||||
// --- Input/Output types ---
|
||||
|
||||
// input := agentic.PrepInput{Repo: "go-io", Issue: 15, Task: "Migrate to Core primitives"}
|
||||
type PrepInput struct {
|
||||
Repo string `json:"repo"` // required: e.g. "go-io"
|
||||
|
|
@ -511,8 +509,6 @@ func (s *PrepSubsystem) prepWorkspace(ctx context.Context, _ *mcp.CallToolReques
|
|||
return nil, out, nil
|
||||
}
|
||||
|
||||
// --- Spec Injection ---
|
||||
|
||||
// copyRepoSpecs copies RFC spec files from the plans repo into the workspace specs/ folder.
|
||||
// Maps repo name to plans directory: go-io → core/go/io, agent → core/agent, core-bio → core/php/bio.
|
||||
// Preserves subdirectory structure so sub-package specs land in specs/{pkg}/RFC.md.
|
||||
|
|
@ -574,8 +570,6 @@ func (s *PrepSubsystem) copyRepoSpecs(workspaceDir, repo string) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Public API for CLI testing ---
|
||||
|
||||
// TestPrepWorkspace exposes prepWorkspace for CLI testing.
|
||||
//
|
||||
// _, out, err := prep.TestPrepWorkspace(ctx, input)
|
||||
|
|
@ -590,8 +584,6 @@ func (s *PrepSubsystem) TestBuildPrompt(ctx context.Context, input PrepInput, br
|
|||
return s.buildPrompt(ctx, input, branch, repoPath)
|
||||
}
|
||||
|
||||
// --- Prompt Building ---
|
||||
|
||||
// buildPrompt assembles all context into a single prompt string.
|
||||
// Context is gathered from: persona, flow, issue, brain, consumers, git log, wiki, plan.
|
||||
func (s *PrepSubsystem) buildPrompt(ctx context.Context, input PrepInput, branch, repoPath string) (string, int, int) {
|
||||
|
|
@ -678,8 +670,6 @@ func (s *PrepSubsystem) buildPrompt(ctx context.Context, input PrepInput, branch
|
|||
return b.String(), memories, consumers
|
||||
}
|
||||
|
||||
// --- Context Helpers (return strings, not write files) ---
|
||||
|
||||
func (s *PrepSubsystem) getIssueBody(ctx context.Context, org, repo string, issue int) string {
|
||||
idx := core.Sprintf("%d", issue)
|
||||
iss, err := s.forge.Issues.Get(ctx, forge.Params{"owner": org, "repo": repo, "index": idx})
|
||||
|
|
@ -864,8 +854,6 @@ func (s *PrepSubsystem) renderPlan(templateSlug string, variables map[string]str
|
|||
return plan.String()
|
||||
}
|
||||
|
||||
// --- Detection helpers (unchanged) ---
|
||||
|
||||
func detectLanguage(repoPath string) string {
|
||||
checks := []struct {
|
||||
file string
|
||||
|
|
|
|||
|
|
@ -141,10 +141,7 @@ func (s *PrepSubsystem) delayForAgent(agent string) time.Duration {
|
|||
return time.Duration(rate.SustainedDelay) * time.Second
|
||||
}
|
||||
|
||||
// countRunningByAgent counts running workspaces for a specific agent type
|
||||
// using the in-memory Registry. Falls back to disk scan if Registry is empty.
|
||||
//
|
||||
// n := s.countRunningByAgent("codex") // counts all codex:* variants
|
||||
// n := s.countRunningByAgent("codex")
|
||||
func (s *PrepSubsystem) countRunningByAgent(agent string) int {
|
||||
var runtime *core.Core
|
||||
if s.ServiceRuntime != nil {
|
||||
|
|
@ -182,10 +179,7 @@ func (s *PrepSubsystem) countRunningByAgentDisk(runtime *core.Core, agent string
|
|||
return count
|
||||
}
|
||||
|
||||
// countRunningByModel counts running workspaces for a specific agent:model string
|
||||
// using the in-memory Registry.
|
||||
//
|
||||
// n := s.countRunningByModel("codex:gpt-5.4") // counts only that model
|
||||
// n := s.countRunningByModel("codex:gpt-5.4")
|
||||
func (s *PrepSubsystem) countRunningByModel(agent string) int {
|
||||
var runtime *core.Core
|
||||
if s.ServiceRuntime != nil {
|
||||
|
|
@ -268,11 +262,8 @@ func (s *PrepSubsystem) canDispatchAgent(agent string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// modelVariant extracts the model name from an agent string.
|
||||
//
|
||||
// codex:gpt-5.4 → gpt-5.4
|
||||
// codex:gpt-5.3-codex-spark → gpt-5.3-codex-spark
|
||||
// claude → ""
|
||||
// model := modelVariant("codex:gpt-5.4")
|
||||
// _ = model
|
||||
func modelVariant(agent string) string {
|
||||
parts := core.SplitN(agent, ":", 2)
|
||||
if len(parts) < 2 {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// --- agentic_dispatch_remote tool ---
|
||||
|
||||
// RemoteDispatchInput dispatches a task to a remote core-agent over HTTP.
|
||||
//
|
||||
// input := agentic.RemoteDispatchInput{Host: "charon", Repo: "go-io", Task: "Run the review queue"}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// --- agentic_status_remote tool ---
|
||||
|
||||
// RemoteStatusInput queries a remote core-agent for workspace status.
|
||||
//
|
||||
// input := agentic.RemoteStatusInput{Host: "charon"}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,7 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// --- agentic_review_queue tool ---
|
||||
|
||||
// ReviewQueueInput controls the review queue runner.
|
||||
//
|
||||
// input := agentic.ReviewQueueInput{Reviewer: "coderabbit", Limit: 4, DryRun: true}
|
||||
// input := agentic.ReviewQueueInput{Reviewer: "coderabbit", Limit: 4, DryRun: true}
|
||||
type ReviewQueueInput struct {
|
||||
Limit int `json:"limit,omitempty"` // Max PRs to process this run (default: 4)
|
||||
Reviewer string `json:"reviewer,omitempty"` // "coderabbit" (default), "codex", or "both"
|
||||
|
|
@ -23,9 +19,7 @@ type ReviewQueueInput struct {
|
|||
LocalOnly bool `json:"local_only,omitempty"` // Run review locally, don't touch GitHub
|
||||
}
|
||||
|
||||
// ReviewQueueOutput reports what happened.
|
||||
//
|
||||
// out := agentic.ReviewQueueOutput{Success: true, Processed: []agentic.ReviewResult{{Repo: "go-io", Verdict: "clean"}}}
|
||||
// out := agentic.ReviewQueueOutput{Success: true, Processed: []agentic.ReviewResult{{Repo: "go-io", Verdict: "clean"}}}
|
||||
type ReviewQueueOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Processed []ReviewResult `json:"processed"`
|
||||
|
|
@ -33,9 +27,7 @@ type ReviewQueueOutput struct {
|
|||
RateLimit *RateLimitInfo `json:"rate_limit,omitempty"`
|
||||
}
|
||||
|
||||
// ReviewResult is the outcome of reviewing one repo.
|
||||
//
|
||||
// result := agentic.ReviewResult{Repo: "go-io", Verdict: "findings", Findings: 3, Action: "fix_dispatched"}
|
||||
// result := agentic.ReviewResult{Repo: "go-io", Verdict: "findings", Findings: 3, Action: "fix_dispatched"}
|
||||
type ReviewResult struct {
|
||||
Repo string `json:"repo"`
|
||||
Verdict string `json:"verdict"` // clean, findings, rate_limited, error
|
||||
|
|
@ -44,9 +36,7 @@ type ReviewResult struct {
|
|||
Detail string `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
// RateLimitInfo tracks CodeRabbit rate limit state.
|
||||
//
|
||||
// limit := agentic.RateLimitInfo{Limited: true, Message: "retry after 2026-03-22T06:00:00Z"}
|
||||
// limit := agentic.RateLimitInfo{Limited: true, Message: "retry after 2026-03-22T06:00:00Z"}
|
||||
type RateLimitInfo struct {
|
||||
Limited bool `json:"limited"`
|
||||
RetryAt time.Time `json:"retry_at,omitempty"`
|
||||
|
|
@ -143,7 +133,7 @@ func (s *PrepSubsystem) reviewQueue(ctx context.Context, _ *mcp.CallToolRequest,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// findReviewCandidates returns repos that are ahead of GitHub main.
|
||||
// repos := s.findReviewCandidates("/srv/Code/core")
|
||||
func (s *PrepSubsystem) findReviewCandidates(basePath string) []string {
|
||||
paths := core.PathGlob(core.JoinPath(basePath, "*"))
|
||||
|
||||
|
|
@ -164,7 +154,7 @@ func (s *PrepSubsystem) findReviewCandidates(basePath string) []string {
|
|||
return candidates
|
||||
}
|
||||
|
||||
// reviewRepo runs CodeRabbit on a single repo and takes action.
|
||||
// result := s.reviewRepo(ctx, repoDir, "go-io", "coderabbit", false, false)
|
||||
func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer string, dryRun, localOnly bool) ReviewResult {
|
||||
result := ReviewResult{Repo: repo}
|
||||
process := s.Core().Process()
|
||||
|
|
@ -253,7 +243,7 @@ func (s *PrepSubsystem) reviewRepo(ctx context.Context, repoDir, repo, reviewer
|
|||
return result
|
||||
}
|
||||
|
||||
// pushAndMerge pushes to GitHub dev and merges the PR.
|
||||
// _ = s.pushAndMerge(ctx, repoDir, "go-io")
|
||||
func (s *PrepSubsystem) pushAndMerge(ctx context.Context, repoDir, repo string) error {
|
||||
process := s.Core().Process()
|
||||
if r := process.RunIn(ctx, repoDir, "git", "push", "github", "HEAD:refs/heads/dev", "--force"); !r.OK {
|
||||
|
|
@ -270,7 +260,7 @@ func (s *PrepSubsystem) pushAndMerge(ctx context.Context, repoDir, repo string)
|
|||
return nil
|
||||
}
|
||||
|
||||
// dispatchFixFromQueue dispatches an opus agent to fix CodeRabbit findings.
|
||||
// _ = s.dispatchFixFromQueue(ctx, "go-io", task)
|
||||
func (s *PrepSubsystem) dispatchFixFromQueue(ctx context.Context, repo, task string) error {
|
||||
// Use the dispatch system — creates workspace, spawns agent
|
||||
input := DispatchInput{
|
||||
|
|
@ -288,7 +278,7 @@ func (s *PrepSubsystem) dispatchFixFromQueue(ctx context.Context, repo, task str
|
|||
return nil
|
||||
}
|
||||
|
||||
// countFindings estimates the number of findings in CodeRabbit output.
|
||||
// findings := countFindings(output)
|
||||
func countFindings(output string) int {
|
||||
// Count lines that look like findings
|
||||
count := 0
|
||||
|
|
@ -306,8 +296,7 @@ func countFindings(output string) int {
|
|||
return count
|
||||
}
|
||||
|
||||
// parseRetryAfter extracts the retry duration from a rate limit message.
|
||||
// Example: "please try after 4 minutes and 56 seconds"
|
||||
// delay := parseRetryAfter("please try after 4 minutes and 56 seconds")
|
||||
func parseRetryAfter(message string) time.Duration {
|
||||
if retryAfterPattern == nil {
|
||||
return 5 * time.Minute
|
||||
|
|
@ -325,9 +314,7 @@ func parseRetryAfter(message string) time.Duration {
|
|||
return 5 * time.Minute
|
||||
}
|
||||
|
||||
// buildReviewCommand returns the command and args for the chosen reviewer.
|
||||
//
|
||||
// cmd, args := s.buildReviewCommand(repoDir, "coderabbit")
|
||||
// cmd, args := s.buildReviewCommand(repoDir, "coderabbit")
|
||||
func (s *PrepSubsystem) buildReviewCommand(repoDir, reviewer string) (string, []string) {
|
||||
switch reviewer {
|
||||
case "codex":
|
||||
|
|
@ -337,7 +324,7 @@ func (s *PrepSubsystem) buildReviewCommand(repoDir, reviewer string) (string, []
|
|||
}
|
||||
}
|
||||
|
||||
// storeReviewOutput saves raw review output for training data collection.
|
||||
// s.storeReviewOutput(repoDir, "go-io", "coderabbit", output)
|
||||
func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string) {
|
||||
dataDir := core.JoinPath(HomeDir(), ".core", "training", "reviews")
|
||||
fs.EnsureDir(dataDir)
|
||||
|
|
@ -369,9 +356,7 @@ func (s *PrepSubsystem) storeReviewOutput(repoDir, repo, reviewer, output string
|
|||
core.WriteAll(r.Value, core.Concat(jsonLine, "\n"))
|
||||
}
|
||||
|
||||
// saveRateLimitState writes the current rate-limit snapshot.
|
||||
//
|
||||
// s.saveRateLimitState(&RateLimitInfo{Limited: true, RetryAt: time.Now().Add(30 * time.Minute)})
|
||||
// s.saveRateLimitState(&RateLimitInfo{Limited: true, RetryAt: time.Now().Add(30 * time.Minute)})
|
||||
func (s *PrepSubsystem) saveRateLimitState(info *RateLimitInfo) {
|
||||
path := core.JoinPath(HomeDir(), ".core", "coderabbit-ratelimit.json")
|
||||
if r := fs.WriteAtomic(path, core.JSONMarshalString(info)); !r.OK {
|
||||
|
|
@ -383,7 +368,7 @@ func (s *PrepSubsystem) saveRateLimitState(info *RateLimitInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
// loadRateLimitState reads persisted rate limit info.
|
||||
// info := s.loadRateLimitState()
|
||||
func (s *PrepSubsystem) loadRateLimitState() *RateLimitInfo {
|
||||
path := core.JoinPath(HomeDir(), ".core", "coderabbit-ratelimit.json")
|
||||
r := fs.Read(path)
|
||||
|
|
|
|||
|
|
@ -2,19 +2,10 @@
|
|||
|
||||
package agentic
|
||||
|
||||
// StartRunner preserves the legacy PrepSubsystem call after queue ownership moved to pkg/runner.Service.
|
||||
//
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.StartRunner()
|
||||
//
|
||||
// The runner service registers as core.WithService(runner.Register) and
|
||||
// manages its own background loop, frozen state, and concurrency checks.
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.StartRunner()
|
||||
func (s *PrepSubsystem) StartRunner() {}
|
||||
|
||||
// Poke preserves the legacy queue signal after queue ownership moved to pkg/runner.Service.
|
||||
//
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.Poke()
|
||||
//
|
||||
// Runner catches AgentCompleted via HandleIPCEvents and pokes itself.
|
||||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.Poke()
|
||||
func (s *PrepSubsystem) Poke() {}
|
||||
|
|
|
|||
|
|
@ -101,8 +101,6 @@ func workspaceStatusValue(result core.Result) (*WorkspaceStatus, bool) {
|
|||
return workspaceStatus, true
|
||||
}
|
||||
|
||||
// --- agentic_status tool ---
|
||||
|
||||
// input := agentic.StatusInput{Workspace: "core/go-io/task-42", Limit: 50}
|
||||
type StatusInput struct {
|
||||
Workspace string `json:"workspace,omitempty"` // specific workspace name, or empty for all
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// HTTP transport for Core API streams.
|
||||
// This is the ONE file in core/agent that imports net/http.
|
||||
// All other files use the exported helpers: HTTPGet, HTTPPost, HTTPCall.
|
||||
|
||||
package agentic
|
||||
|
||||
|
|
@ -26,10 +24,8 @@ type httpStream struct {
|
|||
response []byte
|
||||
}
|
||||
|
||||
// Send issues the configured HTTP request and caches the response body for Receive.
|
||||
//
|
||||
// stream := &httpStream{client: defaultClient, url: "https://forge.lthn.ai/api/v1/version", method: "GET"}
|
||||
// _ = stream.Send(nil)
|
||||
// stream := &httpStream{client: defaultClient, url: "https://forge.lthn.ai/api/v1/version", method: "GET"}
|
||||
// _ = stream.Send(nil)
|
||||
func (s *httpStream) Send(data []byte) error {
|
||||
request, err := http.NewRequestWithContext(context.Background(), s.method, s.url, core.NewReader(string(data)))
|
||||
if err != nil {
|
||||
|
|
@ -55,26 +51,20 @@ func (s *httpStream) Send(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Receive returns the cached response body from the last Send call.
|
||||
//
|
||||
// stream := &httpStream{response: []byte(`{"ok":true}`)}
|
||||
// data, _ := stream.Receive()
|
||||
// _ = data
|
||||
// stream := &httpStream{response: []byte(`{"ok":true}`)}
|
||||
// data, _ := stream.Receive()
|
||||
// _ = data
|
||||
func (s *httpStream) Receive() ([]byte, error) {
|
||||
return s.response, nil
|
||||
}
|
||||
|
||||
// Close satisfies core.Stream for one-shot HTTP requests.
|
||||
//
|
||||
// stream := &httpStream{}
|
||||
// _ = stream.Close()
|
||||
// stream := &httpStream{}
|
||||
// _ = stream.Close()
|
||||
func (s *httpStream) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterHTTPTransport registers the HTTP/HTTPS protocol handler with Core API.
|
||||
//
|
||||
// agentic.RegisterHTTPTransport(c)
|
||||
// agentic.RegisterHTTPTransport(c)
|
||||
func RegisterHTTPTransport(c *core.Core) {
|
||||
factory := func(handle *core.DriveHandle) (core.Stream, error) {
|
||||
token := handle.Options.String("token")
|
||||
|
|
@ -89,50 +79,32 @@ func RegisterHTTPTransport(c *core.Core) {
|
|||
c.API().RegisterProtocol("https", factory)
|
||||
}
|
||||
|
||||
// --- REST helpers — all HTTP in core/agent routes through these ---
|
||||
|
||||
// HTTPGet performs a GET request. Returns Result{Value: string (response body), OK: bool}.
|
||||
// Auth is "token {token}" for Forge, "Bearer {token}" for Brain.
|
||||
//
|
||||
// result := agentic.HTTPGet(ctx, "https://forge.lthn.ai/api/v1/repos", "my-token", "token")
|
||||
// result := agentic.HTTPGet(ctx, "https://forge.lthn.ai/api/v1/repos", "my-token", "token")
|
||||
func HTTPGet(ctx context.Context, url, token, authScheme string) core.Result {
|
||||
return httpDo(ctx, "GET", url, "", token, authScheme)
|
||||
}
|
||||
|
||||
// HTTPPost performs a POST request with a JSON body. Returns Result{Value: string, OK: bool}.
|
||||
//
|
||||
// result := agentic.HTTPPost(ctx, url, core.JSONMarshalString(payload), token, "token")
|
||||
// result := agentic.HTTPPost(ctx, url, core.JSONMarshalString(payload), token, "token")
|
||||
func HTTPPost(ctx context.Context, url, body, token, authScheme string) core.Result {
|
||||
return httpDo(ctx, "POST", url, body, token, authScheme)
|
||||
}
|
||||
|
||||
// HTTPPatch performs a PATCH request with a JSON body.
|
||||
//
|
||||
// result := agentic.HTTPPatch(ctx, url, body, token, "token")
|
||||
// result := agentic.HTTPPatch(ctx, url, body, token, "token")
|
||||
func HTTPPatch(ctx context.Context, url, body, token, authScheme string) core.Result {
|
||||
return httpDo(ctx, "PATCH", url, body, token, authScheme)
|
||||
}
|
||||
|
||||
// HTTPDelete performs a DELETE request.
|
||||
//
|
||||
// result := agentic.HTTPDelete(ctx, url, body, token, "Bearer")
|
||||
// result := agentic.HTTPDelete(ctx, url, body, token, "Bearer")
|
||||
func HTTPDelete(ctx context.Context, url, body, token, authScheme string) core.Result {
|
||||
return httpDo(ctx, "DELETE", url, body, token, authScheme)
|
||||
}
|
||||
|
||||
// HTTPDo performs an HTTP request with the specified method.
|
||||
//
|
||||
// result := agentic.HTTPDo(ctx, "PUT", url, body, token, "token")
|
||||
// result := agentic.HTTPDo(ctx, "PUT", url, body, token, "token")
|
||||
func HTTPDo(ctx context.Context, method, url, body, token, authScheme string) core.Result {
|
||||
return httpDo(ctx, method, url, body, token, authScheme)
|
||||
}
|
||||
|
||||
// --- Drive-aware REST helpers — route through c.Drive() for endpoint resolution ---
|
||||
|
||||
// DriveGet performs a GET request using a named Drive endpoint.
|
||||
// Reads base URL and token from the Drive handle registered in Core.
|
||||
//
|
||||
// result := DriveGet(c, "forge", "/api/v1/repos/core/go-io", "token")
|
||||
// result := DriveGet(c, "forge", "/api/v1/repos/core/go-io", "token")
|
||||
func DriveGet(c *core.Core, drive, path, authScheme string) core.Result {
|
||||
base, token := driveEndpoint(c, drive)
|
||||
if base == "" {
|
||||
|
|
@ -141,9 +113,7 @@ func DriveGet(c *core.Core, drive, path, authScheme string) core.Result {
|
|||
return httpDo(context.Background(), "GET", core.Concat(base, path), "", token, authScheme)
|
||||
}
|
||||
|
||||
// DrivePost performs a POST request using a named Drive endpoint.
|
||||
//
|
||||
// result := DrivePost(c, "forge", "/api/v1/repos/core/go-io/issues", body, "token")
|
||||
// result := DrivePost(c, "forge", "/api/v1/repos/core/go-io/issues", body, "token")
|
||||
func DrivePost(c *core.Core, drive, path, body, authScheme string) core.Result {
|
||||
base, token := driveEndpoint(c, drive)
|
||||
if base == "" {
|
||||
|
|
@ -152,9 +122,7 @@ func DrivePost(c *core.Core, drive, path, body, authScheme string) core.Result {
|
|||
return httpDo(context.Background(), "POST", core.Concat(base, path), body, token, authScheme)
|
||||
}
|
||||
|
||||
// DriveDo performs an HTTP request using a named Drive endpoint.
|
||||
//
|
||||
// result := DriveDo(c, "forge", "PATCH", "/api/v1/repos/core/go-io/pulls/5", body, "token")
|
||||
// result := DriveDo(c, "forge", "PATCH", "/api/v1/repos/core/go-io/pulls/5", body, "token")
|
||||
func DriveDo(c *core.Core, drive, method, path, body, authScheme string) core.Result {
|
||||
base, token := driveEndpoint(c, drive)
|
||||
if base == "" {
|
||||
|
|
@ -163,7 +131,6 @@ func DriveDo(c *core.Core, drive, method, path, body, authScheme string) core.Re
|
|||
return httpDo(context.Background(), method, core.Concat(base, path), body, token, authScheme)
|
||||
}
|
||||
|
||||
// driveEndpoint reads base URL and token from a named Drive handle.
|
||||
func driveEndpoint(c *core.Core, name string) (base, token string) {
|
||||
driveResult := c.Drive().Get(name)
|
||||
if !driveResult.OK {
|
||||
|
|
@ -173,7 +140,6 @@ func driveEndpoint(c *core.Core, name string) (base, token string) {
|
|||
return driveHandle.Transport, driveHandle.Options.String("token")
|
||||
}
|
||||
|
||||
// httpDo is the single HTTP execution point. Every HTTP call in core/agent routes here.
|
||||
func httpDo(ctx context.Context, method, url, body, token, authScheme string) core.Result {
|
||||
var request *http.Request
|
||||
var err error
|
||||
|
|
@ -210,10 +176,7 @@ func httpDo(ctx context.Context, method, url, body, token, authScheme string) co
|
|||
return core.Result{Value: readResult.Value.(string), OK: response.StatusCode < 400}
|
||||
}
|
||||
|
||||
// --- MCP Streamable HTTP Transport ---
|
||||
|
||||
// mcpInitialize performs the MCP initialise handshake over Streamable HTTP.
|
||||
// Returns the session ID from the Mcp-Session-Id header.
|
||||
// sessionID, err := mcpInitialize(ctx, url, token)
|
||||
func mcpInitialize(ctx context.Context, url, token string) (string, error) {
|
||||
result := mcpInitializeResult(ctx, url, token)
|
||||
if !result.OK {
|
||||
|
|
@ -285,7 +248,6 @@ func mcpInitializeResult(ctx context.Context, url, token string) core.Result {
|
|||
return core.Result{Value: sessionID, OK: true}
|
||||
}
|
||||
|
||||
// mcpCall sends a JSON-RPC request and returns the parsed response.
|
||||
func mcpCall(ctx context.Context, url, token, sessionID string, body []byte) ([]byte, error) {
|
||||
result := mcpCallResult(ctx, url, token, sessionID, body)
|
||||
if !result.OK {
|
||||
|
|
@ -322,7 +284,6 @@ func mcpCallResult(ctx context.Context, url, token, sessionID string, body []byt
|
|||
return readSSEDataResult(response)
|
||||
}
|
||||
|
||||
// readSSEData reads an SSE response and extracts JSON from data: lines.
|
||||
func readSSEData(response *http.Response) ([]byte, error) {
|
||||
result := readSSEDataResult(response)
|
||||
if !result.OK {
|
||||
|
|
@ -339,7 +300,6 @@ func readSSEData(response *http.Response) ([]byte, error) {
|
|||
return data, nil
|
||||
}
|
||||
|
||||
// readSSEDataResult parses an SSE response and extracts the first data: payload as core.Result.
|
||||
func readSSEDataResult(response *http.Response) core.Result {
|
||||
readResult := core.ReadAll(response.Body)
|
||||
if !readResult.OK {
|
||||
|
|
@ -354,7 +314,6 @@ func readSSEDataResult(response *http.Response) core.Result {
|
|||
return core.Result{Value: core.E("readSSEData", "no data in SSE response", nil), OK: false}
|
||||
}
|
||||
|
||||
// mcpHeaders applies standard MCP HTTP headers.
|
||||
func mcpHeaders(request *http.Request, token, sessionID string) {
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Accept", "application/json, text/event-stream")
|
||||
|
|
@ -366,7 +325,6 @@ func mcpHeaders(request *http.Request, token, sessionID string) {
|
|||
}
|
||||
}
|
||||
|
||||
// drainSSE reads and discards an SSE response body.
|
||||
func drainSSE(response *http.Response) {
|
||||
core.ReadAll(response.Body)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,9 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// -- Input/Output types -------------------------------------------------------
|
||||
|
||||
// input := brain.RememberInput{
|
||||
// Content: "Use core.Env for system paths.",
|
||||
// Type: "convention",
|
||||
// Content: "Use core.Env for system paths.",
|
||||
// Type: "convention",
|
||||
// }
|
||||
type RememberInput struct {
|
||||
Content string `json:"content"`
|
||||
|
|
@ -28,8 +26,8 @@ type RememberInput struct {
|
|||
}
|
||||
|
||||
// output := brain.RememberOutput{
|
||||
// Success: true,
|
||||
// MemoryID: "mem_123",
|
||||
// Success: true,
|
||||
// MemoryID: "mem_123",
|
||||
// }
|
||||
type RememberOutput struct {
|
||||
Success bool `json:"success"`
|
||||
|
|
@ -38,8 +36,8 @@ type RememberOutput struct {
|
|||
}
|
||||
|
||||
// input := brain.RecallInput{
|
||||
// Query: "core.Env conventions",
|
||||
// TopK: 5,
|
||||
// Query: "core.Env conventions",
|
||||
// TopK: 5,
|
||||
// }
|
||||
type RecallInput struct {
|
||||
Query string `json:"query"`
|
||||
|
|
@ -48,8 +46,8 @@ type RecallInput struct {
|
|||
}
|
||||
|
||||
// filter := brain.RecallFilter{
|
||||
// Project: "agent",
|
||||
// Type: "convention",
|
||||
// Project: "agent",
|
||||
// Type: "convention",
|
||||
// }
|
||||
type RecallFilter struct {
|
||||
Project string `json:"project,omitempty"`
|
||||
|
|
@ -59,8 +57,8 @@ type RecallFilter struct {
|
|||
}
|
||||
|
||||
// output := brain.RecallOutput{
|
||||
// Success: true,
|
||||
// Count: 1,
|
||||
// Success: true,
|
||||
// Count: 1,
|
||||
// }
|
||||
type RecallOutput struct {
|
||||
Success bool `json:"success"`
|
||||
|
|
@ -69,9 +67,9 @@ type RecallOutput struct {
|
|||
}
|
||||
|
||||
// memory := brain.Memory{
|
||||
// ID: "mem_123",
|
||||
// Type: "convention",
|
||||
// Content: "Use core.Env for system paths.",
|
||||
// ID: "mem_123",
|
||||
// Type: "convention",
|
||||
// Content: "Use core.Env for system paths.",
|
||||
// }
|
||||
type Memory struct {
|
||||
ID string `json:"id"`
|
||||
|
|
@ -88,8 +86,8 @@ type Memory struct {
|
|||
}
|
||||
|
||||
// input := brain.ForgetInput{
|
||||
// ID: "mem_123",
|
||||
// Reason: "superseded",
|
||||
// ID: "mem_123",
|
||||
// Reason: "superseded",
|
||||
// }
|
||||
type ForgetInput struct {
|
||||
ID string `json:"id"`
|
||||
|
|
@ -97,8 +95,8 @@ type ForgetInput struct {
|
|||
}
|
||||
|
||||
// output := brain.ForgetOutput{
|
||||
// Success: true,
|
||||
// Forgotten: "mem_123",
|
||||
// Success: true,
|
||||
// Forgotten: "mem_123",
|
||||
// }
|
||||
type ForgetOutput struct {
|
||||
Success bool `json:"success"`
|
||||
|
|
@ -107,8 +105,8 @@ type ForgetOutput struct {
|
|||
}
|
||||
|
||||
// input := brain.ListInput{
|
||||
// Project: "agent",
|
||||
// Limit: 20,
|
||||
// Project: "agent",
|
||||
// Limit: 20,
|
||||
// }
|
||||
type ListInput struct {
|
||||
Project string `json:"project,omitempty"`
|
||||
|
|
@ -118,8 +116,8 @@ type ListInput struct {
|
|||
}
|
||||
|
||||
// output := brain.ListOutput{
|
||||
// Success: true,
|
||||
// Count: 2,
|
||||
// Success: true,
|
||||
// Count: 2,
|
||||
// }
|
||||
type ListOutput struct {
|
||||
Success bool `json:"success"`
|
||||
|
|
@ -127,8 +125,6 @@ type ListOutput struct {
|
|||
Memories []Memory `json:"memories"`
|
||||
}
|
||||
|
||||
// -- Tool registration --------------------------------------------------------
|
||||
|
||||
func (s *Subsystem) registerBrainTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "brain_remember",
|
||||
|
|
@ -151,8 +147,6 @@ func (s *Subsystem) registerBrainTools(server *mcp.Server) {
|
|||
}, s.brainList)
|
||||
}
|
||||
|
||||
// -- Tool handlers ------------------------------------------------------------
|
||||
|
||||
func (s *Subsystem) brainRemember(_ context.Context, _ *mcp.CallToolRequest, input RememberInput) (*mcp.CallToolResult, RememberOutput, error) {
|
||||
if s.bridge == nil {
|
||||
return nil, RememberOutput{}, errBridgeNotAvailable
|
||||
|
|
|
|||
|
|
@ -125,12 +125,8 @@ func mountEmbed(filesystem embed.FS, baseDir string) core.Result {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Prompts ---
|
||||
|
||||
// Template tries Prompt then Task (backwards compat).
|
||||
//
|
||||
// r := lib.Template("coding")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := lib.Template("coding")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func Template(slug string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
|
|
@ -141,10 +137,8 @@ func Template(slug string) core.Result {
|
|||
return Task(slug)
|
||||
}
|
||||
|
||||
// Prompt reads a system prompt by slug.
|
||||
//
|
||||
// r := lib.Prompt("coding")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := lib.Prompt("coding")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func Prompt(slug string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
|
|
@ -152,10 +146,8 @@ func Prompt(slug string) core.Result {
|
|||
return promptFS.ReadString(core.Concat(slug, ".md"))
|
||||
}
|
||||
|
||||
// Task reads a structured task plan by slug. Tries .md, .yaml, .yml.
|
||||
//
|
||||
// r := lib.Task("code/review")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := lib.Task("code/review")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func Task(slug string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
|
|
@ -180,10 +172,8 @@ type Bundle struct {
|
|||
Files map[string]string
|
||||
}
|
||||
|
||||
// TaskBundle reads a task and its companion files.
|
||||
//
|
||||
// r := lib.TaskBundle("code/review")
|
||||
// if r.OK { b := r.Value.(lib.Bundle) }
|
||||
// r := lib.TaskBundle("code/review")
|
||||
// if r.OK { b := r.Value.(lib.Bundle) }
|
||||
func TaskBundle(slug string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
|
|
@ -212,10 +202,8 @@ func TaskBundle(slug string) core.Result {
|
|||
return core.Result{Value: b, OK: true}
|
||||
}
|
||||
|
||||
// Flow reads a build/release workflow by slug.
|
||||
//
|
||||
// r := lib.Flow("go")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := lib.Flow("go")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func Flow(slug string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
|
|
@ -223,10 +211,8 @@ func Flow(slug string) core.Result {
|
|||
return flowFS.ReadString(core.Concat(slug, ".md"))
|
||||
}
|
||||
|
||||
// Persona reads a domain/role persona by path.
|
||||
//
|
||||
// r := lib.Persona("secops/developer")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := lib.Persona("secops/developer")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func Persona(path string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
|
|
@ -234,8 +220,6 @@ func Persona(path string) core.Result {
|
|||
return personaFS.ReadString(core.Concat(path, ".md"))
|
||||
}
|
||||
|
||||
// --- Workspace Templates ---
|
||||
|
||||
// WorkspaceData is the data passed to workspace templates.
|
||||
//
|
||||
// data := &lib.WorkspaceData{
|
||||
|
|
@ -259,13 +243,11 @@ type WorkspaceData struct {
|
|||
TestCmd string
|
||||
}
|
||||
|
||||
// ExtractWorkspace creates an agent workspace from a template.
|
||||
// Template names: "default", "security", "review".
|
||||
//
|
||||
// r := lib.ExtractWorkspace("default", "/tmp/ws", &lib.WorkspaceData{
|
||||
// Repo: "go-io", Task: "fix tests", Agent: "codex",
|
||||
// })
|
||||
// core.Println(r.OK)
|
||||
//
|
||||
// core.Println(r.OK)
|
||||
func ExtractWorkspace(templateName, targetDir string, data *WorkspaceData) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
|
|
@ -309,11 +291,8 @@ func ExtractWorkspace(templateName, targetDir string, data *WorkspaceData) core.
|
|||
return core.Result{Value: targetDir, OK: true}
|
||||
}
|
||||
|
||||
// WorkspaceFile reads a single file from a workspace template.
|
||||
// Returns the file content as a string.
|
||||
//
|
||||
// r := lib.WorkspaceFile("default", "CODEX-PHP.md.tmpl")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
// r := lib.WorkspaceFile("default", "CODEX-PHP.md.tmpl")
|
||||
// if r.OK { content := r.Value.(string) }
|
||||
func WorkspaceFile(templateName, filename string) core.Result {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return result
|
||||
|
|
@ -326,26 +305,16 @@ func WorkspaceFile(templateName, filename string) core.Result {
|
|||
return embed.ReadString(filename)
|
||||
}
|
||||
|
||||
// --- List Functions ---
|
||||
|
||||
// ListPrompts returns available system prompt slugs.
|
||||
//
|
||||
// prompts := lib.ListPrompts() // ["coding", "review", ...]
|
||||
// prompts := lib.ListPrompts() // ["coding", "review", ...]
|
||||
func ListPrompts() []string { return listNames("prompt") }
|
||||
|
||||
// ListFlows returns available build/release flow slugs.
|
||||
//
|
||||
// flows := lib.ListFlows() // ["go", "php", "node", ...]
|
||||
// flows := lib.ListFlows() // ["go", "php", "node", ...]
|
||||
func ListFlows() []string { return listNames("flow") }
|
||||
|
||||
// ListWorkspaces returns available workspace template names.
|
||||
//
|
||||
// templates := lib.ListWorkspaces() // ["default", "security", ...]
|
||||
// templates := lib.ListWorkspaces() // ["default", "security", ...]
|
||||
func ListWorkspaces() []string { return listNames("workspace") }
|
||||
|
||||
// ListTasks returns available task plan slugs, including nested paths.
|
||||
//
|
||||
// tasks := lib.ListTasks() // ["bug-fix", "code/review", "code/refactor", ...]
|
||||
// tasks := lib.ListTasks() // ["bug-fix", "code/review", "code/refactor", ...]
|
||||
func ListTasks() []string {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return nil
|
||||
|
|
@ -357,9 +326,7 @@ func ListTasks() []string {
|
|||
return names.AsSlice()
|
||||
}
|
||||
|
||||
// ListPersonas returns available persona paths, including nested directories.
|
||||
//
|
||||
// personas := lib.ListPersonas() // ["code/go", "secops/developer", ...]
|
||||
// personas := lib.ListPersonas() // ["code/go", "secops/developer", ...]
|
||||
func ListPersonas() []string {
|
||||
if result := ensureMounted(); !result.OK {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
// c.ACTION(messages.AgentCompleted{Agent: "codex", Repo: "go-io", Status: "completed"})
|
||||
package messages
|
||||
|
||||
// --- Agent Lifecycle ---
|
||||
|
||||
// c.ACTION(messages.AgentStarted{Agent: "codex", Repo: "go-io", Workspace: "core/go-io/task-5"})
|
||||
type AgentStarted struct {
|
||||
Agent string
|
||||
|
|
@ -20,8 +18,6 @@ type AgentCompleted struct {
|
|||
Status string // completed, failed, blocked
|
||||
}
|
||||
|
||||
// --- QA & PR Pipeline ---
|
||||
|
||||
// c.ACTION(messages.QAResult{Workspace: "core/go-io/task-5", Repo: "go-io", Passed: true})
|
||||
type QAResult struct {
|
||||
Workspace string
|
||||
|
|
@ -53,8 +49,6 @@ type PRNeedsReview struct {
|
|||
Reason string
|
||||
}
|
||||
|
||||
// --- Queue ---
|
||||
|
||||
// c.ACTION(messages.QueueDrained{Completed: 3})
|
||||
type QueueDrained struct {
|
||||
Completed int
|
||||
|
|
@ -76,8 +70,6 @@ type RateLimitDetected struct {
|
|||
Duration string
|
||||
}
|
||||
|
||||
// --- Monitor Events ---
|
||||
|
||||
// c.ACTION(messages.HarvestComplete{Repo: "go-io", Branch: "agent/fix-tests", Files: 5})
|
||||
type HarvestComplete struct {
|
||||
Repo string
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// result := m.harvestWorkspace("/srv/.core/workspace/core/go-io/task-5")
|
||||
// if result != nil && result.rejected == "" { /* ready-for-review */ }
|
||||
//
|
||||
// Completed workspaces are scanned, validated, and marked ready for review.
|
||||
// The code does not auto-push; review remains an explicit action.
|
||||
// if result != nil && result.rejected == "" { core.Print(nil, "%s", result.repo) }
|
||||
|
||||
package monitor
|
||||
|
||||
|
|
@ -17,7 +14,6 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// harvestResult tracks what happened during harvest.
|
||||
type harvestResult struct {
|
||||
repo string
|
||||
branch string
|
||||
|
|
@ -60,7 +56,7 @@ func (m *Subsystem) harvestCompleted() string {
|
|||
}
|
||||
|
||||
// result := m.harvestWorkspace("/srv/.core/workspace/core/go-io/task-5")
|
||||
// if result != nil && result.rejected == "" { /* ready-for-review */ }
|
||||
// if result != nil && result.rejected == "" { core.Print(nil, "%s", result.repo) }
|
||||
func (m *Subsystem) harvestWorkspace(workspaceDir string) *harvestResult {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(workspaceDir))
|
||||
if !statusResult.OK {
|
||||
|
|
@ -245,9 +241,7 @@ func (m *Subsystem) pushBranch(repoDir, branch string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// updateStatus rewrites status.json after a harvest decision.
|
||||
//
|
||||
// updateStatus(workspaceDir, "ready-for-review", "")
|
||||
// updateStatus(workspaceDir, "ready-for-review", "")
|
||||
func updateStatus(workspaceDir, status, question string) {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(workspaceDir))
|
||||
if !statusResult.OK {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// fs reuses the shared unrestricted filesystem used by agentic.
|
||||
// fs := agentic.LocalFs()
|
||||
var fs = agentic.LocalFs()
|
||||
|
||||
func runnerWorkspaceStatusFromAgentic(status *agentic.WorkspaceStatus) *WorkspaceStatus {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// DispatchConfig mirrors the `dispatch:` block in `agents.yaml`.
|
||||
//
|
||||
// config := runner.DispatchConfig{
|
||||
// DefaultAgent: "codex", DefaultTemplate: "coding", WorkspaceRoot: "/srv/core/workspace",
|
||||
// DefaultAgent: "codex", DefaultTemplate: "coding", WorkspaceRoot: "/srv/core/workspace",
|
||||
// }
|
||||
type DispatchConfig struct {
|
||||
DefaultAgent string `yaml:"default_agent"`
|
||||
|
|
@ -22,10 +20,8 @@ type DispatchConfig struct {
|
|||
WorkspaceRoot string `yaml:"workspace_root"`
|
||||
}
|
||||
|
||||
// RateConfig mirrors one agent pool under `rates:` in `agents.yaml`.
|
||||
//
|
||||
// rate := runner.RateConfig{
|
||||
// ResetUTC: "06:00", DailyLimit: 200, SustainedDelay: 120, BurstWindow: 2, BurstDelay: 300,
|
||||
// ResetUTC: "06:00", DailyLimit: 200, SustainedDelay: 120, BurstWindow: 2, BurstDelay: 300,
|
||||
// }
|
||||
type RateConfig struct {
|
||||
ResetUTC string `yaml:"reset_utc"`
|
||||
|
|
@ -47,10 +43,8 @@ type ConcurrencyLimit struct {
|
|||
Models map[string]int
|
||||
}
|
||||
|
||||
// UnmarshalYAML handles both int and map forms for concurrency limits.
|
||||
//
|
||||
// var limit ConcurrencyLimit
|
||||
// _ = yaml.Unmarshal([]byte("total: 5\ngpt-5.4: 1\n"), &limit)
|
||||
// var limit ConcurrencyLimit
|
||||
// _ = yaml.Unmarshal([]byte("total: 5\ngpt-5.4: 1\n"), &limit)
|
||||
func (c *ConcurrencyLimit) UnmarshalYAML(value *yaml.Node) error {
|
||||
var n int
|
||||
if err := value.Decode(&n); err == nil {
|
||||
|
|
@ -71,11 +65,9 @@ func (c *ConcurrencyLimit) UnmarshalYAML(value *yaml.Node) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AgentsConfig mirrors the full `agents.yaml` file.
|
||||
//
|
||||
// config := runner.AgentsConfig{
|
||||
// Version: 1,
|
||||
// Dispatch: runner.DispatchConfig{DefaultAgent: "codex", DefaultTemplate: "coding"},
|
||||
// Version: 1,
|
||||
// Dispatch: runner.DispatchConfig{DefaultAgent: "codex", DefaultTemplate: "coding"},
|
||||
// }
|
||||
type AgentsConfig struct {
|
||||
Version int `yaml:"version"`
|
||||
|
|
@ -84,10 +76,8 @@ type AgentsConfig struct {
|
|||
Rates map[string]RateConfig `yaml:"rates"`
|
||||
}
|
||||
|
||||
// loadAgentsConfig reads `agents.yaml` from the Core root.
|
||||
//
|
||||
// config := s.loadAgentsConfig()
|
||||
// core.Println(config.Dispatch.DefaultAgent)
|
||||
// config := s.loadAgentsConfig()
|
||||
// core.Println(config.Dispatch.DefaultAgent)
|
||||
func (s *Service) loadAgentsConfig() *AgentsConfig {
|
||||
paths := []string{
|
||||
core.JoinPath(CoreRoot(), "agents.yaml"),
|
||||
|
|
@ -115,9 +105,7 @@ func (s *Service) loadAgentsConfig() *AgentsConfig {
|
|||
}
|
||||
}
|
||||
|
||||
// canDispatchAgent checks both pool-level and per-model concurrency limits.
|
||||
//
|
||||
// if !s.canDispatchAgent("codex") { /* queue it */ }
|
||||
// if can, reason := s.canDispatchAgent("codex"); !can { _ = reason }
|
||||
func (s *Service) canDispatchAgent(agent string) (bool, string) {
|
||||
var concurrency map[string]ConcurrencyLimit
|
||||
if s.ServiceRuntime != nil {
|
||||
|
|
@ -157,9 +145,7 @@ func (s *Service) canDispatchAgent(agent string) (bool, string) {
|
|||
return true, ""
|
||||
}
|
||||
|
||||
// countRunningByAgent counts running workspaces using the in-memory Registry.
|
||||
//
|
||||
// n := s.countRunningByAgent("codex")
|
||||
// n := s.countRunningByAgent("codex")
|
||||
func (s *Service) countRunningByAgent(agent string) int {
|
||||
var runtime *core.Core
|
||||
if s.ServiceRuntime != nil {
|
||||
|
|
@ -180,9 +166,7 @@ func (s *Service) countRunningByAgent(agent string) int {
|
|||
return count
|
||||
}
|
||||
|
||||
// countRunningByModel counts running workspaces for a specific `agent:model`.
|
||||
//
|
||||
// n := s.countRunningByModel("codex:gpt-5.4")
|
||||
// n := s.countRunningByModel("codex:gpt-5.4")
|
||||
func (s *Service) countRunningByModel(agent string) int {
|
||||
var runtime *core.Core
|
||||
if s.ServiceRuntime != nil {
|
||||
|
|
@ -203,9 +187,7 @@ func (s *Service) countRunningByModel(agent string) int {
|
|||
return count
|
||||
}
|
||||
|
||||
// drainQueue fills any free concurrency slots from queued workspaces.
|
||||
//
|
||||
// s.drainQueue()
|
||||
// s.drainQueue()
|
||||
func (s *Service) drainQueue() {
|
||||
if s.frozen {
|
||||
return
|
||||
|
|
@ -336,8 +318,6 @@ func (s *Service) delayForAgent(agent string) time.Duration {
|
|||
return time.Duration(rate.SustainedDelay) * time.Second
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func baseAgent(agent string) string {
|
||||
return core.SplitN(agent, ":", 2)[0]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,8 +250,6 @@ func (s *Service) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Resu
|
|||
return core.Result{Value: s.workspaces, OK: true}
|
||||
}
|
||||
|
||||
// --- Actions ---
|
||||
|
||||
func (s *Service) actionDispatch(_ context.Context, options core.Options) core.Result {
|
||||
if s.frozen {
|
||||
return core.Result{Value: core.E("runner.actionDispatch", "queue is frozen", nil), OK: false}
|
||||
|
|
@ -342,8 +340,6 @@ func (s *Service) actionPoke(_ context.Context, _ core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// --- Queue runner ---
|
||||
|
||||
func (s *Service) startRunner() {
|
||||
s.pokeCh = make(chan struct{}, 1)
|
||||
|
||||
|
|
@ -369,8 +365,6 @@ func (s *Service) runLoop() {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Workspace hydration ---
|
||||
|
||||
func (s *Service) hydrateWorkspaces() {
|
||||
if s.workspaces == nil {
|
||||
s.workspaces = core.NewRegistry[*WorkspaceStatus]()
|
||||
|
|
@ -392,8 +386,6 @@ func (s *Service) hydrateWorkspaces() {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Types ---
|
||||
|
||||
// AgentNotification is the channel payload sent on `agent.status`.
|
||||
//
|
||||
// n := runner.AgentNotification{
|
||||
|
|
@ -419,9 +411,7 @@ type WorkspaceQuery struct {
|
|||
Status string
|
||||
}
|
||||
|
||||
// WorkspaceStatus tracks the state of an agent workspace.
|
||||
//
|
||||
// workspaceStatus := &runner.WorkspaceStatus{Status: "running", Agent: "codex", Repo: "go-io", PID: 12345}
|
||||
// workspaceStatus := &runner.WorkspaceStatus{Status: "running", Agent: "codex", Repo: "go-io", PID: 12345}
|
||||
type WorkspaceStatus struct {
|
||||
Status string `json:"status"`
|
||||
Agent string `json:"agent"`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue