From 3a9834ec03fe18208e71bdb68f563d3ed7d77ead Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 20:59:52 +0000 Subject: [PATCH] feat(agentic): add resume command alias Co-Authored-By: Virgil --- pkg/agentic/commands.go | 36 +++++++++++++++ pkg/agentic/commands_resume_test.go | 71 +++++++++++++++++++++++++++++ pkg/agentic/commands_test.go | 2 + 3 files changed, 109 insertions(+) create mode 100644 pkg/agentic/commands_resume_test.go diff --git a/pkg/agentic/commands.go b/pkg/agentic/commands.go index edddf2e..61a604f 100644 --- a/pkg/agentic/commands.go +++ b/pkg/agentic/commands.go @@ -28,6 +28,8 @@ func (s *PrepSubsystem) registerCommands(ctx context.Context) { c.Command("prep", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep}) c.Command("prep-workspace", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep}) c.Command("agentic:prep-workspace", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep}) + c.Command("resume", core.Command{Description: "Resume a blocked or completed workspace", Action: s.cmdResume}) + c.Command("agentic:resume", core.Command{Description: "Resume a blocked or completed workspace", Action: s.cmdResume}) c.Command("generate", core.Command{Description: "Generate content from a prompt using the platform content pipeline", Action: s.cmdGenerate}) c.Command("agentic:generate", core.Command{Description: "Generate content from a prompt using the platform content pipeline", Action: s.cmdGenerate}) c.Command("complete", core.Command{Description: "Run the completion pipeline (QA → PR → Verify → Ingest → Poke)", Action: s.cmdComplete}) @@ -243,6 +245,40 @@ func (s *PrepSubsystem) cmdPrep(options core.Options) core.Result { return core.Result{OK: true} } +func (s *PrepSubsystem) cmdResume(options core.Options) core.Result { + workspace := optionStringValue(options, "workspace", "_arg") + if workspace == "" { + core.Print(nil, "usage: core-agent resume [--answer=\"...\"] [--agent=codex] [--dry-run]") + return core.Result{Value: core.E("agentic.cmdResume", "workspace is required", nil), OK: false} + } + + _, output, err := s.resume(s.commandContext(), nil, ResumeInput{ + Workspace: workspace, + Answer: optionStringValue(options, "answer"), + Agent: optionStringValue(options, "agent"), + DryRun: optionBoolValue(options, "dry_run", "dry-run"), + }) + if err != nil { + core.Print(nil, "error: %v", err) + return core.Result{Value: err, OK: false} + } + + core.Print(nil, "workspace: %s", output.Workspace) + core.Print(nil, "agent: %s", output.Agent) + if output.PID > 0 { + core.Print(nil, "pid: %d", output.PID) + } + if output.OutputFile != "" { + core.Print(nil, "output: %s", output.OutputFile) + } + if output.Prompt != "" { + core.Print(nil, "") + core.Print(nil, "--- prompt (%d chars) ---", len(output.Prompt)) + core.Print(nil, "%s", output.Prompt) + } + return core.Result{Value: output, OK: true} +} + func (s *PrepSubsystem) cmdGenerate(options core.Options) core.Result { prompt := optionStringValue(options, "prompt", "_arg") briefID := optionStringValue(options, "brief_id", "brief-id") diff --git a/pkg/agentic/commands_resume_test.go b/pkg/agentic/commands_resume_test.go new file mode 100644 index 0000000..5fd4909 --- /dev/null +++ b/pkg/agentic/commands_resume_test.go @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package agentic + +import ( + "testing" + + core "dappco.re/go/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCommandsResume_CmdResume_Good_DryRun(t *testing.T) { + s, _ := testPrepWithCore(t, nil) + + workspaceDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-42") + require.True(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo", ".git")).OK) + require.True(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), core.JSONMarshalString(WorkspaceStatus{ + Status: "blocked", + Agent: "codex", + Repo: "go-io", + Task: "Fix the failing tests", + })).OK) + + result := s.cmdResume(core.NewOptions( + core.Option{Key: "workspace", Value: "core/go-io/task-42"}, + core.Option{Key: "answer", Value: "Use the new Core API"}, + core.Option{Key: "dry_run", Value: true}, + )) + require.True(t, result.OK) + + output, ok := result.Value.(ResumeOutput) + require.True(t, ok) + assert.Equal(t, "core/go-io/task-42", output.Workspace) + assert.Equal(t, "codex", output.Agent) + assert.Contains(t, output.Prompt, "Fix the failing tests") + assert.Contains(t, output.Prompt, "Use the new Core API") +} + +func TestCommandsResume_CmdResume_Bad_MissingWorkspace(t *testing.T) { + s, _ := testPrepWithCore(t, nil) + + result := s.cmdResume(core.NewOptions()) + + assert.False(t, result.OK) + require.Error(t, result.Value.(error)) + assert.Contains(t, result.Value.(error).Error(), "workspace is required") +} + +func TestCommandsResume_CmdResume_Ugly_CorruptStatus(t *testing.T) { + s, _ := testPrepWithCore(t, nil) + + workspaceDir := core.JoinPath(WorkspaceRoot(), "core", "go-io", "task-42") + require.True(t, fs.EnsureDir(core.JoinPath(workspaceDir, "repo", ".git")).OK) + require.True(t, fs.Write(core.JoinPath(workspaceDir, "status.json"), "{broken json").OK) + + result := s.cmdResume(core.NewOptions(core.Option{Key: "_arg", Value: "core/go-io/task-42"})) + + assert.False(t, result.OK) + require.Error(t, result.Value.(error)) + assert.Contains(t, result.Value.(error).Error(), "no status.json in workspace") +} + +func TestCommandsResume_RegisterCommands_Good(t *testing.T) { + s, c := testPrepWithCore(t, nil) + + s.registerCommands(c.Context()) + + assert.Contains(t, c.Commands(), "resume") + assert.Contains(t, c.Commands(), "agentic:resume") +} diff --git a/pkg/agentic/commands_test.go b/pkg/agentic/commands_test.go index 3a4225d..88636e6 100644 --- a/pkg/agentic/commands_test.go +++ b/pkg/agentic/commands_test.go @@ -1282,6 +1282,8 @@ func TestCommands_RegisterCommands_Good_AllRegistered(t *testing.T) { assert.Contains(t, cmds, "dispatch/shutdown-now") assert.Contains(t, cmds, "prep") assert.Contains(t, cmds, "agentic:prep-workspace") + assert.Contains(t, cmds, "resume") + assert.Contains(t, cmds, "agentic:resume") assert.Contains(t, cmds, "complete") assert.Contains(t, cmds, "scan") assert.Contains(t, cmds, "agentic:scan")