Add orch control commands
This commit is contained in:
@@ -879,3 +879,424 @@ func TestOrchWaitTimesOutWithoutMatchingEvent(t *testing.T) {
|
||||
t.Fatalf("expected next_event_id 0 on timeout, got %#v", nextEventID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchRetryCreatesNewAttempt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
repoPath := initGitRepo(t)
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"run", "init",
|
||||
"--run", "run_blog_retry_001",
|
||||
"--goal", "Validate retry behavior",
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"task", "add",
|
||||
"--run", "run_blog_retry_001",
|
||||
"--task", "T1",
|
||||
"--title", "Implement backend",
|
||||
"--default-to", "worker-a",
|
||||
)
|
||||
|
||||
dispatchOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"dispatch",
|
||||
"--run", "run_blog_retry_001",
|
||||
"--task", "T1",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
mustDecodeJSON(t, dispatchOut, &dispatchResp)
|
||||
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
|
||||
firstWorktreePath := nestedString(t, dispatchResp, "data", "attempt", "worktree_path")
|
||||
|
||||
runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"claim",
|
||||
"--agent", "worker-a",
|
||||
"--thread", threadID,
|
||||
)
|
||||
runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"fail",
|
||||
"--agent", "worker-a",
|
||||
"--thread", threadID,
|
||||
"--summary", "Build failed",
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"reconcile",
|
||||
"--run", "run_blog_retry_001",
|
||||
)
|
||||
|
||||
retryOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"retry",
|
||||
"--run", "run_blog_retry_001",
|
||||
"--task", "T1",
|
||||
"--body", "Retry after fixing the failure.",
|
||||
)
|
||||
|
||||
var retryResp map[string]any
|
||||
mustDecodeJSON(t, retryOut, &retryResp)
|
||||
if got := nestedString(t, retryResp, "data", "task", "status"); got != "dispatched" {
|
||||
t.Fatalf("expected retried task to be dispatched, got %q", got)
|
||||
}
|
||||
if got := nestedValue(t, retryResp, "data", "attempt", "attempt_no").(float64); got != 2 {
|
||||
t.Fatalf("expected retry attempt 2, got %#v", got)
|
||||
}
|
||||
secondThreadID := nestedString(t, retryResp, "data", "attempt", "thread_id")
|
||||
if secondThreadID == threadID {
|
||||
t.Fatalf("expected retry to create a new thread, got same thread %q", secondThreadID)
|
||||
}
|
||||
secondWorktreePath := nestedString(t, retryResp, "data", "attempt", "worktree_path")
|
||||
if secondWorktreePath == firstWorktreePath {
|
||||
t.Fatalf("expected retry to create a new worktree, got reused path %q", secondWorktreePath)
|
||||
}
|
||||
if _, err := os.Stat(secondWorktreePath); err != nil {
|
||||
t.Fatalf("stat retry worktree %s: %v", secondWorktreePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchReassignCancelsOldThreadAndDispatchesNewAttempt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
repoPath := initGitRepo(t)
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"run", "init",
|
||||
"--run", "run_blog_reassign_001",
|
||||
"--goal", "Validate reassign behavior",
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"task", "add",
|
||||
"--run", "run_blog_reassign_001",
|
||||
"--task", "T1",
|
||||
"--title", "Implement backend",
|
||||
"--default-to", "worker-a",
|
||||
)
|
||||
|
||||
dispatchOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"dispatch",
|
||||
"--run", "run_blog_reassign_001",
|
||||
"--task", "T1",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
mustDecodeJSON(t, dispatchOut, &dispatchResp)
|
||||
originalThreadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
|
||||
|
||||
runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"claim",
|
||||
"--agent", "worker-a",
|
||||
"--thread", originalThreadID,
|
||||
)
|
||||
runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"update",
|
||||
"--agent", "worker-a",
|
||||
"--thread", originalThreadID,
|
||||
"--status", "blocked",
|
||||
"--summary", "Need product decision",
|
||||
"--payload-json", `{"question":"Proceed with v1 scope?"}`,
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"reconcile",
|
||||
"--run", "run_blog_reassign_001",
|
||||
)
|
||||
|
||||
reassignOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"reassign",
|
||||
"--run", "run_blog_reassign_001",
|
||||
"--task", "T1",
|
||||
"--to", "worker-b",
|
||||
"--reason", "Try another worker with clearer ownership.",
|
||||
)
|
||||
|
||||
var reassignResp map[string]any
|
||||
mustDecodeJSON(t, reassignOut, &reassignResp)
|
||||
if got := nestedString(t, reassignResp, "data", "attempt", "assigned_to"); got != "worker-b" {
|
||||
t.Fatalf("expected reassigned attempt to target worker-b, got %q", got)
|
||||
}
|
||||
if got := nestedValue(t, reassignResp, "data", "attempt", "attempt_no").(float64); got != 2 {
|
||||
t.Fatalf("expected reassign attempt 2, got %#v", got)
|
||||
}
|
||||
newThreadID := nestedString(t, reassignResp, "data", "attempt", "thread_id")
|
||||
if newThreadID == originalThreadID {
|
||||
t.Fatalf("expected reassignment to create a new thread, got %q", newThreadID)
|
||||
}
|
||||
|
||||
showOut := runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"show",
|
||||
"--thread", originalThreadID,
|
||||
)
|
||||
|
||||
var showResp map[string]any
|
||||
mustDecodeJSON(t, showOut, &showResp)
|
||||
if got := nestedString(t, showResp, "data", "thread", "status"); got != "cancelled" {
|
||||
t.Fatalf("expected old reassigned thread to be cancelled, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchCancelTaskAndRun(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"run", "init",
|
||||
"--run", "run_blog_cancel_001",
|
||||
"--goal", "Validate cancel behavior",
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"task", "add",
|
||||
"--run", "run_blog_cancel_001",
|
||||
"--task", "T1",
|
||||
"--title", "Implement backend",
|
||||
"--default-to", "worker-a",
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"task", "add",
|
||||
"--run", "run_blog_cancel_001",
|
||||
"--task", "T2",
|
||||
"--title", "Implement frontend",
|
||||
"--default-to", "worker-b",
|
||||
)
|
||||
|
||||
dispatchOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"dispatch",
|
||||
"--run", "run_blog_cancel_001",
|
||||
"--task", "T1",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
mustDecodeJSON(t, dispatchOut, &dispatchResp)
|
||||
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"cancel",
|
||||
"--run", "run_blog_cancel_001",
|
||||
"--task", "T1",
|
||||
"--reason", "Task is no longer needed.",
|
||||
)
|
||||
|
||||
statusOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"status",
|
||||
"--run", "run_blog_cancel_001",
|
||||
)
|
||||
|
||||
var statusResp map[string]any
|
||||
mustDecodeJSON(t, statusOut, &statusResp)
|
||||
tasks := nestedArray(t, statusResp, "data", "tasks")
|
||||
taskStatuses := map[string]string{}
|
||||
for _, item := range tasks {
|
||||
task, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected task object, got %#v", item)
|
||||
}
|
||||
taskStatuses[task["task_id"].(string)] = task["status"].(string)
|
||||
}
|
||||
if taskStatuses["T1"] != "cancelled" {
|
||||
t.Fatalf("expected T1 cancelled, got %q", taskStatuses["T1"])
|
||||
}
|
||||
if taskStatuses["T2"] == "cancelled" {
|
||||
t.Fatalf("expected T2 to remain active before run cancel, got %q", taskStatuses["T2"])
|
||||
}
|
||||
|
||||
showOut := runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"show",
|
||||
"--thread", threadID,
|
||||
)
|
||||
|
||||
var showResp map[string]any
|
||||
mustDecodeJSON(t, showOut, &showResp)
|
||||
if got := nestedString(t, showResp, "data", "thread", "status"); got != "cancelled" {
|
||||
t.Fatalf("expected cancelled task thread to be cancelled, got %q", got)
|
||||
}
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"cancel",
|
||||
"--run", "run_blog_cancel_001",
|
||||
"--reason", "Stop the run.",
|
||||
)
|
||||
|
||||
statusOut = runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"status",
|
||||
"--run", "run_blog_cancel_001",
|
||||
)
|
||||
mustDecodeJSON(t, statusOut, &statusResp)
|
||||
if got := nestedString(t, statusResp, "data", "run", "status"); got != "cancelled" {
|
||||
t.Fatalf("expected cancelled run, got %q", got)
|
||||
}
|
||||
tasks = nestedArray(t, statusResp, "data", "tasks")
|
||||
for _, item := range tasks {
|
||||
task, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected task object, got %#v", item)
|
||||
}
|
||||
if got, _ := task["status"].(string); got != "cancelled" {
|
||||
t.Fatalf("expected all tasks cancelled after run cancel, got %#v", task["status"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchCleanupRemovesCompletedWorktree(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
repoPath := initGitRepo(t)
|
||||
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"run", "init",
|
||||
"--run", "run_blog_cleanup_001",
|
||||
"--goal", "Validate cleanup behavior",
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"task", "add",
|
||||
"--run", "run_blog_cleanup_001",
|
||||
"--task", "T1",
|
||||
"--title", "Implement backend",
|
||||
"--default-to", "worker-a",
|
||||
)
|
||||
|
||||
dispatchOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"dispatch",
|
||||
"--run", "run_blog_cleanup_001",
|
||||
"--task", "T1",
|
||||
"--repo-path", repoPath,
|
||||
"--workspace-root", ".orch/worktrees",
|
||||
"--strict-worktree",
|
||||
)
|
||||
|
||||
var dispatchResp map[string]any
|
||||
mustDecodeJSON(t, dispatchOut, &dispatchResp)
|
||||
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
|
||||
worktreePath := nestedString(t, dispatchResp, "data", "attempt", "worktree_path")
|
||||
|
||||
runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"claim",
|
||||
"--agent", "worker-a",
|
||||
"--thread", threadID,
|
||||
)
|
||||
runInboxCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"done",
|
||||
"--agent", "worker-a",
|
||||
"--thread", threadID,
|
||||
"--summary", "Backend complete",
|
||||
)
|
||||
runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"reconcile",
|
||||
"--run", "run_blog_cleanup_001",
|
||||
)
|
||||
|
||||
cleanupOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"cleanup",
|
||||
"--run", "run_blog_cleanup_001",
|
||||
"--task", "T1",
|
||||
)
|
||||
|
||||
var cleanupResp map[string]any
|
||||
mustDecodeJSON(t, cleanupOut, &cleanupResp)
|
||||
cleaned := nestedArray(t, cleanupResp, "data", "cleaned")
|
||||
if len(cleaned) != 1 {
|
||||
t.Fatalf("expected one cleaned attempt, got %#v", cleaned)
|
||||
}
|
||||
if _, err := os.Stat(worktreePath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected cleaned worktree path to be removed, err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user