fix(ax): register named agentic commands

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-29 23:37:28 +00:00
parent 7c1aae0402
commit 54581dcbd7
3 changed files with 44 additions and 66 deletions

View file

@ -13,21 +13,32 @@ import (
// registerCommands adds agentic CLI commands to Core's command tree.
func (s *PrepSubsystem) registerCommands(ctx context.Context) {
s.commandCtx = ctx
c := s.Core()
c.Command("run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTaskFactory(ctx)})
c.Command("run/orchestrator", core.Command{Description: "Run the queue orchestrator (standalone, no MCP)", Action: s.cmdOrchestratorFactory(ctx)})
c.Command("run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTask})
c.Command("run/orchestrator", core.Command{Description: "Run the queue orchestrator (standalone, no MCP)", Action: s.cmdOrchestrator})
c.Command("prep", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep})
c.Command("status", core.Command{Description: "List agent workspace statuses", Action: s.cmdStatus})
c.Command("prompt", core.Command{Description: "Build and display an agent prompt for a repo", Action: s.cmdPrompt})
c.Command("extract", core.Command{Description: "Extract a workspace template to a directory", Action: s.cmdExtract})
}
// cmdRunTaskFactory returns the run/task action closure (needs ctx for DispatchSync).
func (s *PrepSubsystem) cmdRunTaskFactory(ctx context.Context) func(core.Options) core.Result {
return func(opts core.Options) core.Result { return s.cmdRunTask(ctx, opts) }
// commandContext returns the startup context captured during command registration.
//
// ctx := s.commandContext()
// _ = ctx.Err()
func (s *PrepSubsystem) commandContext() context.Context {
if s.commandCtx != nil {
return s.commandCtx
}
return context.Background()
}
func (s *PrepSubsystem) cmdRunTask(ctx context.Context, opts core.Options) core.Result {
func (s *PrepSubsystem) cmdRunTask(opts core.Options) core.Result {
return s.runTask(s.commandContext(), opts)
}
func (s *PrepSubsystem) runTask(ctx context.Context, opts core.Options) core.Result {
repo := opts.String("repo")
agent := opts.String("agent")
task := opts.String("task")
@ -72,12 +83,8 @@ func (s *PrepSubsystem) cmdRunTask(ctx context.Context, opts core.Options) core.
return core.Result{OK: true}
}
// cmdOrchestratorFactory returns the orchestrator action closure (needs ctx for blocking).
func (s *PrepSubsystem) cmdOrchestratorFactory(ctx context.Context) func(core.Options) core.Result {
return func(opts core.Options) core.Result { return s.cmdOrchestrator(ctx, opts) }
}
func (s *PrepSubsystem) cmdOrchestrator(ctx context.Context, _ core.Options) core.Result {
func (s *PrepSubsystem) cmdOrchestrator(_ core.Options) core.Result {
ctx := s.commandContext()
core.Print(nil, "core-agent orchestrator running (pid %s)", core.Env("PID"))
core.Print(nil, " workspace: %s", WorkspaceRoot())
core.Print(nil, " watching queue, draining on 30s tick + completion poke")

View file

@ -640,7 +640,8 @@ func TestCommands_CmdRunTask_Bad_MissingArgs(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
r := s.cmdRunTask(ctx, core.NewOptions())
s.commandCtx = ctx
r := s.cmdRunTask(core.NewOptions())
assert.False(t, r.OK)
}
@ -648,7 +649,8 @@ func TestCommands_CmdRunTask_Bad_MissingTask(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
r := s.cmdRunTask(ctx, core.NewOptions(core.Option{Key: "repo", Value: "go-io"}))
s.commandCtx = ctx
r := s.cmdRunTask(core.NewOptions(core.Option{Key: "repo", Value: "go-io"}))
assert.False(t, r.OK)
}
@ -656,7 +658,8 @@ func TestCommands_CmdOrchestrator_Good_CancelledCtx(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancel immediately
r := s.cmdOrchestrator(ctx, core.NewOptions())
s.commandCtx = ctx
r := s.cmdOrchestrator(core.NewOptions())
assert.True(t, r.OK)
}
@ -719,7 +722,8 @@ func TestCommands_CmdOrchestrator_Bad_DoneContext(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second))
defer cancel()
r := s.cmdOrchestrator(ctx, core.NewOptions())
s.commandCtx = ctx
r := s.cmdOrchestrator(core.NewOptions())
assert.True(t, r.OK) // returns OK after ctx.Done()
}
@ -727,7 +731,8 @@ func TestCommands_CmdOrchestrator_Ugly_CancelledImmediately(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
cancel()
r := s.cmdOrchestrator(ctx, core.NewOptions())
s.commandCtx = ctx
r := s.cmdOrchestrator(core.NewOptions())
assert.True(t, r.OK) // exits immediately when context is already cancelled
}
@ -770,8 +775,9 @@ func TestCommands_CmdRunTask_Good_DefaultsApplied(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.commandCtx = ctx
// Provide repo + task but omit agent + org — tests that defaults (codex, core) are applied
r := s.cmdRunTask(ctx, core.NewOptions(
r := s.cmdRunTask(core.NewOptions(
core.Option{Key: "repo", Value: "go-io"},
core.Option{Key: "task", Value: "run all tests"},
))
@ -783,7 +789,8 @@ func TestCommands_CmdRunTask_Ugly_MixedIssueString(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
r := s.cmdRunTask(ctx, core.NewOptions(
s.commandCtx = ctx
r := s.cmdRunTask(core.NewOptions(
core.Option{Key: "repo", Value: "go-io"},
core.Option{Key: "task", Value: "fix it"},
core.Option{Key: "issue", Value: "issue-42abc"},
@ -792,65 +799,28 @@ func TestCommands_CmdRunTask_Ugly_MixedIssueString(t *testing.T) {
assert.False(t, r.OK)
}
// --- CmdRunTaskFactory Good/Bad/Ugly ---
// --- CommandContext Good/Bad/Ugly ---
func TestCommands_CmdRunTaskFactory_Good(t *testing.T) {
func TestCommands_CommandContext_Good_StoredStartupContext(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fn := s.cmdRunTaskFactory(ctx)
assert.NotNil(t, fn, "factory should return a non-nil func")
s.registerCommands(ctx)
assert.Same(t, ctx, s.commandContext())
}
func TestCommands_CmdRunTaskFactory_Bad(t *testing.T) {
func TestCommands_CommandContext_Bad_FallsBackToBackground(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancelled ctx
fn := s.cmdRunTaskFactory(ctx)
assert.NotNil(t, fn, "factory should return a func even with cancelled ctx")
assert.NotNil(t, s.commandContext())
assert.NoError(t, s.commandContext().Err())
}
func TestCommands_CmdRunTaskFactory_Ugly(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fn := s.cmdRunTaskFactory(ctx)
// Call with empty options — should fail gracefully (missing repo+task)
r := fn(core.NewOptions())
assert.False(t, r.OK)
}
// --- CmdOrchestratorFactory Good/Bad/Ugly ---
func TestCommands_CmdOrchestratorFactory_Good(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fn := s.cmdOrchestratorFactory(ctx)
assert.NotNil(t, fn, "factory should return a non-nil func")
}
func TestCommands_CmdOrchestratorFactory_Bad(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
cancel() // cancelled ctx
fn := s.cmdOrchestratorFactory(ctx)
assert.NotNil(t, fn, "factory should return a func even with cancelled ctx")
}
func TestCommands_CmdOrchestratorFactory_Ugly(t *testing.T) {
func TestCommands_CommandContext_Ugly_CancelledStartupContext(t *testing.T) {
s, _ := testPrepWithCore(t, nil)
ctx, cancel := context.WithCancel(context.Background())
cancel() // pre-cancelled
fn := s.cmdOrchestratorFactory(ctx)
// Calling the factory result with a cancelled ctx should return OK (exits immediately)
r := fn(core.NewOptions())
s.commandCtx = ctx
r := s.cmdOrchestrator(core.NewOptions())
assert.True(t, r.OK)
}

View file

@ -33,6 +33,7 @@ type PrepSubsystem struct {
brainURL string
brainKey string
codePath string
commandCtx context.Context
dispatchMu sync.Mutex // serialises concurrency check + spawn
drainMu sync.Mutex
pokeCh chan struct{}