diff --git a/pkg/agentic/commands_workspace.go b/pkg/agentic/commands_workspace.go index 10be260..68cc79a 100644 --- a/pkg/agentic/commands_workspace.go +++ b/pkg/agentic/commands_workspace.go @@ -13,6 +13,9 @@ func (s *PrepSubsystem) registerWorkspaceCommands() { c.Command("workspace/list", core.Command{Description: "List all agent workspaces with status", Action: s.cmdWorkspaceList}) c.Command("workspace/clean", core.Command{Description: "Remove completed/failed/blocked workspaces", Action: s.cmdWorkspaceClean}) c.Command("workspace/dispatch", core.Command{Description: "Dispatch an agent to work on a repo task", Action: s.cmdWorkspaceDispatch}) + c.Command("workspace/watch", core.Command{Description: "Watch workspaces until they complete", Action: s.cmdWorkspaceWatch}) + c.Command("watch", core.Command{Description: "Watch workspaces until they complete", Action: s.cmdWorkspaceWatch}) + c.Command("agentic:watch", core.Command{Description: "Watch workspaces until they complete", Action: s.cmdWorkspaceWatch}) } func (s *PrepSubsystem) cmdWorkspaceList(_ core.Options) core.Result { @@ -117,6 +120,27 @@ func (s *PrepSubsystem) cmdWorkspaceDispatch(options core.Options) core.Result { return core.Result{OK: true} } +func (s *PrepSubsystem) cmdWorkspaceWatch(options core.Options) core.Result { + watchOptions := core.NewOptions(options.Items()...) + if watchOptions.String("workspace") == "" && len(optionStringSliceValue(watchOptions, "workspaces")) == 0 { + if workspace := optionStringValue(options, "_arg"); workspace != "" { + watchOptions.Set("workspace", workspace) + } + } + input := watchInputFromOptions(watchOptions) + + _, output, err := s.watch(s.commandContext(), nil, input) + if err != nil { + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "completed: %d", len(output.Completed)) + core.Print(nil, "failed: %d", len(output.Failed)) + core.Print(nil, "duration: %s", output.Duration) + return core.Result{Value: output, OK: output.Success} +} + func workspaceDispatchInputFromOptions(options core.Options) DispatchInput { dispatchOptions := core.NewOptions(options.Items()...) if dispatchOptions.String("repo") == "" { diff --git a/pkg/agentic/commands_workspace_test.go b/pkg/agentic/commands_workspace_test.go index 160e98a..d7bfdb8 100644 --- a/pkg/agentic/commands_workspace_test.go +++ b/pkg/agentic/commands_workspace_test.go @@ -158,6 +158,38 @@ func TestCommandsworkspace_CmdWorkspaceDispatch_Ugly_AllFieldsSet(t *testing.T) assert.False(t, r.OK) } +func TestCommandsworkspace_CmdWorkspaceWatch_Good_ExplicitWorkspaceCompletes(t *testing.T) { + root := t.TempDir() + t.Setenv("CORE_WORKSPACE", root) + + writeWatchStatus(root, "core/go-io/task-42", WorkspaceStatus{ + Status: "ready-for-review", + Repo: "go-io", + Agent: "codex", + PRURL: "https://forge.lthn.ai/core/go-io/pulls/42", + }) + + c := core.New() + s := &PrepSubsystem{ + ServiceRuntime: core.NewServiceRuntime(c, AgentOptions{}), + backoff: make(map[string]time.Time), + failCount: make(map[string]int), + } + + r := s.cmdWorkspaceWatch(core.NewOptions( + core.Option{Key: "workspace", Value: "core/go-io/task-42"}, + core.Option{Key: "poll_interval", Value: 1}, + core.Option{Key: "timeout", Value: 2}, + )) + assert.True(t, r.OK) + + output, ok := r.Value.(WatchOutput) + assert.True(t, ok) + assert.True(t, output.Success) + assert.Len(t, output.Completed, 1) + assert.Equal(t, "core/go-io/task-42", output.Completed[0].Workspace) +} + func TestCommandsworkspace_WorkspaceDispatchInputFromOptions_Good_MapsFullContract(t *testing.T) { input := workspaceDispatchInputFromOptions(core.NewOptions( core.Option{Key: "_arg", Value: "go-io"}, diff --git a/pkg/agentic/prep_test.go b/pkg/agentic/prep_test.go index 6c0c753..62d67d6 100644 --- a/pkg/agentic/prep_test.go +++ b/pkg/agentic/prep_test.go @@ -658,6 +658,9 @@ func TestPrep_OnStartup_Good_RegistersGenerateCommand(t *testing.T) { assert.Contains(t, c.Commands(), "dispatch/sync") assert.Contains(t, c.Commands(), "agentic:plan") assert.Contains(t, c.Commands(), "prep-workspace") + assert.Contains(t, c.Commands(), "watch") + assert.Contains(t, c.Commands(), "workspace/watch") + assert.Contains(t, c.Commands(), "agentic:watch") assert.Contains(t, c.Commands(), "brain/ingest") assert.Contains(t, c.Commands(), "brain/seed-memory") assert.Contains(t, c.Commands(), "brain/list")