feat(agentic): expose workspace watch command

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 20:43:42 +00:00
parent dbbc09b09c
commit 02aea97b7d
3 changed files with 59 additions and 0 deletions

View file

@ -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") == "" {

View file

@ -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"},

View file

@ -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")