fix(ax): continue AX naming cleanup
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
83703e1d99
commit
f11d1d47a1
23 changed files with 633 additions and 633 deletions
|
|
@ -28,7 +28,7 @@ func main() {
|
|||
// core.Println(c.App().Name) // "core-agent"
|
||||
// core.Println(c.App().Version) // "dev" or linked version
|
||||
func newCoreAgent() *core.Core {
|
||||
c := core.New(
|
||||
coreApp := core.New(
|
||||
core.WithOptions(core.NewOptions(core.Option{Key: "name", Value: "core-agent"})),
|
||||
core.WithService(agentic.ProcessRegister),
|
||||
core.WithService(agentic.Register),
|
||||
|
|
@ -37,15 +37,15 @@ func newCoreAgent() *core.Core {
|
|||
core.WithService(brain.Register),
|
||||
core.WithService(registerMCPService),
|
||||
)
|
||||
c.App().Version = appVersion()
|
||||
coreApp.App().Version = appVersion()
|
||||
|
||||
c.Cli().SetBanner(func(_ *core.Cli) string {
|
||||
return core.Concat("core-agent ", c.App().Version, " — agentic orchestration for the Core ecosystem")
|
||||
coreApp.Cli().SetBanner(func(_ *core.Cli) string {
|
||||
return core.Concat("core-agent ", coreApp.App().Version, " — agentic orchestration for the Core ecosystem")
|
||||
})
|
||||
|
||||
registerAppCommands(c)
|
||||
registerAppCommands(coreApp)
|
||||
|
||||
return c
|
||||
return coreApp
|
||||
}
|
||||
|
||||
// appVersion resolves the build version injected at link time.
|
||||
|
|
@ -69,19 +69,19 @@ func runCoreAgent() error {
|
|||
// runApp starts services, runs the CLI with explicit args, then shuts down.
|
||||
//
|
||||
// err := runApp(c, []string{"version"})
|
||||
func runApp(c *core.Core, cliArgs []string) error {
|
||||
if c == nil {
|
||||
func runApp(coreApp *core.Core, cliArgs []string) error {
|
||||
if coreApp == nil {
|
||||
return core.E("main.runApp", "core is required", nil)
|
||||
}
|
||||
|
||||
defer c.ServiceShutdown(context.Background())
|
||||
defer coreApp.ServiceShutdown(context.Background())
|
||||
|
||||
result := c.ServiceStartup(c.Context(), nil)
|
||||
result := coreApp.ServiceStartup(coreApp.Context(), nil)
|
||||
if !result.OK {
|
||||
return resultError("main.runApp", "startup failed", result)
|
||||
}
|
||||
|
||||
if cli := c.Cli(); cli != nil {
|
||||
if cli := coreApp.Cli(); cli != nil {
|
||||
result = cli.Run(cliArgs...)
|
||||
if !result.OK {
|
||||
return resultError("main.runApp", "cli failed", result)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
// Each handler adapts (ctx, Options) → Result to call the existing MCP tool method.
|
||||
// Registered during OnStartup — the Action registry IS the capability map.
|
||||
//
|
||||
// c.Action("agentic.dispatch").Run(ctx, opts)
|
||||
// c.Action("agentic.dispatch").Run(ctx, options)
|
||||
// c.Actions() // all registered capabilities
|
||||
|
||||
package agentic
|
||||
|
|
@ -21,16 +21,16 @@ import (
|
|||
|
||||
// handleDispatch dispatches a subagent to work on a repo task.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleDispatch(ctx context.Context, opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) handleDispatch(ctx context.Context, options core.Options) core.Result {
|
||||
input := DispatchInput{
|
||||
Repo: opts.String("repo"),
|
||||
Task: opts.String("task"),
|
||||
Agent: opts.String("agent"),
|
||||
Issue: opts.Int("issue"),
|
||||
Repo: options.String("repo"),
|
||||
Task: options.String("task"),
|
||||
Agent: options.String("agent"),
|
||||
Issue: options.Int("issue"),
|
||||
}
|
||||
_, out, err := s.dispatch(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -41,15 +41,15 @@ func (s *PrepSubsystem) handleDispatch(ctx context.Context, opts core.Options) c
|
|||
|
||||
// handlePrep prepares a workspace without dispatching an agent.
|
||||
//
|
||||
// r := 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},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePrep(ctx context.Context, opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) handlePrep(ctx context.Context, options core.Options) core.Result {
|
||||
input := PrepInput{
|
||||
Repo: opts.String("repo"),
|
||||
Org: opts.String("org"),
|
||||
Issue: opts.Int("issue"),
|
||||
Repo: options.String("repo"),
|
||||
Org: options.String("org"),
|
||||
Issue: options.Int("issue"),
|
||||
}
|
||||
_, out, err := s.prepWorkspace(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -60,12 +60,12 @@ func (s *PrepSubsystem) handlePrep(ctx context.Context, opts core.Options) core.
|
|||
|
||||
// handleStatus lists workspace statuses.
|
||||
//
|
||||
// r := c.Action("agentic.status").Run(ctx, core.NewOptions())
|
||||
func (s *PrepSubsystem) handleStatus(ctx context.Context, opts core.Options) core.Result {
|
||||
// result := c.Action("agentic.status").Run(ctx, core.NewOptions())
|
||||
func (s *PrepSubsystem) handleStatus(ctx context.Context, options core.Options) core.Result {
|
||||
input := StatusInput{
|
||||
Workspace: opts.String("workspace"),
|
||||
Limit: opts.Int("limit"),
|
||||
Status: opts.String("status"),
|
||||
Workspace: options.String("workspace"),
|
||||
Limit: options.Int("limit"),
|
||||
Status: options.String("status"),
|
||||
}
|
||||
_, out, err := s.status(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -76,13 +76,13 @@ func (s *PrepSubsystem) handleStatus(ctx context.Context, opts core.Options) cor
|
|||
|
||||
// handleResume resumes a blocked workspace.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleResume(ctx context.Context, opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) handleResume(ctx context.Context, options core.Options) core.Result {
|
||||
input := ResumeInput{
|
||||
Workspace: opts.String("workspace"),
|
||||
Answer: opts.String("answer"),
|
||||
Workspace: options.String("workspace"),
|
||||
Answer: options.String("answer"),
|
||||
}
|
||||
_, out, err := s.resume(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -93,11 +93,11 @@ func (s *PrepSubsystem) handleResume(ctx context.Context, opts core.Options) cor
|
|||
|
||||
// handleScan scans forge repos for actionable issues.
|
||||
//
|
||||
// r := c.Action("agentic.scan").Run(ctx, core.NewOptions())
|
||||
func (s *PrepSubsystem) handleScan(ctx context.Context, opts core.Options) core.Result {
|
||||
// result := c.Action("agentic.scan").Run(ctx, core.NewOptions())
|
||||
func (s *PrepSubsystem) handleScan(ctx context.Context, options core.Options) core.Result {
|
||||
input := ScanInput{
|
||||
Org: opts.String("org"),
|
||||
Limit: opts.Int("limit"),
|
||||
Org: options.String("org"),
|
||||
Limit: options.Int("limit"),
|
||||
}
|
||||
_, out, err := s.scan(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -108,15 +108,15 @@ func (s *PrepSubsystem) handleScan(ctx context.Context, opts core.Options) core.
|
|||
|
||||
// handleWatch watches a workspace for completion.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleWatch(ctx context.Context, opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) handleWatch(ctx context.Context, options core.Options) core.Result {
|
||||
input := WatchInput{
|
||||
PollInterval: opts.Int("poll_interval"),
|
||||
Timeout: opts.Int("timeout"),
|
||||
PollInterval: options.Int("poll_interval"),
|
||||
Timeout: options.Int("timeout"),
|
||||
}
|
||||
if workspace := opts.String("workspace"); workspace != "" {
|
||||
if workspace := options.String("workspace"); workspace != "" {
|
||||
input.Workspaces = []string{workspace}
|
||||
}
|
||||
_, out, err := s.watch(ctx, nil, input)
|
||||
|
|
@ -128,83 +128,83 @@ func (s *PrepSubsystem) handleWatch(ctx context.Context, opts core.Options) core
|
|||
|
||||
// handlePrompt reads an embedded prompt by slug.
|
||||
//
|
||||
// r := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
|
||||
// result := c.Action("agentic.prompt").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "slug", Value: "coding"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePrompt(_ context.Context, opts core.Options) core.Result {
|
||||
return lib.Prompt(opts.String("slug"))
|
||||
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.
|
||||
//
|
||||
// r := c.Action("agentic.task").Run(ctx, core.NewOptions(
|
||||
// result := c.Action("agentic.task").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "slug", Value: "bug-fix"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleTask(_ context.Context, opts core.Options) core.Result {
|
||||
return lib.Task(opts.String("slug"))
|
||||
func (s *PrepSubsystem) handleTask(_ context.Context, options core.Options) core.Result {
|
||||
return lib.Task(options.String("slug"))
|
||||
}
|
||||
|
||||
// handleFlow reads an embedded flow by slug.
|
||||
//
|
||||
// r := c.Action("agentic.flow").Run(ctx, core.NewOptions(
|
||||
// result := c.Action("agentic.flow").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "slug", Value: "go"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleFlow(_ context.Context, opts core.Options) core.Result {
|
||||
return lib.Flow(opts.String("slug"))
|
||||
func (s *PrepSubsystem) handleFlow(_ context.Context, options core.Options) core.Result {
|
||||
return lib.Flow(options.String("slug"))
|
||||
}
|
||||
|
||||
// handlePersona reads an embedded persona by path.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePersona(_ context.Context, opts core.Options) core.Result {
|
||||
return lib.Persona(opts.String("path"))
|
||||
func (s *PrepSubsystem) handlePersona(_ context.Context, options core.Options) core.Result {
|
||||
return lib.Persona(options.String("path"))
|
||||
}
|
||||
|
||||
// --- Pipeline ---
|
||||
|
||||
// handleComplete runs the named completion task.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleComplete(ctx context.Context, opts core.Options) core.Result {
|
||||
return s.Core().Task("agent.completion").Run(ctx, s.Core(), opts)
|
||||
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.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleQA(ctx context.Context, opts core.Options) core.Result {
|
||||
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") {
|
||||
return core.Result{Value: true, OK: true}
|
||||
}
|
||||
wsDir := opts.String("workspace")
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
return core.Result{Value: core.E("agentic.qa", "workspace is required", nil), OK: false}
|
||||
}
|
||||
passed := s.runQA(wsDir)
|
||||
if !passed {
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok {
|
||||
st.Status = "failed"
|
||||
st.Question = "QA check failed — build or tests did not pass"
|
||||
writeStatusResult(wsDir, st)
|
||||
workspaceStatus.Status = "failed"
|
||||
workspaceStatus.Question = "QA check failed — build or tests did not pass"
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Emit QA result for observability (monitor picks this up)
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
repo := ""
|
||||
if ok {
|
||||
repo = st.Repo
|
||||
repo = workspaceStatus.Repo
|
||||
}
|
||||
s.Core().ACTION(messages.QAResult{
|
||||
Workspace: WorkspaceName(wsDir),
|
||||
|
|
@ -217,14 +217,14 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, opts core.Options) core.Re
|
|||
|
||||
// handleAutoPR creates a PR for a completed workspace.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleAutoPR(ctx context.Context, opts core.Options) core.Result {
|
||||
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}
|
||||
}
|
||||
wsDir := opts.String("workspace")
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
return core.Result{Value: core.E("agentic.auto-pr", "workspace is required", nil), OK: false}
|
||||
}
|
||||
|
|
@ -233,13 +233,13 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, opts core.Options) cor
|
|||
// Emit PRCreated for observability
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if ok && st.PRURL != "" {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok && workspaceStatus.PRURL != "" {
|
||||
s.Core().ACTION(messages.PRCreated{
|
||||
Repo: st.Repo,
|
||||
Branch: st.Branch,
|
||||
PRURL: st.PRURL,
|
||||
PRNum: extractPRNumber(st.PRURL),
|
||||
Repo: workspaceStatus.Repo,
|
||||
Branch: workspaceStatus.Branch,
|
||||
PRURL: workspaceStatus.PRURL,
|
||||
PRNum: extractPRNumber(workspaceStatus.PRURL),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -248,14 +248,14 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, opts core.Options) cor
|
|||
|
||||
// handleVerify verifies and auto-merges a PR.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleVerify(ctx context.Context, opts core.Options) core.Result {
|
||||
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}
|
||||
}
|
||||
wsDir := opts.String("workspace")
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
return core.Result{Value: core.E("agentic.verify", "workspace is required", nil), OK: false}
|
||||
}
|
||||
|
|
@ -264,20 +264,20 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, opts core.Options) cor
|
|||
// Emit merge/review events for observability
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok {
|
||||
if st.Status == "merged" {
|
||||
if workspaceStatus.Status == "merged" {
|
||||
s.Core().ACTION(messages.PRMerged{
|
||||
Repo: st.Repo,
|
||||
PRURL: st.PRURL,
|
||||
PRNum: extractPRNumber(st.PRURL),
|
||||
Repo: workspaceStatus.Repo,
|
||||
PRURL: workspaceStatus.PRURL,
|
||||
PRNum: extractPRNumber(workspaceStatus.PRURL),
|
||||
})
|
||||
} else if st.Question != "" {
|
||||
} else if workspaceStatus.Question != "" {
|
||||
s.Core().ACTION(messages.PRNeedsReview{
|
||||
Repo: st.Repo,
|
||||
PRURL: st.PRURL,
|
||||
PRNum: extractPRNumber(st.PRURL),
|
||||
Reason: st.Question,
|
||||
Repo: workspaceStatus.Repo,
|
||||
PRURL: workspaceStatus.PRURL,
|
||||
PRNum: extractPRNumber(workspaceStatus.PRURL),
|
||||
Reason: workspaceStatus.Question,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -287,11 +287,11 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, opts core.Options) cor
|
|||
|
||||
// handleIngest creates issues from agent findings.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleIngest(ctx context.Context, opts core.Options) core.Result {
|
||||
wsDir := opts.String("workspace")
|
||||
func (s *PrepSubsystem) handleIngest(ctx context.Context, options core.Options) core.Result {
|
||||
wsDir := options.String("workspace")
|
||||
if wsDir == "" {
|
||||
return core.Result{Value: core.E("agentic.ingest", "workspace is required", nil), OK: false}
|
||||
}
|
||||
|
|
@ -301,20 +301,20 @@ func (s *PrepSubsystem) handleIngest(ctx context.Context, opts core.Options) cor
|
|||
|
||||
// handlePoke drains the dispatch queue.
|
||||
//
|
||||
// r := c.Action("agentic.poke").Run(ctx, core.NewOptions())
|
||||
func (s *PrepSubsystem) handlePoke(ctx context.Context, opts core.Options) core.Result {
|
||||
// result := c.Action("agentic.poke").Run(ctx, core.NewOptions())
|
||||
func (s *PrepSubsystem) handlePoke(ctx context.Context, _ core.Options) core.Result {
|
||||
s.Poke()
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// handleMirror mirrors agent branches to GitHub.
|
||||
//
|
||||
// r := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
|
||||
// result := c.Action("agentic.mirror").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleMirror(ctx context.Context, opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) handleMirror(ctx context.Context, options core.Options) core.Result {
|
||||
input := MirrorInput{
|
||||
Repo: opts.String("repo"),
|
||||
Repo: options.String("repo"),
|
||||
}
|
||||
_, out, err := s.mirror(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -327,74 +327,74 @@ func (s *PrepSubsystem) handleMirror(ctx context.Context, opts core.Options) cor
|
|||
|
||||
// handleIssueGet retrieves a forge issue.
|
||||
//
|
||||
// r := c.Action("agentic.issue.get").Run(ctx, core.NewOptions(
|
||||
// result := c.Action("agentic.issue.get").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "repo", Value: "go-io"},
|
||||
// core.Option{Key: "number", Value: "42"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleIssueGet(ctx context.Context, opts core.Options) core.Result {
|
||||
return s.cmdIssueGet(opts)
|
||||
func (s *PrepSubsystem) handleIssueGet(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdIssueGet(options)
|
||||
}
|
||||
|
||||
// handleIssueList lists forge issues.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleIssueList(ctx context.Context, opts core.Options) core.Result {
|
||||
return s.cmdIssueList(opts)
|
||||
func (s *PrepSubsystem) handleIssueList(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdIssueList(options)
|
||||
}
|
||||
|
||||
// handleIssueCreate creates a forge issue.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleIssueCreate(ctx context.Context, opts core.Options) core.Result {
|
||||
return s.cmdIssueCreate(opts)
|
||||
func (s *PrepSubsystem) handleIssueCreate(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdIssueCreate(options)
|
||||
}
|
||||
|
||||
// handlePRGet retrieves a forge PR.
|
||||
//
|
||||
// r := c.Action("agentic.pr.get").Run(ctx, core.NewOptions(
|
||||
// result := c.Action("agentic.pr.get").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
// core.Option{Key: "number", Value: "12"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePRGet(ctx context.Context, opts core.Options) core.Result {
|
||||
return s.cmdPRGet(opts)
|
||||
func (s *PrepSubsystem) handlePRGet(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdPRGet(options)
|
||||
}
|
||||
|
||||
// handlePRList lists forge PRs.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePRList(ctx context.Context, opts core.Options) core.Result {
|
||||
return s.cmdPRList(opts)
|
||||
func (s *PrepSubsystem) handlePRList(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdPRList(options)
|
||||
}
|
||||
|
||||
// handlePRMerge merges a forge PR.
|
||||
//
|
||||
// r := c.Action("agentic.pr.merge").Run(ctx, core.NewOptions(
|
||||
// result := c.Action("agentic.pr.merge").Run(ctx, core.NewOptions(
|
||||
// core.Option{Key: "_arg", Value: "go-io"},
|
||||
// core.Option{Key: "number", Value: "12"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handlePRMerge(ctx context.Context, opts core.Options) core.Result {
|
||||
return s.cmdPRMerge(opts)
|
||||
func (s *PrepSubsystem) handlePRMerge(ctx context.Context, options core.Options) core.Result {
|
||||
return s.cmdPRMerge(options)
|
||||
}
|
||||
|
||||
// --- Review ---
|
||||
|
||||
// handleReviewQueue runs CodeRabbit review on a workspace.
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, options core.Options) core.Result {
|
||||
input := ReviewQueueInput{
|
||||
Limit: opts.Int("limit"),
|
||||
Reviewer: opts.String("reviewer"),
|
||||
DryRun: opts.Bool("dry_run"),
|
||||
Limit: options.Int("limit"),
|
||||
Reviewer: options.String("reviewer"),
|
||||
DryRun: options.Bool("dry_run"),
|
||||
}
|
||||
_, out, err := s.reviewQueue(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -407,15 +407,15 @@ func (s *PrepSubsystem) handleReviewQueue(ctx context.Context, opts core.Options
|
|||
|
||||
// handleEpic creates an epic (multi-repo task breakdown).
|
||||
//
|
||||
// r := 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"},
|
||||
// ))
|
||||
func (s *PrepSubsystem) handleEpic(ctx context.Context, opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) handleEpic(ctx context.Context, options core.Options) core.Result {
|
||||
input := EpicInput{
|
||||
Repo: opts.String("repo"),
|
||||
Org: opts.String("org"),
|
||||
Title: opts.String("title"),
|
||||
Body: opts.String("body"),
|
||||
Repo: options.String("repo"),
|
||||
Org: options.String("org"),
|
||||
Title: options.String("title"),
|
||||
Body: options.String("body"),
|
||||
}
|
||||
_, out, err := s.createEpic(ctx, nil, input)
|
||||
if err != nil {
|
||||
|
|
@ -426,20 +426,20 @@ func (s *PrepSubsystem) handleEpic(ctx context.Context, opts core.Options) core.
|
|||
|
||||
// handleWorkspaceQuery answers workspace state queries from Core QUERY calls.
|
||||
//
|
||||
// r := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-42"})
|
||||
// r := c.QUERY(agentic.WorkspaceQuery{Status: "blocked"})
|
||||
func (s *PrepSubsystem) handleWorkspaceQuery(_ *core.Core, q core.Query) core.Result {
|
||||
wq, ok := q.(WorkspaceQuery)
|
||||
// result := c.QUERY(agentic.WorkspaceQuery{Name: "core/go-io/task-42"})
|
||||
// result := c.QUERY(agentic.WorkspaceQuery{Status: "blocked"})
|
||||
func (s *PrepSubsystem) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Result {
|
||||
workspaceQuery, ok := query.(WorkspaceQuery)
|
||||
if !ok {
|
||||
return core.Result{}
|
||||
}
|
||||
if wq.Name != "" {
|
||||
return s.workspaces.Get(wq.Name)
|
||||
if workspaceQuery.Name != "" {
|
||||
return s.workspaces.Get(workspaceQuery.Name)
|
||||
}
|
||||
if wq.Status != "" {
|
||||
if workspaceQuery.Status != "" {
|
||||
var names []string
|
||||
s.workspaces.Each(func(name string, st *WorkspaceStatus) {
|
||||
if st.Status == wq.Status {
|
||||
s.workspaces.Each(func(name string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Status == workspaceQuery.Status {
|
||||
names = append(names, name)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import (
|
|||
// if the agent made any commits beyond the initial clone.
|
||||
func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok || st.Branch == "" || st.Repo == "" {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Branch == "" || workspaceStatus.Repo == "" {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -25,78 +25,78 @@ func (s *PrepSubsystem) autoCreatePR(wsDir string) {
|
|||
// PRs target dev — agents never merge directly to main
|
||||
base := "dev"
|
||||
|
||||
r := process.RunIn(ctx, repoDir, "git", "log", "--oneline", core.Concat("origin/", base, "..HEAD"))
|
||||
if !r.OK {
|
||||
processResult := process.RunIn(ctx, repoDir, "git", "log", "--oneline", core.Concat("origin/", base, "..HEAD"))
|
||||
if !processResult.OK {
|
||||
return
|
||||
}
|
||||
out := core.Trim(r.Value.(string))
|
||||
out := core.Trim(processResult.Value.(string))
|
||||
if out == "" {
|
||||
return
|
||||
}
|
||||
|
||||
commitCount := len(core.Split(out, "\n"))
|
||||
|
||||
org := st.Org
|
||||
org := workspaceStatus.Org
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
|
||||
// Push the branch to forge
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
|
||||
if !process.RunIn(ctx, repoDir, "git", "push", forgeRemote, st.Branch).OK {
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, workspaceStatus.Repo)
|
||||
if !process.RunIn(ctx, repoDir, "git", "push", forgeRemote, workspaceStatus.Branch).OK {
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
st2, ok := workspaceStatusValue(result)
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
st2.Question = "PR push failed"
|
||||
writeStatusResult(wsDir, st2)
|
||||
workspaceStatusUpdate.Question = "PR push failed"
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Create PR via Forge API
|
||||
title := core.Sprintf("[agent/%s] %s", st.Agent, truncate(st.Task, 60))
|
||||
body := s.buildAutoPRBody(st, commitCount)
|
||||
title := core.Sprintf("[agent/%s] %s", workspaceStatus.Agent, truncate(workspaceStatus.Task, 60))
|
||||
body := s.buildAutoPRBody(workspaceStatus, commitCount)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
prURL, _, err := s.forgeCreatePR(ctx, org, st.Repo, st.Branch, base, title, body)
|
||||
prURL, _, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
|
||||
if err != nil {
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
st2, ok := workspaceStatusValue(result)
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
st2.Question = core.Sprintf("PR creation failed: %v", err)
|
||||
writeStatusResult(wsDir, st2)
|
||||
workspaceStatusUpdate.Question = core.Sprintf("PR creation failed: %v", err)
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update status with PR URL
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
st2, ok := workspaceStatusValue(result)
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
st2.PRURL = prURL
|
||||
writeStatusResult(wsDir, st2)
|
||||
workspaceStatusUpdate.PRURL = prURL
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) buildAutoPRBody(st *WorkspaceStatus, commits int) string {
|
||||
func (s *PrepSubsystem) buildAutoPRBody(workspaceStatus *WorkspaceStatus, commits int) string {
|
||||
b := core.NewBuilder()
|
||||
b.WriteString("## Task\n\n")
|
||||
b.WriteString(st.Task)
|
||||
b.WriteString(workspaceStatus.Task)
|
||||
b.WriteString("\n\n")
|
||||
if st.Issue > 0 {
|
||||
b.WriteString(core.Sprintf("Closes #%d\n\n", st.Issue))
|
||||
if workspaceStatus.Issue > 0 {
|
||||
b.WriteString(core.Sprintf("Closes #%d\n\n", workspaceStatus.Issue))
|
||||
}
|
||||
b.WriteString(core.Sprintf("**Agent:** %s\n", st.Agent))
|
||||
b.WriteString(core.Sprintf("**Agent:** %s\n", workspaceStatus.Agent))
|
||||
b.WriteString(core.Sprintf("**Commits:** %d\n", commits))
|
||||
b.WriteString(core.Sprintf("**Branch:** `%s`\n", st.Branch))
|
||||
b.WriteString(core.Sprintf("**Branch:** `%s`\n", workspaceStatus.Branch))
|
||||
b.WriteString("\n---\n")
|
||||
b.WriteString("Auto-created by core-agent dispatch system.\n")
|
||||
b.WriteString("Co-Authored-By: Virgil <virgil@lethean.io>\n")
|
||||
|
|
|
|||
|
|
@ -34,16 +34,16 @@ func (s *PrepSubsystem) commandContext() context.Context {
|
|||
return context.Background()
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdRunTask(opts core.Options) core.Result {
|
||||
return s.runTask(s.commandContext(), opts)
|
||||
func (s *PrepSubsystem) cmdRunTask(options core.Options) core.Result {
|
||||
return s.runTask(s.commandContext(), options)
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) runTask(ctx context.Context, opts core.Options) core.Result {
|
||||
repo := opts.String("repo")
|
||||
agent := opts.String("agent")
|
||||
task := opts.String("task")
|
||||
issueStr := opts.String("issue")
|
||||
org := opts.String("org")
|
||||
func (s *PrepSubsystem) runTask(ctx context.Context, options core.Options) core.Result {
|
||||
repo := options.String("repo")
|
||||
agent := options.String("agent")
|
||||
task := options.String("task")
|
||||
issueStr := options.String("issue")
|
||||
org := options.String("org")
|
||||
|
||||
if repo == "" || task == "" {
|
||||
core.Print(nil, "usage: core-agent run task --repo=<repo> --task=\"...\" --agent=codex [--issue=N] [--org=core]")
|
||||
|
|
@ -98,40 +98,40 @@ func (s *PrepSubsystem) cmdOrchestrator(_ core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdPrep(opts core.Options) core.Result {
|
||||
repo := opts.String("_arg")
|
||||
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=\"...\"")
|
||||
return core.Result{Value: core.E("agentic.cmdPrep", "repo is required", nil), OK: false}
|
||||
}
|
||||
|
||||
input := PrepInput{
|
||||
prepInput := PrepInput{
|
||||
Repo: repo,
|
||||
Org: opts.String("org"),
|
||||
Task: opts.String("task"),
|
||||
Template: opts.String("template"),
|
||||
Persona: opts.String("persona"),
|
||||
DryRun: opts.Bool("dry-run"),
|
||||
Org: options.String("org"),
|
||||
Task: options.String("task"),
|
||||
Template: options.String("template"),
|
||||
Persona: options.String("persona"),
|
||||
DryRun: options.Bool("dry-run"),
|
||||
}
|
||||
|
||||
if v := opts.String("issue"); v != "" {
|
||||
input.Issue = parseIntStr(v)
|
||||
if value := options.String("issue"); value != "" {
|
||||
prepInput.Issue = parseIntStr(value)
|
||||
}
|
||||
if v := opts.String("pr"); v != "" {
|
||||
input.PR = parseIntStr(v)
|
||||
if value := options.String("pr"); value != "" {
|
||||
prepInput.PR = parseIntStr(value)
|
||||
}
|
||||
if v := opts.String("branch"); v != "" {
|
||||
input.Branch = v
|
||||
if value := options.String("branch"); value != "" {
|
||||
prepInput.Branch = value
|
||||
}
|
||||
if v := opts.String("tag"); v != "" {
|
||||
input.Tag = v
|
||||
if value := options.String("tag"); value != "" {
|
||||
prepInput.Tag = value
|
||||
}
|
||||
|
||||
if input.Issue == 0 && input.PR == 0 && input.Branch == "" && input.Tag == "" {
|
||||
input.Branch = "dev"
|
||||
if prepInput.Issue == 0 && prepInput.PR == 0 && prepInput.Branch == "" && prepInput.Tag == "" {
|
||||
prepInput.Branch = "dev"
|
||||
}
|
||||
|
||||
_, out, err := s.TestPrepWorkspace(context.Background(), input)
|
||||
_, out, err := s.TestPrepWorkspace(context.Background(), prepInput)
|
||||
if err != nil {
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
|
|
@ -151,11 +151,11 @@ func (s *PrepSubsystem) cmdPrep(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdStatus(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdStatus(_ core.Options) core.Result {
|
||||
wsRoot := WorkspaceRoot()
|
||||
fsys := s.Core().Fs()
|
||||
r := fsys.List(wsRoot)
|
||||
if !r.OK {
|
||||
listResult := fsys.List(wsRoot)
|
||||
if !listResult.OK {
|
||||
core.Print(nil, "no workspaces found at %s", wsRoot)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
|
@ -172,33 +172,33 @@ func (s *PrepSubsystem) cmdStatus(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdPrompt(opts core.Options) core.Result {
|
||||
repo := opts.String("_arg")
|
||||
func (s *PrepSubsystem) cmdPrompt(options core.Options) core.Result {
|
||||
repo := options.String("_arg")
|
||||
if repo == "" {
|
||||
core.Print(nil, "usage: core-agent prompt <repo> --task=\"...\"")
|
||||
return core.Result{Value: core.E("agentic.cmdPrompt", "repo is required", nil), OK: false}
|
||||
}
|
||||
|
||||
org := opts.String("org")
|
||||
org := options.String("org")
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
task := opts.String("task")
|
||||
task := options.String("task")
|
||||
if task == "" {
|
||||
task = "Review and report findings"
|
||||
}
|
||||
|
||||
repoPath := core.JoinPath(HomeDir(), "Code", org, repo)
|
||||
|
||||
input := PrepInput{
|
||||
prepInput := PrepInput{
|
||||
Repo: repo,
|
||||
Org: org,
|
||||
Task: task,
|
||||
Template: opts.String("template"),
|
||||
Persona: opts.String("persona"),
|
||||
Template: options.String("template"),
|
||||
Persona: options.String("persona"),
|
||||
}
|
||||
|
||||
prompt, memories, consumers := s.TestBuildPrompt(context.Background(), input, "dev", repoPath)
|
||||
prompt, memories, consumers := s.TestBuildPrompt(context.Background(), prepInput, "dev", repoPath)
|
||||
core.Print(nil, "memories: %d", memories)
|
||||
core.Print(nil, "consumers: %d", consumers)
|
||||
core.Print(nil, "")
|
||||
|
|
@ -206,29 +206,29 @@ func (s *PrepSubsystem) cmdPrompt(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdExtract(opts core.Options) core.Result {
|
||||
tmpl := opts.String("_arg")
|
||||
if tmpl == "" {
|
||||
tmpl = "default"
|
||||
func (s *PrepSubsystem) cmdExtract(options core.Options) core.Result {
|
||||
templateName := options.String("_arg")
|
||||
if templateName == "" {
|
||||
templateName = "default"
|
||||
}
|
||||
target := opts.String("target")
|
||||
target := options.String("target")
|
||||
if target == "" {
|
||||
target = core.JoinPath(WorkspaceRoot(), "test-extract")
|
||||
}
|
||||
|
||||
data := &lib.WorkspaceData{
|
||||
workspaceData := &lib.WorkspaceData{
|
||||
Repo: "test-repo",
|
||||
Branch: "dev",
|
||||
Task: "test extraction",
|
||||
Agent: "codex",
|
||||
}
|
||||
|
||||
core.Print(nil, "extracting template %q to %s", tmpl, target)
|
||||
if result := lib.ExtractWorkspace(tmpl, target, data); !result.OK {
|
||||
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 ", tmpl), err), OK: false}
|
||||
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 ", tmpl), nil), OK: false}
|
||||
return core.Result{Value: core.E("agentic.cmdExtract", core.Concat("extract workspace template ", templateName), nil), OK: false}
|
||||
}
|
||||
|
||||
fsys := s.Core().Fs()
|
||||
|
|
|
|||
|
|
@ -71,14 +71,14 @@ func pullRequestAuthor(pr pullRequestView) string {
|
|||
return pr.User.Login
|
||||
}
|
||||
|
||||
// parseForgeArgs extracts org and repo from opts.
|
||||
func parseForgeArgs(opts core.Options) (org, repo string, num int64) {
|
||||
org = opts.String("org")
|
||||
// parseForgeArgs extracts org and repo from options.
|
||||
func parseForgeArgs(options core.Options) (org, repo string, num int64) {
|
||||
org = options.String("org")
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
repo = opts.String("_arg")
|
||||
if v := opts.String("number"); v != "" {
|
||||
repo = options.String("_arg")
|
||||
if v := options.String("number"); v != "" {
|
||||
num, _ = strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
return
|
||||
|
|
@ -100,9 +100,9 @@ func (s *PrepSubsystem) registerForgeCommands() {
|
|||
c.Command("repo/list", core.Command{Description: "List Forge repos for an org", Action: s.cmdRepoList})
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdIssueGet(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdIssueGet(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, num := parseForgeArgs(opts)
|
||||
org, repo, num := parseForgeArgs(options)
|
||||
if repo == "" || num == 0 {
|
||||
core.Print(nil, "usage: core-agent issue get <repo> --number=N [--org=core]")
|
||||
return core.Result{Value: core.E("agentic.cmdIssueGet", "repo and number are required", nil), OK: false}
|
||||
|
|
@ -123,9 +123,9 @@ func (s *PrepSubsystem) cmdIssueGet(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdIssueList(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdIssueList(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, _ := parseForgeArgs(opts)
|
||||
org, repo, _ := parseForgeArgs(options)
|
||||
if repo == "" {
|
||||
core.Print(nil, "usage: core-agent issue list <repo> [--org=core]")
|
||||
return core.Result{Value: core.E("agentic.cmdIssueList", "repo is required", nil), OK: false}
|
||||
|
|
@ -145,10 +145,10 @@ func (s *PrepSubsystem) cmdIssueList(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdIssueComment(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdIssueComment(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, num := parseForgeArgs(opts)
|
||||
body := opts.String("body")
|
||||
org, repo, num := parseForgeArgs(options)
|
||||
body := options.String("body")
|
||||
if repo == "" || num == 0 || body == "" {
|
||||
core.Print(nil, "usage: core-agent issue comment <repo> --number=N --body=\"text\" [--org=core]")
|
||||
return core.Result{Value: core.E("agentic.cmdIssueComment", "repo, number, and body are required", nil), OK: false}
|
||||
|
|
@ -162,15 +162,15 @@ func (s *PrepSubsystem) cmdIssueComment(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdIssueCreate(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdIssueCreate(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, _ := parseForgeArgs(opts)
|
||||
title := opts.String("title")
|
||||
body := opts.String("body")
|
||||
labels := opts.String("labels")
|
||||
milestone := opts.String("milestone")
|
||||
assignee := opts.String("assignee")
|
||||
ref := opts.String("ref")
|
||||
org, repo, _ := parseForgeArgs(options)
|
||||
title := options.String("title")
|
||||
body := options.String("body")
|
||||
labels := options.String("labels")
|
||||
milestone := options.String("milestone")
|
||||
assignee := options.String("assignee")
|
||||
ref := options.String("ref")
|
||||
if repo == "" || title == "" {
|
||||
core.Print(nil, "usage: core-agent issue create <repo> --title=\"...\" [--body=\"...\"] [--labels=\"agentic,bug\"] [--milestone=\"v0.2.0\"] [--assignee=virgil] [--ref=dev] [--org=core]")
|
||||
return core.Result{Value: core.E("agentic.cmdIssueCreate", "repo and title are required", nil), OK: false}
|
||||
|
|
@ -219,9 +219,9 @@ func (s *PrepSubsystem) cmdIssueCreate(opts core.Options) core.Result {
|
|||
return core.Result{Value: issue.Index, OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdPRGet(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdPRGet(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, num := parseForgeArgs(opts)
|
||||
org, repo, num := parseForgeArgs(options)
|
||||
if repo == "" || num == 0 {
|
||||
core.Print(nil, "usage: core-agent pr get <repo> --number=N [--org=core]")
|
||||
return core.Result{Value: core.E("agentic.cmdPRGet", "repo and number are required", nil), OK: false}
|
||||
|
|
@ -245,9 +245,9 @@ func (s *PrepSubsystem) cmdPRGet(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdPRList(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdPRList(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, _ := parseForgeArgs(opts)
|
||||
org, repo, _ := parseForgeArgs(options)
|
||||
if repo == "" {
|
||||
core.Print(nil, "usage: core-agent pr list <repo> [--org=core]")
|
||||
return core.Result{Value: core.E("agentic.cmdPRList", "repo is required", nil), OK: false}
|
||||
|
|
@ -267,10 +267,10 @@ func (s *PrepSubsystem) cmdPRList(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdPRMerge(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdPRMerge(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, num := parseForgeArgs(opts)
|
||||
method := opts.String("method")
|
||||
org, repo, num := parseForgeArgs(options)
|
||||
method := options.String("method")
|
||||
if method == "" {
|
||||
method = "merge"
|
||||
}
|
||||
|
|
@ -286,30 +286,30 @@ func (s *PrepSubsystem) cmdPRMerge(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdRepoGet(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdRepoGet(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org, repo, _ := parseForgeArgs(opts)
|
||||
org, repo, _ := parseForgeArgs(options)
|
||||
if repo == "" {
|
||||
core.Print(nil, "usage: core-agent repo get <repo> [--org=core]")
|
||||
return core.Result{Value: core.E("agentic.cmdRepoGet", "repo is required", nil), OK: false}
|
||||
}
|
||||
r, err := s.forge.Repos.Get(ctx, forge.Params{"owner": org, "repo": repo})
|
||||
repositoryResult, err := s.forge.Repos.Get(ctx, forge.Params{"owner": org, "repo": repo})
|
||||
if err != nil {
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
core.Print(nil, "%s/%s", r.Owner.UserName, r.Name)
|
||||
core.Print(nil, " description: %s", r.Description)
|
||||
core.Print(nil, " default: %s", r.DefaultBranch)
|
||||
core.Print(nil, " private: %v", r.Private)
|
||||
core.Print(nil, " archived: %v", r.Archived)
|
||||
core.Print(nil, " url: %s", r.HTMLURL)
|
||||
core.Print(nil, "%s/%s", repositoryResult.Owner.UserName, repositoryResult.Name)
|
||||
core.Print(nil, " description: %s", repositoryResult.Description)
|
||||
core.Print(nil, " default: %s", repositoryResult.DefaultBranch)
|
||||
core.Print(nil, " private: %v", repositoryResult.Private)
|
||||
core.Print(nil, " archived: %v", repositoryResult.Archived)
|
||||
core.Print(nil, " url: %s", repositoryResult.HTMLURL)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdRepoList(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdRepoList(options core.Options) core.Result {
|
||||
ctx := context.Background()
|
||||
org := opts.String("org")
|
||||
org := options.String("org")
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
|
|
@ -318,12 +318,12 @@ func (s *PrepSubsystem) cmdRepoList(opts core.Options) core.Result {
|
|||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
for _, r := range repos {
|
||||
for _, repository := range repos {
|
||||
archived := ""
|
||||
if r.Archived {
|
||||
if repository.Archived {
|
||||
archived = " (archived)"
|
||||
}
|
||||
core.Print(nil, " %-30s %s%s", r.Name, r.Description, archived)
|
||||
core.Print(nil, " %-30s %s%s", repository.Name, repository.Description, archived)
|
||||
}
|
||||
core.Print(nil, "\n %d repos", len(repos))
|
||||
return core.Result{OK: true}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
|
|||
wsDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(wsDir)
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
core.Print(nil, " %-8s %-8s %-10s %s", st.Status, st.Agent, st.Repo, wsName)
|
||||
core.Print(nil, " %-8s %-8s %-10s %s", workspaceStatus.Status, workspaceStatus.Agent, workspaceStatus.Repo, wsName)
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
|
|
@ -38,10 +38,10 @@ func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
|
||||
func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
|
||||
wsRoot := WorkspaceRoot()
|
||||
fsys := s.Core().Fs()
|
||||
filter := opts.String("_arg")
|
||||
filter := options.String("_arg")
|
||||
if filter == "" {
|
||||
filter = "all"
|
||||
}
|
||||
|
|
@ -53,11 +53,11 @@ func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
|
|||
wsDir := core.PathDir(sf)
|
||||
wsName := WorkspaceName(wsDir)
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
status := st.Status
|
||||
status := workspaceStatus.Status
|
||||
|
||||
switch filter {
|
||||
case "all":
|
||||
|
|
@ -93,8 +93,8 @@ func (s *PrepSubsystem) cmdWorkspaceClean(opts core.Options) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdWorkspaceDispatch(opts core.Options) core.Result {
|
||||
repo := opts.String("_arg")
|
||||
func (s *PrepSubsystem) cmdWorkspaceDispatch(options core.Options) core.Result {
|
||||
repo := options.String("_arg")
|
||||
if repo == "" {
|
||||
core.Print(nil, "usage: core-agent workspace dispatch <repo> --task=\"...\" --issue=N|--pr=N|--branch=X [--agent=codex]")
|
||||
return core.Result{Value: core.E("agentic.cmdWorkspaceDispatch", "repo is required", nil), OK: false}
|
||||
|
|
@ -104,13 +104,13 @@ func (s *PrepSubsystem) cmdWorkspaceDispatch(opts core.Options) core.Result {
|
|||
// not gated by the frozen-queue entitlement.
|
||||
input := DispatchInput{
|
||||
Repo: repo,
|
||||
Task: opts.String("task"),
|
||||
Agent: opts.String("agent"),
|
||||
Org: opts.String("org"),
|
||||
Template: opts.String("template"),
|
||||
Branch: opts.String("branch"),
|
||||
Issue: parseIntStr(opts.String("issue")),
|
||||
PR: parseIntStr(opts.String("pr")),
|
||||
Task: options.String("task"),
|
||||
Agent: options.String("agent"),
|
||||
Org: options.String("org"),
|
||||
Template: options.String("template"),
|
||||
Branch: options.String("branch"),
|
||||
Issue: parseIntStr(options.String("issue")),
|
||||
PR: parseIntStr(options.String("pr")),
|
||||
}
|
||||
_, out, err := s.dispatch(context.Background(), nil, input)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
// workspaceTracker is the interface runner.Service satisfies.
|
||||
// Uses *WorkspaceStatus from agentic — runner imports agentic for the type.
|
||||
type workspaceTracker interface {
|
||||
TrackWorkspace(name string, st any)
|
||||
TrackWorkspace(name string, status any)
|
||||
}
|
||||
|
||||
// DispatchInput is the input for agentic_dispatch.
|
||||
|
|
@ -60,16 +60,16 @@ func (s *PrepSubsystem) registerDispatchTool(server *mcp.Server) {
|
|||
// agentCommand returns the command and args for a given agent type.
|
||||
// Supports model variants: "gemini", "gemini:flash", "codex", "claude", "claude:haiku".
|
||||
func agentCommand(agent, prompt string) (string, []string, error) {
|
||||
r := agentCommandResult(agent, prompt)
|
||||
if !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
commandResult := agentCommandResult(agent, prompt)
|
||||
if !commandResult.OK {
|
||||
err, _ := commandResult.Value.(error)
|
||||
if err == nil {
|
||||
err = core.E("agentCommand", "failed to resolve command", nil)
|
||||
}
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
result, ok := r.Value.(agentCommandResultValue)
|
||||
result, ok := commandResult.Value.(agentCommandResultValue)
|
||||
if !ok {
|
||||
return "", nil, core.E("agentCommand", "invalid command result", nil)
|
||||
}
|
||||
|
|
@ -242,8 +242,8 @@ func agentOutputFile(wsDir, agent string) string {
|
|||
// Returns (status, question) — "completed", "blocked", or "failed".
|
||||
func detectFinalStatus(repoDir string, exitCode int, procStatus string) (string, string) {
|
||||
blockedPath := core.JoinPath(repoDir, "BLOCKED.md")
|
||||
if r := fs.Read(blockedPath); r.OK && core.Trim(r.Value.(string)) != "" {
|
||||
return "blocked", core.Trim(r.Value.(string))
|
||||
if blockedResult := fs.Read(blockedPath); blockedResult.OK && core.Trim(blockedResult.Value.(string)) != "" {
|
||||
return "blocked", core.Trim(blockedResult.Value.(string))
|
||||
}
|
||||
if exitCode != 0 || procStatus == "failed" || procStatus == "killed" {
|
||||
question := ""
|
||||
|
|
@ -283,15 +283,15 @@ func (s *PrepSubsystem) startIssueTracking(wsDir string) {
|
|||
return
|
||||
}
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok || st.Issue == 0 {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Issue == 0 {
|
||||
return
|
||||
}
|
||||
org := st.Org
|
||||
org := workspaceStatus.Org
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
s.forge.Issues.StartStopwatch(context.Background(), org, st.Repo, int64(st.Issue))
|
||||
s.forge.Issues.StartStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue))
|
||||
}
|
||||
|
||||
// stopIssueTracking stops a Forge stopwatch on the workspace's issue.
|
||||
|
|
@ -300,25 +300,25 @@ func (s *PrepSubsystem) stopIssueTracking(wsDir string) {
|
|||
return
|
||||
}
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok || st.Issue == 0 {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Issue == 0 {
|
||||
return
|
||||
}
|
||||
org := st.Org
|
||||
org := workspaceStatus.Org
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
s.forge.Issues.StopStopwatch(context.Background(), org, st.Repo, int64(st.Issue))
|
||||
s.forge.Issues.StopStopwatch(context.Background(), org, workspaceStatus.Repo, int64(workspaceStatus.Issue))
|
||||
}
|
||||
|
||||
// broadcastStart emits IPC + audit events for agent start.
|
||||
func (s *PrepSubsystem) broadcastStart(agent, wsDir string) {
|
||||
wsName := WorkspaceName(wsDir)
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
repo := ""
|
||||
if ok {
|
||||
repo = st.Repo
|
||||
repo = workspaceStatus.Repo
|
||||
}
|
||||
if s.ServiceRuntime != nil {
|
||||
s.Core().ACTION(messages.AgentStarted{
|
||||
|
|
@ -334,10 +334,10 @@ func (s *PrepSubsystem) broadcastComplete(agent, wsDir, finalStatus string) {
|
|||
emitCompletionEvent(agent, wsName, finalStatus)
|
||||
if s.ServiceRuntime != nil {
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
repo := ""
|
||||
if ok {
|
||||
repo = st.Repo
|
||||
repo = workspaceStatus.Repo
|
||||
}
|
||||
s.Core().ACTION(messages.AgentCompleted{
|
||||
Agent: agent, Repo: repo,
|
||||
|
|
@ -359,16 +359,16 @@ func (s *PrepSubsystem) onAgentComplete(agent, wsDir, outputFile string, exitCod
|
|||
|
||||
// Update workspace status (disk + registry)
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if ok {
|
||||
st.Status = finalStatus
|
||||
st.PID = 0
|
||||
st.Question = question
|
||||
writeStatusResult(wsDir, st)
|
||||
s.TrackWorkspace(WorkspaceName(wsDir), st)
|
||||
workspaceStatus.Status = finalStatus
|
||||
workspaceStatus.PID = 0
|
||||
workspaceStatus.Question = question
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
s.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
|
||||
// Rate-limit tracking
|
||||
s.trackFailureRate(agent, finalStatus, st.StartedAt)
|
||||
s.trackFailureRate(agent, finalStatus, workspaceStatus.StartedAt)
|
||||
}
|
||||
|
||||
// Forge time tracking
|
||||
|
|
@ -380,7 +380,7 @@ func (s *PrepSubsystem) onAgentComplete(agent, wsDir, outputFile string, exitCod
|
|||
// Run completion pipeline via PerformAsync for successful agents.
|
||||
// Gets ActionTaskStarted/Completed broadcasts + WaitGroup integration for graceful shutdown.
|
||||
//
|
||||
// c.PerformAsync("agentic.complete", opts) → runs agent.completion Task in background
|
||||
// c.PerformAsync("agentic.complete", options) → runs agent.completion Task in background
|
||||
if finalStatus == "completed" && s.ServiceRuntime != nil {
|
||||
s.Core().PerformAsync("agentic.complete", core.NewOptions(
|
||||
core.Option{Key: "workspace", Value: wsDir},
|
||||
|
|
@ -568,13 +568,13 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
// Step 2: Ask runner service for permission (frozen + concurrency check).
|
||||
// Runner owns the gate — agentic owns the spawn.
|
||||
if s.ServiceRuntime != nil {
|
||||
r := s.Core().Action("runner.dispatch").Run(ctx, core.NewOptions(
|
||||
dispatchResult := s.Core().Action("runner.dispatch").Run(ctx, core.NewOptions(
|
||||
core.Option{Key: "agent", Value: input.Agent},
|
||||
core.Option{Key: "repo", Value: input.Repo},
|
||||
))
|
||||
if !r.OK {
|
||||
if !dispatchResult.OK {
|
||||
// Runner denied — queue it
|
||||
st := &WorkspaceStatus{
|
||||
workspaceStatus := &WorkspaceStatus{
|
||||
Status: "queued",
|
||||
Agent: input.Agent,
|
||||
Repo: input.Repo,
|
||||
|
|
@ -584,9 +584,9 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
StartedAt: time.Now(),
|
||||
Runs: 0,
|
||||
}
|
||||
writeStatusResult(wsDir, st)
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok {
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), st)
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
}
|
||||
return nil, DispatchOutput{
|
||||
Success: true,
|
||||
|
|
@ -604,7 +604,7 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
return nil, DispatchOutput{}, err
|
||||
}
|
||||
|
||||
st := &WorkspaceStatus{
|
||||
workspaceStatus := &WorkspaceStatus{
|
||||
Status: "running",
|
||||
Agent: input.Agent,
|
||||
Repo: input.Repo,
|
||||
|
|
@ -616,11 +616,11 @@ func (s *PrepSubsystem) dispatch(ctx context.Context, req *mcp.CallToolRequest,
|
|||
StartedAt: time.Now(),
|
||||
Runs: 1,
|
||||
}
|
||||
writeStatusResult(wsDir, st)
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
// Track in runner's registry (runner owns workspace state)
|
||||
if s.ServiceRuntime != nil {
|
||||
if runnerSvc, ok := core.ServiceFor[workspaceTracker](s.Core(), "runner"); ok {
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), st)
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,15 +39,15 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res
|
|||
}
|
||||
// Update status with real PID
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
st.PID = pid
|
||||
st.ProcessID = processID
|
||||
writeStatusResult(wsDir, st)
|
||||
workspaceStatus.PID = pid
|
||||
workspaceStatus.ProcessID = processID
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
if runnerSvc, ok := core.ServiceFor[workspaceTracker](c, "runner"); ok {
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), st)
|
||||
runnerSvc.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
}
|
||||
}
|
||||
_ = outputFile
|
||||
|
|
@ -59,8 +59,8 @@ func (s *PrepSubsystem) HandleIPCEvents(c *core.Core, msg core.Message) core.Res
|
|||
// SpawnFromQueue spawns an agent in a pre-prepped workspace.
|
||||
// Called by runner.Service via ServiceFor interface matching.
|
||||
//
|
||||
// r := prep.SpawnFromQueue("codex", prompt, wsDir)
|
||||
// pid := r.Value.(int)
|
||||
// spawnResult := prep.SpawnFromQueue("codex", prompt, wsDir)
|
||||
// pid := spawnResult.Value.(int)
|
||||
func (s *PrepSubsystem) SpawnFromQueue(agent, prompt, wsDir string) core.Result {
|
||||
pid, _, _, err := s.spawnAgent(agent, prompt, wsDir)
|
||||
if err != nil {
|
||||
|
|
@ -88,12 +88,12 @@ func resolveWorkspace(name string) string {
|
|||
func findWorkspaceByPR(repo, branch string) string {
|
||||
for _, path := range WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(path)
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
statusResult := ReadStatusResult(wsDir)
|
||||
workspaceStatus, ok := workspaceStatusValue(statusResult)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if st.Repo == repo && st.Branch == branch {
|
||||
if workspaceStatus.Repo == repo && workspaceStatus.Branch == branch {
|
||||
return wsDir
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,25 +60,25 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
|
||||
// Read workspace status for repo, branch, issue context
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
err, _ := result.Value.(error)
|
||||
return nil, CreatePROutput{}, core.E("createPR", "no status.json", err)
|
||||
}
|
||||
|
||||
if st.Branch == "" {
|
||||
if workspaceStatus.Branch == "" {
|
||||
process := s.Core().Process()
|
||||
r := process.RunIn(ctx, repoDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if !r.OK {
|
||||
result := process.RunIn(ctx, repoDir, "git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if !result.OK {
|
||||
return nil, CreatePROutput{}, core.E("createPR", "failed to detect branch", nil)
|
||||
}
|
||||
st.Branch = core.Trim(r.Value.(string))
|
||||
if st.Branch == "" {
|
||||
workspaceStatus.Branch = core.Trim(result.Value.(string))
|
||||
if workspaceStatus.Branch == "" {
|
||||
return nil, CreatePROutput{}, core.E("createPR", "failed to detect branch", nil)
|
||||
}
|
||||
}
|
||||
|
||||
org := st.Org
|
||||
org := workspaceStatus.Org
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
|
|
@ -90,48 +90,48 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
// Build PR title
|
||||
title := input.Title
|
||||
if title == "" {
|
||||
title = st.Task
|
||||
title = workspaceStatus.Task
|
||||
}
|
||||
if title == "" {
|
||||
title = core.Sprintf("Agent work on %s", st.Branch)
|
||||
title = core.Sprintf("Agent work on %s", workspaceStatus.Branch)
|
||||
}
|
||||
|
||||
// Build PR body
|
||||
body := input.Body
|
||||
if body == "" {
|
||||
body = s.buildPRBody(st)
|
||||
body = s.buildPRBody(workspaceStatus)
|
||||
}
|
||||
|
||||
if input.DryRun {
|
||||
return nil, CreatePROutput{
|
||||
Success: true,
|
||||
Title: title,
|
||||
Branch: st.Branch,
|
||||
Repo: st.Repo,
|
||||
Branch: workspaceStatus.Branch,
|
||||
Repo: workspaceStatus.Repo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Push branch to Forge (origin is the local clone, not Forge)
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, st.Repo)
|
||||
r := s.Core().Process().RunIn(ctx, repoDir, "git", "push", forgeRemote, st.Branch)
|
||||
if !r.OK {
|
||||
return nil, CreatePROutput{}, core.E("createPR", core.Concat("git push failed: ", r.Value.(string)), nil)
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, workspaceStatus.Repo)
|
||||
pushResult := s.Core().Process().RunIn(ctx, repoDir, "git", "push", forgeRemote, workspaceStatus.Branch)
|
||||
if !pushResult.OK {
|
||||
return nil, CreatePROutput{}, core.E("createPR", core.Concat("git push failed: ", pushResult.Value.(string)), nil)
|
||||
}
|
||||
|
||||
// Create PR via Forge API
|
||||
prURL, prNum, err := s.forgeCreatePR(ctx, org, st.Repo, st.Branch, base, title, body)
|
||||
prURL, prNum, err := s.forgeCreatePR(ctx, org, workspaceStatus.Repo, workspaceStatus.Branch, base, title, body)
|
||||
if err != nil {
|
||||
return nil, CreatePROutput{}, core.E("createPR", "failed to create PR", err)
|
||||
}
|
||||
|
||||
// Update status with PR URL
|
||||
st.PRURL = prURL
|
||||
writeStatusResult(wsDir, st)
|
||||
workspaceStatus.PRURL = prURL
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
|
||||
// Comment on issue if tracked
|
||||
if st.Issue > 0 {
|
||||
if workspaceStatus.Issue > 0 {
|
||||
comment := core.Sprintf("Pull request created: %s", prURL)
|
||||
s.commentOnIssue(ctx, org, st.Repo, st.Issue, comment)
|
||||
s.commentOnIssue(ctx, org, workspaceStatus.Repo, workspaceStatus.Issue, comment)
|
||||
}
|
||||
|
||||
return nil, CreatePROutput{
|
||||
|
|
@ -139,24 +139,24 @@ func (s *PrepSubsystem) createPR(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
PRURL: prURL,
|
||||
PRNum: prNum,
|
||||
Title: title,
|
||||
Branch: st.Branch,
|
||||
Repo: st.Repo,
|
||||
Branch: workspaceStatus.Branch,
|
||||
Repo: workspaceStatus.Repo,
|
||||
Pushed: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) buildPRBody(st *WorkspaceStatus) string {
|
||||
func (s *PrepSubsystem) buildPRBody(workspaceStatus *WorkspaceStatus) string {
|
||||
b := core.NewBuilder()
|
||||
b.WriteString("## Summary\n\n")
|
||||
if st.Task != "" {
|
||||
b.WriteString(st.Task)
|
||||
if workspaceStatus.Task != "" {
|
||||
b.WriteString(workspaceStatus.Task)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
if st.Issue > 0 {
|
||||
b.WriteString(core.Sprintf("Closes #%d\n\n", st.Issue))
|
||||
if workspaceStatus.Issue > 0 {
|
||||
b.WriteString(core.Sprintf("Closes #%d\n\n", workspaceStatus.Issue))
|
||||
}
|
||||
b.WriteString(core.Sprintf("**Agent:** %s\n", st.Agent))
|
||||
b.WriteString(core.Sprintf("**Runs:** %d\n", st.Runs))
|
||||
b.WriteString(core.Sprintf("**Agent:** %s\n", workspaceStatus.Agent))
|
||||
b.WriteString(core.Sprintf("**Runs:** %d\n", workspaceStatus.Runs))
|
||||
b.WriteString("\n---\n*Created by agentic dispatch*\n")
|
||||
return b.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ func ProcessRegister(c *core.Core) core.Result {
|
|||
if !ok {
|
||||
return core.Result{Value: core.E("agentic.ProcessRegister", "unexpected process service type", nil), OK: false}
|
||||
}
|
||||
if r := c.RegisterService("process", service); !r.OK {
|
||||
return r
|
||||
if registerResult := c.RegisterService("process", service); !registerResult.OK {
|
||||
return registerResult
|
||||
}
|
||||
|
||||
handlers := &processActionHandlers{service: service}
|
||||
|
|
@ -49,12 +49,12 @@ func ProcessRegister(c *core.Core) core.Result {
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func (h *processActionHandlers) handleRun(ctx context.Context, opts core.Options) core.Result {
|
||||
func (h *processActionHandlers) handleRun(ctx context.Context, options core.Options) core.Result {
|
||||
output, err := h.service.RunWithOptions(ctx, process.RunOptions{
|
||||
Command: opts.String("command"),
|
||||
Args: optionStrings(opts, "args"),
|
||||
Dir: opts.String("dir"),
|
||||
Env: optionStrings(opts, "env"),
|
||||
Command: options.String("command"),
|
||||
Args: optionStrings(options, "args"),
|
||||
Dir: options.String("dir"),
|
||||
Env: optionStrings(options, "env"),
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
|
|
@ -62,13 +62,13 @@ func (h *processActionHandlers) handleRun(ctx context.Context, opts core.Options
|
|||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
func (h *processActionHandlers) handleStart(ctx context.Context, opts core.Options) core.Result {
|
||||
func (h *processActionHandlers) handleStart(ctx context.Context, options core.Options) core.Result {
|
||||
proc, err := h.service.StartWithOptions(ctx, process.RunOptions{
|
||||
Command: opts.String("command"),
|
||||
Args: optionStrings(opts, "args"),
|
||||
Dir: opts.String("dir"),
|
||||
Env: optionStrings(opts, "env"),
|
||||
Detach: opts.Bool("detach"),
|
||||
Command: options.String("command"),
|
||||
Args: optionStrings(options, "args"),
|
||||
Dir: options.String("dir"),
|
||||
Env: optionStrings(options, "env"),
|
||||
Detach: options.Bool("detach"),
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
|
|
@ -76,8 +76,8 @@ func (h *processActionHandlers) handleStart(ctx context.Context, opts core.Optio
|
|||
return core.Result{Value: proc, OK: true}
|
||||
}
|
||||
|
||||
func (h *processActionHandlers) handleKill(_ context.Context, opts core.Options) core.Result {
|
||||
id := opts.String("id")
|
||||
func (h *processActionHandlers) handleKill(_ context.Context, options core.Options) core.Result {
|
||||
id := options.String("id")
|
||||
if id == "" {
|
||||
return core.Result{Value: core.E("agentic.ProcessRegister", "process id is required", nil), OK: false}
|
||||
}
|
||||
|
|
@ -87,12 +87,12 @@ func (h *processActionHandlers) handleKill(_ context.Context, opts core.Options)
|
|||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
func optionStrings(opts core.Options, key string) []string {
|
||||
r := opts.Get(key)
|
||||
if !r.OK {
|
||||
func optionStrings(options core.Options, key string) []string {
|
||||
result := options.Get(key)
|
||||
if !result.OK {
|
||||
return nil
|
||||
}
|
||||
switch values := r.Value.(type) {
|
||||
switch values := result.Value.(type) {
|
||||
case []string:
|
||||
return values
|
||||
case []any:
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ func (s *PrepSubsystem) loadAgentsConfig() *AgentsConfig {
|
|||
}
|
||||
|
||||
for _, path := range paths {
|
||||
r := fs.Read(path)
|
||||
if !r.OK {
|
||||
readResult := fs.Read(path)
|
||||
if !readResult.OK {
|
||||
continue
|
||||
}
|
||||
var config AgentsConfig
|
||||
if err := yaml.Unmarshal([]byte(r.Value.(string)), &config); err != nil {
|
||||
if err := yaml.Unmarshal([]byte(readResult.Value.(string)), &config); err != nil {
|
||||
continue
|
||||
}
|
||||
return &config
|
||||
|
|
@ -169,8 +169,8 @@ func (s *PrepSubsystem) countRunningByAgent(agent string) int {
|
|||
}
|
||||
if s.workspaces != nil && s.workspaces.Len() > 0 {
|
||||
count := 0
|
||||
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
|
||||
if st.Status == "running" && baseAgent(st.Agent) == agent && ProcessAlive(runtime, st.ProcessID, st.PID) {
|
||||
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Status == "running" && baseAgent(workspaceStatus.Agent) == agent && ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
|
@ -187,14 +187,14 @@ func (s *PrepSubsystem) countRunningByAgentDisk(runtime *core.Core, agent string
|
|||
count := 0
|
||||
for _, statusPath := range WorkspaceStatusPaths() {
|
||||
result := ReadStatusResult(core.PathDir(statusPath))
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok || st.Status != "running" {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Status != "running" {
|
||||
continue
|
||||
}
|
||||
if baseAgent(st.Agent) != agent {
|
||||
if baseAgent(workspaceStatus.Agent) != agent {
|
||||
continue
|
||||
}
|
||||
if ProcessAlive(runtime, st.ProcessID, st.PID) {
|
||||
if ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
|
@ -212,8 +212,8 @@ func (s *PrepSubsystem) countRunningByModel(agent string) int {
|
|||
}
|
||||
if s.workspaces != nil && s.workspaces.Len() > 0 {
|
||||
count := 0
|
||||
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
|
||||
if st.Status == "running" && st.Agent == agent && ProcessAlive(runtime, st.ProcessID, st.PID) {
|
||||
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Status == "running" && workspaceStatus.Agent == agent && ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
|
@ -230,14 +230,14 @@ func (s *PrepSubsystem) countRunningByModelDisk(runtime *core.Core, agent string
|
|||
count := 0
|
||||
for _, statusPath := range WorkspaceStatusPaths() {
|
||||
result := ReadStatusResult(core.PathDir(statusPath))
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok || st.Status != "running" {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Status != "running" {
|
||||
continue
|
||||
}
|
||||
if st.Agent != agent {
|
||||
if workspaceStatus.Agent != agent {
|
||||
continue
|
||||
}
|
||||
if ProcessAlive(runtime, st.ProcessID, st.PID) {
|
||||
if ProcessAlive(runtime, workspaceStatus.ProcessID, workspaceStatus.PID) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
|
@ -255,9 +255,9 @@ func baseAgent(agent string) string {
|
|||
func (s *PrepSubsystem) canDispatchAgent(agent string) bool {
|
||||
var concurrency map[string]ConcurrencyLimit
|
||||
if s.ServiceRuntime != nil {
|
||||
r := s.Core().Config().Get("agents.concurrency")
|
||||
if r.OK {
|
||||
concurrency, _ = r.Value.(map[string]ConcurrencyLimit)
|
||||
configurationResult := s.Core().Config().Get("agents.concurrency")
|
||||
if configurationResult.OK {
|
||||
concurrency, _ = configurationResult.Value.(map[string]ConcurrencyLimit)
|
||||
}
|
||||
}
|
||||
if concurrency == nil {
|
||||
|
|
@ -329,45 +329,45 @@ func (s *PrepSubsystem) drainOne() bool {
|
|||
for _, statusPath := range WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(statusPath)
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok || st.Status != "queued" {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.Status != "queued" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !s.canDispatchAgent(st.Agent) {
|
||||
if !s.canDispatchAgent(workspaceStatus.Agent) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if agent pool is in rate-limit backoff
|
||||
pool := baseAgent(st.Agent)
|
||||
pool := baseAgent(workspaceStatus.Agent)
|
||||
if until, ok := s.backoff[pool]; ok && time.Now().Before(until) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply rate delay before spawning
|
||||
delay := s.delayForAgent(st.Agent)
|
||||
delay := s.delayForAgent(workspaceStatus.Agent)
|
||||
if delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
// Re-check concurrency after delay (another task may have started)
|
||||
if !s.canDispatchAgent(st.Agent) {
|
||||
if !s.canDispatchAgent(workspaceStatus.Agent) {
|
||||
continue
|
||||
}
|
||||
|
||||
prompt := core.Concat("TASK: ", st.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
|
||||
prompt := core.Concat("TASK: ", workspaceStatus.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
|
||||
|
||||
pid, processID, _, err := s.spawnAgent(st.Agent, prompt, wsDir)
|
||||
pid, processID, _, err := s.spawnAgent(workspaceStatus.Agent, prompt, wsDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
st.Status = "running"
|
||||
st.PID = pid
|
||||
st.ProcessID = processID
|
||||
st.Runs++
|
||||
writeStatusResult(wsDir, st)
|
||||
s.TrackWorkspace(WorkspaceName(wsDir), st)
|
||||
workspaceStatus.Status = "running"
|
||||
workspaceStatus.PID = pid
|
||||
workspaceStatus.ProcessID = processID
|
||||
workspaceStatus.Runs++
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
s.TrackWorkspace(WorkspaceName(wsDir), workspaceStatus)
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,18 +53,18 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
|
||||
// Read current status
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
err, _ := result.Value.(error)
|
||||
return nil, ResumeOutput{}, core.E("resume", "no status.json in workspace", err)
|
||||
}
|
||||
|
||||
if st.Status != "blocked" && st.Status != "failed" && st.Status != "completed" {
|
||||
return nil, ResumeOutput{}, core.E("resume", core.Concat("workspace is ", st.Status, ", not resumable (must be blocked, failed, or completed)"), nil)
|
||||
if workspaceStatus.Status != "blocked" && workspaceStatus.Status != "failed" && workspaceStatus.Status != "completed" {
|
||||
return nil, ResumeOutput{}, core.E("resume", core.Concat("workspace is ", workspaceStatus.Status, ", not resumable (must be blocked, failed, or completed)"), nil)
|
||||
}
|
||||
|
||||
// Determine agent
|
||||
agent := st.Agent
|
||||
agent := workspaceStatus.Agent
|
||||
if input.Agent != "" {
|
||||
agent = input.Agent
|
||||
}
|
||||
|
|
@ -73,14 +73,14 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
if input.Answer != "" {
|
||||
answerPath := workspaceAnswerPath(wsDir)
|
||||
content := core.Sprintf("# Answer\n\n%s\n", input.Answer)
|
||||
if r := fs.Write(answerPath, content); !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
if writeResult := fs.Write(answerPath, content); !writeResult.OK {
|
||||
err, _ := writeResult.Value.(error)
|
||||
return nil, ResumeOutput{}, core.E("resume", "failed to write ANSWER.md", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Build resume prompt — inline the task and answer, no file references
|
||||
prompt := core.Concat("You are resuming previous work.\n\nORIGINAL TASK:\n", st.Task)
|
||||
prompt := core.Concat("You are resuming previous work.\n\nORIGINAL TASK:\n", workspaceStatus.Task)
|
||||
if input.Answer != "" {
|
||||
prompt = core.Concat(prompt, "\n\nANSWER TO YOUR QUESTION:\n", input.Answer)
|
||||
}
|
||||
|
|
@ -102,12 +102,12 @@ func (s *PrepSubsystem) resume(ctx context.Context, _ *mcp.CallToolRequest, inpu
|
|||
}
|
||||
|
||||
// Update status
|
||||
st.Status = "running"
|
||||
st.PID = pid
|
||||
st.ProcessID = processID
|
||||
st.Runs++
|
||||
st.Question = ""
|
||||
writeStatusResult(wsDir, st)
|
||||
workspaceStatus.Status = "running"
|
||||
workspaceStatus.PID = pid
|
||||
workspaceStatus.ProcessID = processID
|
||||
workspaceStatus.Runs++
|
||||
workspaceStatus.Question = ""
|
||||
writeStatusResult(wsDir, workspaceStatus)
|
||||
|
||||
return nil, ResumeOutput{
|
||||
Success: true,
|
||||
|
|
|
|||
|
|
@ -18,18 +18,18 @@ import (
|
|||
// agentic_dispatch repo=go-crypt template=verify persona=engineering/engineering-security-engineer
|
||||
func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
|
||||
result := ReadStatusResult(wsDir)
|
||||
st, ok := workspaceStatusValue(result)
|
||||
if !ok || st.PRURL == "" || st.Repo == "" {
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
if !ok || workspaceStatus.PRURL == "" || workspaceStatus.Repo == "" {
|
||||
return
|
||||
}
|
||||
|
||||
repoDir := WorkspaceRepoDir(wsDir)
|
||||
org := st.Org
|
||||
org := workspaceStatus.Org
|
||||
if org == "" {
|
||||
org = "core"
|
||||
}
|
||||
|
||||
prNum := extractPRNumber(st.PRURL)
|
||||
prNum := extractPRNumber(workspaceStatus.PRURL)
|
||||
if prNum == 0 {
|
||||
return
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
|
|||
}
|
||||
|
||||
// Attempt 1: run tests and try to merge
|
||||
mergeOutcome := s.attemptVerifyAndMerge(repoDir, org, st.Repo, st.Branch, prNum)
|
||||
mergeOutcome := s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, prNum)
|
||||
if mergeOutcome == mergeSuccess {
|
||||
markMerged()
|
||||
return
|
||||
|
|
@ -55,8 +55,8 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
|
|||
|
||||
// Attempt 2: rebase onto main and retry
|
||||
if mergeOutcome == mergeConflict || mergeOutcome == testFailed {
|
||||
if s.rebaseBranch(repoDir, st.Branch) {
|
||||
if s.attemptVerifyAndMerge(repoDir, org, st.Repo, st.Branch, prNum) == mergeSuccess {
|
||||
if s.rebaseBranch(repoDir, workspaceStatus.Branch) {
|
||||
if s.attemptVerifyAndMerge(repoDir, org, workspaceStatus.Repo, workspaceStatus.Branch, prNum) == mergeSuccess {
|
||||
markMerged()
|
||||
return
|
||||
}
|
||||
|
|
@ -64,15 +64,15 @@ func (s *PrepSubsystem) autoVerifyAndMerge(wsDir string) {
|
|||
}
|
||||
|
||||
// Both attempts failed — flag for human review
|
||||
s.flagForReview(org, st.Repo, prNum, mergeOutcome)
|
||||
s.flagForReview(org, workspaceStatus.Repo, prNum, mergeOutcome)
|
||||
|
||||
if result := ReadStatusResult(wsDir); result.OK {
|
||||
st2, ok := workspaceStatusValue(result)
|
||||
workspaceStatusUpdate, ok := workspaceStatusValue(result)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
st2.Question = "Flagged for review — auto-merge failed after retry"
|
||||
writeStatusResult(wsDir, st2)
|
||||
workspaceStatusUpdate.Question = "Flagged for review — auto-merge failed after retry"
|
||||
writeStatusResult(wsDir, workspaceStatusUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ func (s *PrepSubsystem) attemptVerifyAndMerge(repoDir, org, repo, branch string,
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if r := s.forgeMergePR(ctx, org, repo, prNum); !r.OK {
|
||||
if mergeResult := s.forgeMergePR(ctx, org, repo, prNum); !mergeResult.OK {
|
||||
comment := core.Sprintf("## Tests Passed — Merge Failed\n\n`%s` passed but merge failed", testResult.testCmd)
|
||||
s.commentOnIssue(context.Background(), org, repo, prNum, comment)
|
||||
return mergeConflict
|
||||
|
|
@ -126,14 +126,14 @@ func (s *PrepSubsystem) rebaseBranch(repoDir, branch string) bool {
|
|||
}
|
||||
|
||||
result := ReadStatusResult(core.PathDir(repoDir))
|
||||
st, ok := workspaceStatusValue(result)
|
||||
workspaceStatus, ok := workspaceStatusValue(result)
|
||||
org := "core"
|
||||
repo := ""
|
||||
if ok {
|
||||
if st.Org != "" {
|
||||
org = st.Org
|
||||
if workspaceStatus.Org != "" {
|
||||
org = workspaceStatus.Org
|
||||
}
|
||||
repo = st.Repo
|
||||
repo = workspaceStatus.Repo
|
||||
}
|
||||
forgeRemote := core.Sprintf("ssh://git@forge.lthn.ai:2223/%s/%s.git", org, repo)
|
||||
return process.RunIn(ctx, repoDir, "git", "push", "--force-with-lease", forgeRemote, branch).OK
|
||||
|
|
@ -176,8 +176,8 @@ func (s *PrepSubsystem) ensureLabel(ctx context.Context, org, repo, name, colour
|
|||
// getLabelID fetches the ID of a label by name.
|
||||
func (s *PrepSubsystem) getLabelID(ctx context.Context, org, repo, name string) int {
|
||||
url := core.Sprintf("%s/api/v1/repos/%s/%s/labels", s.forgeURL, org, repo)
|
||||
r := HTTPGet(ctx, url, s.forgeToken, "token")
|
||||
if !r.OK {
|
||||
getResult := HTTPGet(ctx, url, s.forgeToken, "token")
|
||||
if !getResult.OK {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ func (s *PrepSubsystem) getLabelID(ctx context.Context, org, repo, name string)
|
|||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
core.JSONUnmarshalString(r.Value.(string), &labels)
|
||||
core.JSONUnmarshalString(getResult.Value.(string), &labels)
|
||||
for _, l := range labels {
|
||||
if l.Name == name {
|
||||
return l.ID
|
||||
|
|
@ -202,12 +202,12 @@ type verifyResult struct {
|
|||
testCmd string
|
||||
}
|
||||
|
||||
func resultText(r core.Result) string {
|
||||
if text, ok := r.Value.(string); ok {
|
||||
func resultText(result core.Result) string {
|
||||
if text, ok := result.Value.(string); ok {
|
||||
return text
|
||||
}
|
||||
if r.Value != nil {
|
||||
return core.Sprint(r.Value)
|
||||
if result.Value != nil {
|
||||
return core.Sprint(result.Value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -229,52 +229,52 @@ func (s *PrepSubsystem) runVerification(repoDir string) verifyResult {
|
|||
func (s *PrepSubsystem) runGoTests(repoDir string) verifyResult {
|
||||
ctx := context.Background()
|
||||
process := s.Core().Process()
|
||||
r := process.RunWithEnv(ctx, repoDir, []string{"GOWORK=off"}, "go", "test", "./...", "-count=1", "-timeout", "120s")
|
||||
out := resultText(r)
|
||||
processResult := process.RunWithEnv(ctx, repoDir, []string{"GOWORK=off"}, "go", "test", "./...", "-count=1", "-timeout", "120s")
|
||||
out := resultText(processResult)
|
||||
exitCode := 0
|
||||
if !r.OK {
|
||||
if !processResult.OK {
|
||||
exitCode = 1
|
||||
}
|
||||
return verifyResult{passed: r.OK, output: out, exitCode: exitCode, testCmd: "go test ./..."}
|
||||
return verifyResult{passed: processResult.OK, output: out, exitCode: exitCode, testCmd: "go test ./..."}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) runPHPTests(repoDir string) verifyResult {
|
||||
ctx := context.Background()
|
||||
process := s.Core().Process()
|
||||
r := process.RunIn(ctx, repoDir, "composer", "test", "--no-interaction")
|
||||
if !r.OK {
|
||||
composerResult := process.RunIn(ctx, repoDir, "composer", "test", "--no-interaction")
|
||||
if !composerResult.OK {
|
||||
// Try pest as fallback
|
||||
r2 := process.RunIn(ctx, repoDir, "./vendor/bin/pest", "--no-interaction")
|
||||
if !r2.OK {
|
||||
fallbackResult := process.RunIn(ctx, repoDir, "./vendor/bin/pest", "--no-interaction")
|
||||
if !fallbackResult.OK {
|
||||
return verifyResult{passed: false, testCmd: "none", output: "No PHP test runner found (composer test and vendor/bin/pest both unavailable)", exitCode: 1}
|
||||
}
|
||||
return verifyResult{passed: true, output: resultText(r2), exitCode: 0, testCmd: "vendor/bin/pest"}
|
||||
return verifyResult{passed: true, output: resultText(fallbackResult), exitCode: 0, testCmd: "vendor/bin/pest"}
|
||||
}
|
||||
return verifyResult{passed: true, output: resultText(r), exitCode: 0, testCmd: "composer test"}
|
||||
return verifyResult{passed: true, output: resultText(composerResult), exitCode: 0, testCmd: "composer test"}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) runNodeTests(repoDir string) verifyResult {
|
||||
r := fs.Read(core.JoinPath(repoDir, "package.json"))
|
||||
if !r.OK {
|
||||
packageResult := fs.Read(core.JoinPath(repoDir, "package.json"))
|
||||
if !packageResult.OK {
|
||||
return verifyResult{passed: true, testCmd: "none", output: "Could not read package.json"}
|
||||
}
|
||||
|
||||
var pkg struct {
|
||||
Scripts map[string]string `json:"scripts"`
|
||||
}
|
||||
if ur := core.JSONUnmarshalString(r.Value.(string), &pkg); !ur.OK || pkg.Scripts["test"] == "" {
|
||||
if parseResult := core.JSONUnmarshalString(packageResult.Value.(string), &pkg); !parseResult.OK || pkg.Scripts["test"] == "" {
|
||||
return verifyResult{passed: true, testCmd: "none", output: "No test script in package.json"}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
process := s.Core().Process()
|
||||
r = process.RunIn(ctx, repoDir, "npm", "test")
|
||||
out := resultText(r)
|
||||
testResult := process.RunIn(ctx, repoDir, "npm", "test")
|
||||
out := resultText(testResult)
|
||||
exitCode := 0
|
||||
if !r.OK {
|
||||
if !testResult.OK {
|
||||
exitCode = 1
|
||||
}
|
||||
return verifyResult{passed: r.OK, output: out, exitCode: exitCode, testCmd: "npm test"}
|
||||
return verifyResult{passed: testResult.OK, output: out, exitCode: exitCode, testCmd: "npm test"}
|
||||
}
|
||||
|
||||
// forgeMergePR merges a PR via the Forge API.
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ import (
|
|||
// fs provides unrestricted filesystem access for shared brain credentials.
|
||||
//
|
||||
// keyPath := core.JoinPath(home, ".claude", "brain.key")
|
||||
// if r := fs.Read(keyPath); r.OK {
|
||||
// apiKey = core.Trim(r.Value.(string))
|
||||
// if readResult := fs.Read(keyPath); readResult.OK {
|
||||
// apiKey = core.Trim(readResult.Value.(string))
|
||||
// }
|
||||
var fs = agentic.LocalFs()
|
||||
|
||||
func fieldString(values map[string]any, key string) string {
|
||||
func stringField(values map[string]any, key string) string {
|
||||
return core.Sprint(values[key])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ func NewDirect() *DirectSubsystem {
|
|||
if apiKey == "" {
|
||||
keyPath = brainKeyPath(agentic.HomeDir())
|
||||
if keyPath != "" {
|
||||
if r := fs.Read(keyPath); r.OK {
|
||||
apiKey = core.Trim(r.Value.(string))
|
||||
if readResult := fs.Read(keyPath); readResult.OK {
|
||||
apiKey = core.Trim(readResult.Value.(string))
|
||||
if apiKey != "" {
|
||||
core.Info("brain direct subsystem loaded API key from file", "path", keyPath)
|
||||
}
|
||||
|
|
@ -110,22 +110,22 @@ func (s *DirectSubsystem) apiCall(ctx context.Context, method, path string, body
|
|||
if body != nil {
|
||||
bodyStr = core.JSONMarshalString(body)
|
||||
}
|
||||
r := agentic.HTTPDo(ctx, method, requestURL, bodyStr, s.apiKey, "Bearer")
|
||||
if !r.OK {
|
||||
requestResult := agentic.HTTPDo(ctx, method, requestURL, bodyStr, s.apiKey, "Bearer")
|
||||
if !requestResult.OK {
|
||||
core.Error("brain API call failed", "method", method, "path", path)
|
||||
if err, ok := r.Value.(error); ok {
|
||||
if err, ok := requestResult.Value.(error); ok {
|
||||
return core.Result{Value: core.E("brain.apiCall", "API call failed", err), OK: false}
|
||||
}
|
||||
if responseBody, ok := r.Value.(string); ok && responseBody != "" {
|
||||
if responseBody, ok := requestResult.Value.(string); ok && responseBody != "" {
|
||||
return core.Result{Value: core.E("brain.apiCall", core.Concat("API call failed: ", core.Trim(responseBody)), nil), OK: false}
|
||||
}
|
||||
return core.Result{Value: core.E("brain.apiCall", "API call failed", nil), OK: false}
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if ur := core.JSONUnmarshalString(r.Value.(string), &result); !ur.OK {
|
||||
if parseResult := core.JSONUnmarshalString(requestResult.Value.(string), &result); !parseResult.OK {
|
||||
core.Error("brain API response parse failed", "method", method, "path", path)
|
||||
err, _ := ur.Value.(error)
|
||||
err, _ := parseResult.Value.(error)
|
||||
return core.Result{Value: core.E("brain.apiCall", "parse response", err), OK: false}
|
||||
}
|
||||
|
||||
|
|
@ -191,11 +191,11 @@ func (s *DirectSubsystem) recall(ctx context.Context, _ *mcp.CallToolRequest, in
|
|||
for _, m := range mems {
|
||||
if mm, ok := m.(map[string]any); ok {
|
||||
mem := Memory{
|
||||
Content: fieldString(mm, "content"),
|
||||
Type: fieldString(mm, "type"),
|
||||
Project: fieldString(mm, "project"),
|
||||
AgentID: fieldString(mm, "agent_id"),
|
||||
CreatedAt: fieldString(mm, "created_at"),
|
||||
Content: stringField(mm, "content"),
|
||||
Type: stringField(mm, "type"),
|
||||
Project: stringField(mm, "project"),
|
||||
AgentID: stringField(mm, "agent_id"),
|
||||
CreatedAt: stringField(mm, "created_at"),
|
||||
}
|
||||
if id, ok := mm["id"].(string); ok {
|
||||
mem.ID = id
|
||||
|
|
|
|||
|
|
@ -165,12 +165,12 @@ func parseMessages(result map[string]any) []MessageItem {
|
|||
mm, _ := m.(map[string]any)
|
||||
messages = append(messages, MessageItem{
|
||||
ID: toInt(mm["id"]),
|
||||
From: fieldString(mm, "from"),
|
||||
To: fieldString(mm, "to"),
|
||||
Subject: fieldString(mm, "subject"),
|
||||
Content: fieldString(mm, "content"),
|
||||
From: stringField(mm, "from"),
|
||||
To: stringField(mm, "to"),
|
||||
Subject: stringField(mm, "subject"),
|
||||
Content: stringField(mm, "content"),
|
||||
Read: mm["read"] == true,
|
||||
CreatedAt: fieldString(mm, "created_at"),
|
||||
CreatedAt: stringField(mm, "created_at"),
|
||||
})
|
||||
}
|
||||
return messages
|
||||
|
|
|
|||
|
|
@ -62,26 +62,26 @@ func (m *Subsystem) harvestCompleted() string {
|
|||
|
||||
// harvestWorkspace checks a single workspace and pushes if ready.
|
||||
func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
||||
r := fs.Read(agentic.WorkspaceStatusPath(wsDir))
|
||||
if !r.OK {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(wsDir))
|
||||
if !statusResult.OK {
|
||||
return nil
|
||||
}
|
||||
statusData, ok := resultString(r)
|
||||
statusData, ok := resultString(statusResult)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var st struct {
|
||||
var workspaceStatus struct {
|
||||
Status string `json:"status"`
|
||||
Repo string `json:"repo"`
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
if r := core.JSONUnmarshalString(statusData, &st); !r.OK {
|
||||
if parseResult := core.JSONUnmarshalString(statusData, &workspaceStatus); !parseResult.OK {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only harvest completed workspaces (not merged, running, etc.)
|
||||
if st.Status != "completed" {
|
||||
if workspaceStatus.Status != "completed" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
|||
}
|
||||
|
||||
// Check if there are commits to push
|
||||
branch := st.Branch
|
||||
branch := workspaceStatus.Branch
|
||||
if branch == "" {
|
||||
branch = m.detectBranch(repoDir)
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
|||
// Safety checks before pushing
|
||||
if reason := m.checkSafety(repoDir); reason != "" {
|
||||
updateStatus(wsDir, "rejected", reason)
|
||||
return &harvestResult{repo: st.Repo, branch: branch, rejected: reason}
|
||||
return &harvestResult{repo: workspaceStatus.Repo, branch: branch, rejected: reason}
|
||||
}
|
||||
|
||||
// Count changed files
|
||||
|
|
@ -120,16 +120,16 @@ func (m *Subsystem) harvestWorkspace(wsDir string) *harvestResult {
|
|||
// explicit review (/review command), not silently in the background.
|
||||
updateStatus(wsDir, "ready-for-review", "")
|
||||
|
||||
return &harvestResult{repo: st.Repo, branch: branch, files: files}
|
||||
return &harvestResult{repo: workspaceStatus.Repo, branch: branch, files: files}
|
||||
}
|
||||
|
||||
// gitOutput runs a git command and returns trimmed stdout via Core Process.
|
||||
func (m *Subsystem) gitOutput(dir string, args ...string) string {
|
||||
r := m.Core().Process().RunIn(context.Background(), dir, "git", args...)
|
||||
if !r.OK {
|
||||
processResult := m.Core().Process().RunIn(context.Background(), dir, "git", args...)
|
||||
if !processResult.OK {
|
||||
return ""
|
||||
}
|
||||
return core.Trim(r.Value.(string))
|
||||
return core.Trim(processResult.Value.(string))
|
||||
}
|
||||
|
||||
// gitOK runs a git command and returns true if it exits 0.
|
||||
|
|
@ -235,9 +235,9 @@ func (m *Subsystem) countChangedFiles(srcDir string) int {
|
|||
|
||||
// pushBranch pushes the agent's branch to origin.
|
||||
func (m *Subsystem) pushBranch(srcDir, branch string) error {
|
||||
r := m.Core().Process().RunIn(context.Background(), srcDir, "git", "push", "origin", branch)
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
processResult := m.Core().Process().RunIn(context.Background(), srcDir, "git", "push", "origin", branch)
|
||||
if !processResult.OK {
|
||||
if err, ok := processResult.Value.(error); ok {
|
||||
return core.E("harvest.pushBranch", "push failed", err)
|
||||
}
|
||||
return core.E("harvest.pushBranch", "push failed", nil)
|
||||
|
|
@ -249,27 +249,27 @@ func (m *Subsystem) pushBranch(srcDir, branch string) error {
|
|||
//
|
||||
// updateStatus(wsDir, "ready-for-review", "")
|
||||
func updateStatus(wsDir, status, question string) {
|
||||
r := fs.Read(agentic.WorkspaceStatusPath(wsDir))
|
||||
if !r.OK {
|
||||
statusResult := fs.Read(agentic.WorkspaceStatusPath(wsDir))
|
||||
if !statusResult.OK {
|
||||
return
|
||||
}
|
||||
statusData, ok := resultString(r)
|
||||
statusData, ok := resultString(statusResult)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var st map[string]any
|
||||
if r := core.JSONUnmarshalString(statusData, &st); !r.OK {
|
||||
var workspaceStatus map[string]any
|
||||
if parseResult := core.JSONUnmarshalString(statusData, &workspaceStatus); !parseResult.OK {
|
||||
return
|
||||
}
|
||||
st["status"] = status
|
||||
workspaceStatus["status"] = status
|
||||
if question != "" {
|
||||
st["question"] = question
|
||||
workspaceStatus["question"] = question
|
||||
} else {
|
||||
delete(st, "question") // clear stale question from previous state
|
||||
delete(workspaceStatus, "question") // clear stale question from previous state
|
||||
}
|
||||
statusPath := agentic.WorkspaceStatusPath(wsDir)
|
||||
if r := fs.WriteAtomic(statusPath, core.JSONMarshalString(st)); !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
if writeResult := fs.WriteAtomic(statusPath, core.JSONMarshalString(workspaceStatus)); !writeResult.OK {
|
||||
if err, ok := writeResult.Value.(error); ok {
|
||||
core.Warn("monitor.updateStatus: failed to write status", "path", statusPath, "reason", err)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// r := fs.Read(core.JoinPath(wsRoot, name, "status.json"))
|
||||
// if text, ok := resultString(r); ok { _ = core.JSONUnmarshalString(text, &st) }
|
||||
// readResult := fs.Read(core.JoinPath(wsRoot, name, "status.json"))
|
||||
// if text, ok := resultString(readResult); ok { _ = core.JSONUnmarshalString(text, &workspaceStatus) }
|
||||
var fs = agentic.LocalFs()
|
||||
|
||||
type channelSender interface {
|
||||
|
|
@ -42,16 +42,16 @@ func monitorBrainKey() string {
|
|||
if k := core.Env("CORE_BRAIN_KEY"); k != "" {
|
||||
return k
|
||||
}
|
||||
if r := fs.Read(brainKeyPath(agentic.HomeDir())); r.OK {
|
||||
if value, ok := resultString(r); ok {
|
||||
if readResult := fs.Read(brainKeyPath(agentic.HomeDir())); readResult.OK {
|
||||
if value, ok := resultString(readResult); ok {
|
||||
return core.Trim(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func resultString(r core.Result) (string, bool) {
|
||||
value, ok := r.Value.(string)
|
||||
func resultString(result core.Result) (string, bool) {
|
||||
value, ok := result.Value.(string)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
|
@ -172,15 +172,15 @@ func (m *Subsystem) Start(ctx context.Context) {
|
|||
}()
|
||||
}
|
||||
|
||||
// r := service.OnStartup(context.Background())
|
||||
// core.Println(r.OK)
|
||||
// result := service.OnStartup(context.Background())
|
||||
// core.Println(result.OK)
|
||||
func (m *Subsystem) OnStartup(ctx context.Context) core.Result {
|
||||
m.Start(ctx)
|
||||
return core.Result{OK: true}
|
||||
}
|
||||
|
||||
// r := service.OnShutdown(context.Background())
|
||||
// core.Println(r.OK)
|
||||
// result := service.OnShutdown(context.Background())
|
||||
// core.Println(result.OK)
|
||||
func (m *Subsystem) OnShutdown(ctx context.Context) core.Result {
|
||||
_ = m.Shutdown(ctx)
|
||||
return core.Result{OK: true}
|
||||
|
|
|
|||
|
|
@ -69,16 +69,16 @@ func CoreRoot() string {
|
|||
// result := ReadStatusResult("/srv/core/workspace/core/go-io/task-5")
|
||||
// if result.OK { workspaceStatus := result.Value.(*WorkspaceStatus) }
|
||||
func ReadStatusResult(wsDir string) core.Result {
|
||||
result := agentic.ReadStatusResult(wsDir)
|
||||
if !result.OK {
|
||||
err, _ := result.Value.(error)
|
||||
statusResult := agentic.ReadStatusResult(wsDir)
|
||||
if !statusResult.OK {
|
||||
err, _ := statusResult.Value.(error)
|
||||
if err == nil {
|
||||
return core.Result{Value: core.E("runner.ReadStatusResult", "failed to read status", nil), OK: false}
|
||||
}
|
||||
return core.Result{Value: core.E("runner.ReadStatusResult", "failed to read status", err), OK: false}
|
||||
}
|
||||
|
||||
agenticStatus, ok := result.Value.(*agentic.WorkspaceStatus)
|
||||
agenticStatus, ok := statusResult.Value.(*agentic.WorkspaceStatus)
|
||||
if !ok || agenticStatus == nil {
|
||||
return core.Result{Value: core.E("runner.ReadStatusResult", "invalid status payload", nil), OK: false}
|
||||
}
|
||||
|
|
@ -91,8 +91,8 @@ func ReadStatusResult(wsDir string) core.Result {
|
|||
|
||||
// WriteStatus writes `status.json` for one workspace directory.
|
||||
//
|
||||
// r := runner.WriteStatus("/srv/core/workspace/core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// core.Println(r.OK)
|
||||
// result := runner.WriteStatus("/srv/core/workspace/core/go-io/task-5", &runner.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// core.Println(result.OK)
|
||||
func WriteStatus(wsDir string, status *WorkspaceStatus) core.Result {
|
||||
if status == nil {
|
||||
return core.Result{Value: core.E("runner.WriteStatus", "status is required", nil), OK: false}
|
||||
|
|
@ -103,8 +103,8 @@ func WriteStatus(wsDir string, status *WorkspaceStatus) core.Result {
|
|||
return core.Result{Value: core.E("runner.WriteStatus", "status conversion failed", nil), OK: false}
|
||||
}
|
||||
agenticStatus.UpdatedAt = time.Now()
|
||||
if r := fs.WriteAtomic(agentic.WorkspaceStatusPath(wsDir), core.JSONMarshalString(agenticStatus)); !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
if writeResult := fs.WriteAtomic(agentic.WorkspaceStatusPath(wsDir), core.JSONMarshalString(agenticStatus)); !writeResult.OK {
|
||||
err, _ := writeResult.Value.(error)
|
||||
if err == nil {
|
||||
return core.Result{Value: core.E("runner.WriteStatus", "failed to write status", nil), OK: false}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,12 +93,12 @@ func (s *Service) loadAgentsConfig() *AgentsConfig {
|
|||
core.JoinPath(CoreRoot(), "agents.yaml"),
|
||||
}
|
||||
for _, path := range paths {
|
||||
r := fs.Read(path)
|
||||
if !r.OK {
|
||||
readResult := fs.Read(path)
|
||||
if !readResult.OK {
|
||||
continue
|
||||
}
|
||||
var config AgentsConfig
|
||||
if err := yaml.Unmarshal([]byte(r.Value.(string)), &config); err != nil {
|
||||
if err := yaml.Unmarshal([]byte(readResult.Value.(string)), &config); err != nil {
|
||||
continue
|
||||
}
|
||||
return &config
|
||||
|
|
@ -121,9 +121,9 @@ func (s *Service) loadAgentsConfig() *AgentsConfig {
|
|||
func (s *Service) canDispatchAgent(agent string) (bool, string) {
|
||||
var concurrency map[string]ConcurrencyLimit
|
||||
if s.ServiceRuntime != nil {
|
||||
r := s.Core().Config().Get("agents.concurrency")
|
||||
if r.OK {
|
||||
concurrency, _ = r.Value.(map[string]ConcurrencyLimit)
|
||||
configurationResult := s.Core().Config().Get("agents.concurrency")
|
||||
if configurationResult.OK {
|
||||
concurrency, _ = configurationResult.Value.(map[string]ConcurrencyLimit)
|
||||
}
|
||||
}
|
||||
if concurrency == nil {
|
||||
|
|
@ -166,14 +166,14 @@ func (s *Service) countRunningByAgent(agent string) int {
|
|||
runtime = s.Core()
|
||||
}
|
||||
count := 0
|
||||
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
|
||||
if st.Status != "running" || baseAgent(st.Agent) != agent {
|
||||
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Status != "running" || baseAgent(workspaceStatus.Agent) != agent {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case st.PID < 0:
|
||||
case workspaceStatus.PID < 0:
|
||||
count++
|
||||
case st.PID > 0 && agentic.ProcessAlive(runtime, "", st.PID):
|
||||
case workspaceStatus.PID > 0 && agentic.ProcessAlive(runtime, "", workspaceStatus.PID):
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
|
@ -189,14 +189,14 @@ func (s *Service) countRunningByModel(agent string) int {
|
|||
runtime = s.Core()
|
||||
}
|
||||
count := 0
|
||||
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
|
||||
if st.Status != "running" || st.Agent != agent {
|
||||
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Status != "running" || workspaceStatus.Agent != agent {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case st.PID < 0:
|
||||
case workspaceStatus.PID < 0:
|
||||
count++
|
||||
case st.PID > 0 && agentic.ProcessAlive(runtime, "", st.PID):
|
||||
case workspaceStatus.PID > 0 && agentic.ProcessAlive(runtime, "", workspaceStatus.PID):
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
|
@ -221,30 +221,30 @@ func (s *Service) drainQueue() {
|
|||
func (s *Service) drainOne() bool {
|
||||
for _, statusPath := range agentic.WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(statusPath)
|
||||
result := ReadStatusResult(wsDir)
|
||||
if !result.OK {
|
||||
statusResult := ReadStatusResult(wsDir)
|
||||
if !statusResult.OK {
|
||||
continue
|
||||
}
|
||||
st, ok := result.Value.(*WorkspaceStatus)
|
||||
if !ok || st == nil || st.Status != "queued" {
|
||||
workspaceStatus, ok := statusResult.Value.(*WorkspaceStatus)
|
||||
if !ok || workspaceStatus == nil || workspaceStatus.Status != "queued" {
|
||||
continue
|
||||
}
|
||||
|
||||
if can, _ := s.canDispatchAgent(st.Agent); !can {
|
||||
if can, _ := s.canDispatchAgent(workspaceStatus.Agent); !can {
|
||||
continue
|
||||
}
|
||||
|
||||
pool := baseAgent(st.Agent)
|
||||
pool := baseAgent(workspaceStatus.Agent)
|
||||
if until, ok := s.backoff[pool]; ok && time.Now().Before(until) {
|
||||
continue
|
||||
}
|
||||
|
||||
delay := s.delayForAgent(st.Agent)
|
||||
delay := s.delayForAgent(workspaceStatus.Agent)
|
||||
if delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
if can, _ := s.canDispatchAgent(st.Agent); !can {
|
||||
if can, _ := s.canDispatchAgent(workspaceStatus.Agent); !can {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ func (s *Service) drainOne() bool {
|
|||
// agentic owns the actual process launch.
|
||||
// Workspace name is relative path from workspace root (e.g. "core/go-ai/dev")
|
||||
wsName := agentic.WorkspaceName(wsDir)
|
||||
core.Info("drainOne: found queued workspace", "workspace", wsName, "agent", st.Agent)
|
||||
core.Info("drainOne: found queued workspace", "workspace", wsName, "agent", workspaceStatus.Agent)
|
||||
|
||||
// Spawn directly — agentic is a Core service, use ServiceFor to get it
|
||||
if s.ServiceRuntime == nil {
|
||||
|
|
@ -261,13 +261,13 @@ func (s *Service) drainOne() bool {
|
|||
type spawner interface {
|
||||
SpawnFromQueue(agent, prompt, wsDir string) core.Result
|
||||
}
|
||||
prep, ok := core.ServiceFor[spawner](s.Core(), "agentic")
|
||||
agenticService, ok := core.ServiceFor[spawner](s.Core(), "agentic")
|
||||
if !ok {
|
||||
core.Error("drainOne: agentic service not found")
|
||||
continue
|
||||
}
|
||||
prompt := core.Concat("TASK: ", st.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
|
||||
spawnResult := prep.SpawnFromQueue(st.Agent, prompt, wsDir)
|
||||
prompt := core.Concat("TASK: ", workspaceStatus.Task, "\n\nResume from where you left off. Read CODEX.md for conventions. Commit when done.")
|
||||
spawnResult := agenticService.SpawnFromQueue(workspaceStatus.Agent, prompt, wsDir)
|
||||
if !spawnResult.OK {
|
||||
core.Error("drainOne: spawn failed", "workspace", wsName, "reason", core.Sprint(spawnResult.Value))
|
||||
continue
|
||||
|
|
@ -279,14 +279,14 @@ func (s *Service) drainOne() bool {
|
|||
}
|
||||
|
||||
// Only mark running AFTER successful spawn
|
||||
st.Status = "running"
|
||||
st.PID = pid
|
||||
st.Runs++
|
||||
if r := WriteStatus(wsDir, st); !r.OK {
|
||||
core.Error("drainOne: failed to write workspace status", "workspace", wsName, "reason", core.Sprint(r.Value))
|
||||
workspaceStatus.Status = "running"
|
||||
workspaceStatus.PID = pid
|
||||
workspaceStatus.Runs++
|
||||
if writeResult := WriteStatus(wsDir, workspaceStatus); !writeResult.OK {
|
||||
core.Error("drainOne: failed to write workspace status", "workspace", wsName, "reason", core.Sprint(writeResult.Value))
|
||||
continue
|
||||
}
|
||||
s.TrackWorkspace(wsName, st)
|
||||
s.TrackWorkspace(wsName, workspaceStatus)
|
||||
core.Info("drainOne: spawned", "pid", pid, "workspace", wsName)
|
||||
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ func (s *Service) OnStartup(ctx context.Context) core.Result {
|
|||
|
||||
// OnShutdown freezes the queue.
|
||||
//
|
||||
// r := service.OnShutdown(context.Background())
|
||||
// if r.OK {
|
||||
// result := service.OnShutdown(context.Background())
|
||||
// if result.OK {
|
||||
// core.Println(service.IsFrozen())
|
||||
// }
|
||||
func (s *Service) OnShutdown(_ context.Context) core.Result {
|
||||
|
|
@ -129,9 +129,9 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
|
|||
base := baseAgent(ev.Agent)
|
||||
running := s.countRunningByAgent(base)
|
||||
var limit int
|
||||
r := c.Config().Get("agents.concurrency")
|
||||
if r.OK {
|
||||
if concurrency, ok := r.Value.(map[string]ConcurrencyLimit); ok {
|
||||
configurationResult := c.Config().Get("agents.concurrency")
|
||||
if configurationResult.OK {
|
||||
if concurrency, ok := configurationResult.Value.(map[string]ConcurrencyLimit); ok {
|
||||
if cl, has := concurrency[base]; has {
|
||||
limit = cl.Total
|
||||
}
|
||||
|
|
@ -152,26 +152,26 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result {
|
|||
case messages.AgentCompleted:
|
||||
// Update workspace status in Registry so concurrency count drops
|
||||
if ev.Workspace != "" {
|
||||
if r := s.workspaces.Get(ev.Workspace); r.OK {
|
||||
if st, ok := r.Value.(*WorkspaceStatus); ok && st.Status == "running" {
|
||||
st.Status = ev.Status
|
||||
st.PID = 0
|
||||
if workspaceResult := s.workspaces.Get(ev.Workspace); workspaceResult.OK {
|
||||
if workspaceStatus, ok := workspaceResult.Value.(*WorkspaceStatus); ok && workspaceStatus.Status == "running" {
|
||||
workspaceStatus.Status = ev.Status
|
||||
workspaceStatus.PID = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
|
||||
if st.Repo == ev.Repo && st.Status == "running" {
|
||||
st.Status = ev.Status
|
||||
st.PID = 0
|
||||
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Repo == ev.Repo && workspaceStatus.Status == "running" {
|
||||
workspaceStatus.Status = ev.Status
|
||||
workspaceStatus.PID = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
cBase := baseAgent(ev.Agent)
|
||||
cRunning := s.countRunningByAgent(cBase)
|
||||
var cLimit int
|
||||
cr := c.Config().Get("agents.concurrency")
|
||||
if cr.OK {
|
||||
if concurrency, ok := cr.Value.(map[string]ConcurrencyLimit); ok {
|
||||
completionResult := c.Config().Get("agents.concurrency")
|
||||
if completionResult.OK {
|
||||
if concurrency, ok := completionResult.Value.(map[string]ConcurrencyLimit); ok {
|
||||
if cl, has := concurrency[cBase]; has {
|
||||
cLimit = cl.Total
|
||||
}
|
||||
|
|
@ -222,54 +222,54 @@ func (s *Service) Poke() {
|
|||
//
|
||||
// s.TrackWorkspace("core/go-io/task-5", &WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
// s.TrackWorkspace("core/go-io/task-5", &agentic.WorkspaceStatus{Status: "running", Agent: "codex"})
|
||||
func (s *Service) TrackWorkspace(name string, st any) {
|
||||
func (s *Service) TrackWorkspace(name string, status any) {
|
||||
if s.workspaces == nil {
|
||||
return
|
||||
}
|
||||
var ws *WorkspaceStatus
|
||||
switch value := st.(type) {
|
||||
var workspaceStatus *WorkspaceStatus
|
||||
switch value := status.(type) {
|
||||
case *WorkspaceStatus:
|
||||
ws = value
|
||||
workspaceStatus = value
|
||||
case *agentic.WorkspaceStatus:
|
||||
ws = runnerWorkspaceStatusFromAgentic(value)
|
||||
workspaceStatus = runnerWorkspaceStatusFromAgentic(value)
|
||||
default:
|
||||
json := core.JSONMarshalString(st)
|
||||
var workspace WorkspaceStatus
|
||||
if r := core.JSONUnmarshalString(json, &workspace); r.OK {
|
||||
ws = &workspace
|
||||
statusJSON := core.JSONMarshalString(status)
|
||||
var decodedWorkspace WorkspaceStatus
|
||||
if result := core.JSONUnmarshalString(statusJSON, &decodedWorkspace); result.OK {
|
||||
workspaceStatus = &decodedWorkspace
|
||||
}
|
||||
}
|
||||
if ws == nil {
|
||||
if workspaceStatus == nil {
|
||||
return
|
||||
}
|
||||
s.workspaces.Set(name, ws)
|
||||
s.workspaces.Set(name, workspaceStatus)
|
||||
// Remove pending reservation now that the real workspace is tracked
|
||||
s.workspaces.Delete(core.Concat("pending/", ws.Repo))
|
||||
s.workspaces.Delete(core.Concat("pending/", workspaceStatus.Repo))
|
||||
}
|
||||
|
||||
// Workspaces returns the workspace Registry.
|
||||
//
|
||||
// s.Workspaces().Each(func(name string, st *WorkspaceStatus) { ... })
|
||||
// s.Workspaces().Each(func(name string, workspaceStatus *WorkspaceStatus) { ... })
|
||||
func (s *Service) Workspaces() *core.Registry[*WorkspaceStatus] {
|
||||
return s.workspaces
|
||||
}
|
||||
|
||||
// handleWorkspaceQuery answers workspace state queries from Core QUERY calls.
|
||||
//
|
||||
// r := c.QUERY(runner.WorkspaceQuery{Name: "core/go-io/task-42"})
|
||||
// r := c.QUERY(runner.WorkspaceQuery{Status: "running"})
|
||||
func (s *Service) handleWorkspaceQuery(_ *core.Core, q core.Query) core.Result {
|
||||
wq, ok := q.(WorkspaceQuery)
|
||||
// result := c.QUERY(runner.WorkspaceQuery{Name: "core/go-io/task-42"})
|
||||
// result := c.QUERY(runner.WorkspaceQuery{Status: "running"})
|
||||
func (s *Service) handleWorkspaceQuery(_ *core.Core, query core.Query) core.Result {
|
||||
workspaceQuery, ok := query.(WorkspaceQuery)
|
||||
if !ok {
|
||||
return core.Result{}
|
||||
}
|
||||
if wq.Name != "" {
|
||||
return s.workspaces.Get(wq.Name)
|
||||
if workspaceQuery.Name != "" {
|
||||
return s.workspaces.Get(workspaceQuery.Name)
|
||||
}
|
||||
if wq.Status != "" {
|
||||
if workspaceQuery.Status != "" {
|
||||
var names []string
|
||||
s.workspaces.Each(func(name string, st *WorkspaceStatus) {
|
||||
if st.Status == wq.Status {
|
||||
s.workspaces.Each(func(name string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Status == workspaceQuery.Status {
|
||||
names = append(names, name)
|
||||
}
|
||||
})
|
||||
|
|
@ -280,16 +280,16 @@ func (s *Service) handleWorkspaceQuery(_ *core.Core, q core.Query) core.Result {
|
|||
|
||||
// --- Actions ---
|
||||
|
||||
func (s *Service) actionDispatch(_ context.Context, opts core.Options) core.Result {
|
||||
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}
|
||||
}
|
||||
|
||||
agent := opts.String("agent")
|
||||
agent := options.String("agent")
|
||||
if agent == "" {
|
||||
agent = "codex"
|
||||
}
|
||||
repo := opts.String("repo")
|
||||
repo := options.String("repo")
|
||||
|
||||
s.dispatchMu.Lock()
|
||||
defer s.dispatchMu.Unlock()
|
||||
|
|
@ -313,8 +313,8 @@ func (s *Service) actionDispatch(_ context.Context, opts core.Options) core.Resu
|
|||
|
||||
func (s *Service) actionStatus(_ context.Context, _ core.Options) core.Result {
|
||||
running, queued, completed, failed := 0, 0, 0, 0
|
||||
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
|
||||
switch st.Status {
|
||||
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
|
||||
switch workspaceStatus.Status {
|
||||
case "running":
|
||||
running++
|
||||
case "queued":
|
||||
|
|
@ -350,16 +350,16 @@ func (s *Service) actionKill(_ context.Context, _ core.Options) core.Result {
|
|||
runtime = s.Core()
|
||||
}
|
||||
killed := 0
|
||||
s.workspaces.Each(func(_ string, st *WorkspaceStatus) {
|
||||
if st.Status == "running" && st.PID > 0 {
|
||||
if agentic.ProcessTerminate(runtime, "", st.PID) {
|
||||
s.workspaces.Each(func(_ string, workspaceStatus *WorkspaceStatus) {
|
||||
if workspaceStatus.Status == "running" && workspaceStatus.PID > 0 {
|
||||
if agentic.ProcessTerminate(runtime, "", workspaceStatus.PID) {
|
||||
killed++
|
||||
}
|
||||
st.Status = "failed"
|
||||
st.PID = 0
|
||||
workspaceStatus.Status = "failed"
|
||||
workspaceStatus.PID = 0
|
||||
}
|
||||
if st.Status == "queued" {
|
||||
st.Status = "failed"
|
||||
if workspaceStatus.Status == "queued" {
|
||||
workspaceStatus.Status = "failed"
|
||||
}
|
||||
})
|
||||
return core.Result{Value: core.Sprintf("killed %d agents", killed), OK: true}
|
||||
|
|
@ -405,19 +405,19 @@ func (s *Service) hydrateWorkspaces() {
|
|||
}
|
||||
for _, path := range agentic.WorkspaceStatusPaths() {
|
||||
wsDir := core.PathDir(path)
|
||||
result := ReadStatusResult(wsDir)
|
||||
if !result.OK {
|
||||
statusResult := ReadStatusResult(wsDir)
|
||||
if !statusResult.OK {
|
||||
continue
|
||||
}
|
||||
st, ok := result.Value.(*WorkspaceStatus)
|
||||
if !ok || st == nil {
|
||||
workspaceStatus, ok := statusResult.Value.(*WorkspaceStatus)
|
||||
if !ok || workspaceStatus == nil {
|
||||
continue
|
||||
}
|
||||
// Re-queue running agents on restart — process is dead, re-dispatch
|
||||
if st.Status == "running" {
|
||||
st.Status = "queued"
|
||||
if workspaceStatus.Status == "running" {
|
||||
workspaceStatus.Status = "queued"
|
||||
}
|
||||
s.workspaces.Set(agentic.WorkspaceName(wsDir), st)
|
||||
s.workspaces.Set(agentic.WorkspaceName(wsDir), workspaceStatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +442,7 @@ type AgentNotification struct {
|
|||
|
||||
// WorkspaceQuery is the QUERY type for workspace lookups.
|
||||
//
|
||||
// r := c.QUERY(runner.WorkspaceQuery{Status: "running"})
|
||||
// result := c.QUERY(runner.WorkspaceQuery{Status: "running"})
|
||||
type WorkspaceQuery struct {
|
||||
Name string
|
||||
Status string
|
||||
|
|
@ -450,7 +450,7 @@ type WorkspaceQuery struct {
|
|||
|
||||
// WorkspaceStatus tracks the state of an agent workspace.
|
||||
//
|
||||
// st := &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"`
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ func (s *Service) OnStartup(ctx context.Context) core.Result {
|
|||
//
|
||||
// remote := service.DetectGitRemote("/srv/repos/agent")
|
||||
func (s *Service) DetectGitRemote(path string) string {
|
||||
r := s.Core().Process().RunIn(context.Background(), path, "git", "remote", "get-url", "origin")
|
||||
if !r.OK {
|
||||
result := s.Core().Process().RunIn(context.Background(), path, "git", "remote", "get-url", "origin")
|
||||
if !result.OK {
|
||||
return ""
|
||||
}
|
||||
return parseGitRemote(core.Trim(r.Value.(string)))
|
||||
return parseGitRemote(core.Trim(result.Value.(string)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,24 +22,24 @@ type Options struct {
|
|||
//
|
||||
// result := service.Run(setup.Options{Path: ".", Template: "auto"})
|
||||
// core.Println(result.OK)
|
||||
func (s *Service) Run(opts Options) core.Result {
|
||||
if opts.Path == "" {
|
||||
opts.Path = core.Env("DIR_CWD")
|
||||
func (s *Service) Run(options Options) core.Result {
|
||||
if options.Path == "" {
|
||||
options.Path = core.Env("DIR_CWD")
|
||||
}
|
||||
opts.Path = absolutePath(opts.Path)
|
||||
options.Path = absolutePath(options.Path)
|
||||
|
||||
projType := Detect(opts.Path)
|
||||
allTypes := DetectAll(opts.Path)
|
||||
projType := Detect(options.Path)
|
||||
allTypes := DetectAll(options.Path)
|
||||
|
||||
core.Print(nil, "Project: %s", core.PathBase(opts.Path))
|
||||
core.Print(nil, "Project: %s", core.PathBase(options.Path))
|
||||
core.Print(nil, "Type: %s", projType)
|
||||
if len(allTypes) > 1 {
|
||||
core.Print(nil, "Also: %v (polyglot)", allTypes)
|
||||
}
|
||||
|
||||
var tmplName string
|
||||
if opts.Template != "" {
|
||||
templateResult := resolveTemplateName(opts.Template, projType)
|
||||
if options.Template != "" {
|
||||
templateResult := resolveTemplateName(options.Template, projType)
|
||||
if !templateResult.OK {
|
||||
return templateResult
|
||||
}
|
||||
|
|
@ -53,28 +53,28 @@ func (s *Service) Run(opts Options) core.Result {
|
|||
}
|
||||
|
||||
// Generate .core/ config files
|
||||
if result := setupCoreDir(opts, projType); !result.OK {
|
||||
if result := setupCoreDir(options, projType); !result.OK {
|
||||
return result
|
||||
}
|
||||
|
||||
// Scaffold from dir template if requested
|
||||
if tmplName != "" {
|
||||
return s.scaffoldTemplate(opts, projType, tmplName)
|
||||
return s.scaffoldTemplate(options, projType, tmplName)
|
||||
}
|
||||
|
||||
return core.Result{Value: opts.Path, OK: true}
|
||||
return core.Result{Value: options.Path, OK: true}
|
||||
}
|
||||
|
||||
// setupCoreDir creates .core/ with build.yaml and test.yaml.
|
||||
func setupCoreDir(opts Options, projType ProjectType) core.Result {
|
||||
coreDir := core.JoinPath(opts.Path, ".core")
|
||||
func setupCoreDir(options Options, projType ProjectType) core.Result {
|
||||
coreDir := core.JoinPath(options.Path, ".core")
|
||||
|
||||
if opts.DryRun {
|
||||
if options.DryRun {
|
||||
core.Print(nil, "")
|
||||
core.Print(nil, "Would create %s/", coreDir)
|
||||
} else {
|
||||
if r := fs.EnsureDir(coreDir); !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
if ensureResult := fs.EnsureDir(coreDir); !ensureResult.OK {
|
||||
err, _ := ensureResult.Value.(error)
|
||||
return core.Result{
|
||||
Value: core.E("setup.setupCoreDir", "create .core directory", err),
|
||||
OK: false,
|
||||
|
|
@ -83,7 +83,7 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
|
|||
}
|
||||
|
||||
// build.yaml
|
||||
buildConfig := GenerateBuildConfig(opts.Path, projType)
|
||||
buildConfig := GenerateBuildConfig(options.Path, projType)
|
||||
if !buildConfig.OK {
|
||||
err, _ := buildConfig.Value.(error)
|
||||
return core.Result{
|
||||
|
|
@ -91,7 +91,7 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
|
|||
OK: false,
|
||||
}
|
||||
}
|
||||
if result := writeConfig(core.JoinPath(coreDir, "build.yaml"), buildConfig.Value.(string), opts); !result.OK {
|
||||
if result := writeConfig(core.JoinPath(coreDir, "build.yaml"), buildConfig.Value.(string), options); !result.OK {
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
|
|||
OK: false,
|
||||
}
|
||||
}
|
||||
if result := writeConfig(core.JoinPath(coreDir, "test.yaml"), testConfig.Value.(string), opts); !result.OK {
|
||||
if result := writeConfig(core.JoinPath(coreDir, "test.yaml"), testConfig.Value.(string), options); !result.OK {
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -112,29 +112,29 @@ func setupCoreDir(opts Options, projType ProjectType) core.Result {
|
|||
}
|
||||
|
||||
// scaffoldTemplate extracts a dir template into the target path.
|
||||
func (s *Service) scaffoldTemplate(opts Options, projType ProjectType, tmplName string) core.Result {
|
||||
func (s *Service) scaffoldTemplate(options Options, projType ProjectType, tmplName string) core.Result {
|
||||
core.Print(nil, "Template: %s", tmplName)
|
||||
|
||||
data := &lib.WorkspaceData{
|
||||
Repo: core.PathBase(opts.Path),
|
||||
Repo: core.PathBase(options.Path),
|
||||
Branch: "main",
|
||||
Task: core.Sprintf("Initialise %s project tooling.", projType),
|
||||
Agent: "setup",
|
||||
Language: string(projType),
|
||||
Prompt: "This workspace was scaffolded by pkg/setup. Review the repository and continue from the generated context files.",
|
||||
Flow: formatFlow(projType),
|
||||
RepoDescription: s.DetectGitRemote(opts.Path),
|
||||
RepoDescription: s.DetectGitRemote(options.Path),
|
||||
BuildCmd: defaultBuildCommand(projType),
|
||||
TestCmd: defaultTestCommand(projType),
|
||||
}
|
||||
|
||||
if opts.DryRun {
|
||||
core.Print(nil, "Would extract workspace/%s to %s", tmplName, opts.Path)
|
||||
if options.DryRun {
|
||||
core.Print(nil, "Would extract workspace/%s to %s", tmplName, options.Path)
|
||||
core.Print(nil, " Template found: %s", tmplName)
|
||||
return core.Result{Value: opts.Path, OK: true}
|
||||
return core.Result{Value: options.Path, OK: true}
|
||||
}
|
||||
|
||||
if result := lib.ExtractWorkspace(tmplName, opts.Path, data); !result.OK {
|
||||
if result := lib.ExtractWorkspace(tmplName, options.Path, data); !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return core.Result{
|
||||
Value: core.E("setup.scaffoldTemplate", core.Concat("extract workspace template ", tmplName), err),
|
||||
|
|
@ -146,22 +146,22 @@ func (s *Service) scaffoldTemplate(opts Options, projType ProjectType, tmplName
|
|||
OK: false,
|
||||
}
|
||||
}
|
||||
return core.Result{Value: opts.Path, OK: true}
|
||||
return core.Result{Value: options.Path, OK: true}
|
||||
}
|
||||
|
||||
func writeConfig(path, content string, opts Options) core.Result {
|
||||
if opts.DryRun {
|
||||
func writeConfig(path, content string, options Options) core.Result {
|
||||
if options.DryRun {
|
||||
core.Print(nil, " %s", path)
|
||||
return core.Result{Value: path, OK: true}
|
||||
}
|
||||
|
||||
if !opts.Force && fs.Exists(path) {
|
||||
if !options.Force && fs.Exists(path) {
|
||||
core.Print(nil, " skip %s (exists, use --force to overwrite)", core.PathBase(path))
|
||||
return core.Result{Value: path, OK: true}
|
||||
}
|
||||
|
||||
if r := fs.WriteMode(path, content, 0644); !r.OK {
|
||||
err, _ := r.Value.(error)
|
||||
if writeResult := fs.WriteMode(path, content, 0644); !writeResult.OK {
|
||||
err, _ := writeResult.Value.(error)
|
||||
return core.Result{
|
||||
Value: core.E("setup.writeConfig", core.Concat("write ", core.PathBase(path)), err),
|
||||
OK: false,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue