@tea-agent/loop-agent 0.8.0 → 0.10.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/AGENTS.md +10 -0
  2. package/CHANGELOG.md +101 -1
  3. package/README.md +69 -5
  4. package/dist/application/dag/args.js +13 -16
  5. package/dist/application/dag/generate-task-dag.js +32 -2
  6. package/dist/application/dag/run-dag.js +1 -27
  7. package/dist/application/dag/validate-dag.js +2 -2
  8. package/dist/application/loop/run-action.js +0 -4
  9. package/dist/cli/command-definitions.js +7 -11
  10. package/dist/cli/program.js +9 -21
  11. package/dist/commands/cursor-prompt.js +42 -82
  12. package/dist/commands/dag-approve.js +36 -0
  13. package/dist/commands/dag-reconcile-run.js +118 -0
  14. package/dist/commands/delegate.js +75 -77
  15. package/dist/commands/doctor.js +0 -18
  16. package/dist/commands/init.js +60 -40
  17. package/dist/commands/instructions.js +7 -10
  18. package/dist/commands/loop.js +4 -20
  19. package/dist/executors/config-core.js +0 -51
  20. package/dist/executors/dag-pi-executor.js +1 -1
  21. package/dist/executors/dag.js +0 -1
  22. package/dist/executors/index.js +0 -2
  23. package/dist/executors/model-routing.js +9 -9
  24. package/dist/executors/shell-executor.js +75 -9
  25. package/dist/governance/checks.js +6 -3
  26. package/dist/governance/manifest-types.js +33 -2
  27. package/dist/infrastructure/harness/loop-action-store.js +0 -3
  28. package/dist/records/harvest.js +2 -23
  29. package/dist/records/one-shot-runs.js +1 -1
  30. package/dist/shared/artifacts-core.js +24 -5
  31. package/dist/shared/output-truncation.js +37 -0
  32. package/dist/shared/package-metadata.js +353 -0
  33. package/dist/shared/reference-context.js +48 -22
  34. package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  35. package/dist/sidecars/cursor-prompt/index.js +3 -0
  36. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  37. package/dist/task/config-types.js +30 -13
  38. package/dist/task/delegate.js +9 -21
  39. package/dist/task/runtime.js +2 -3
  40. package/dist/worker/cli.js +243 -0
  41. package/dist/worker/closeout/apply.js +73 -0
  42. package/dist/worker/closeout/preview.js +30 -0
  43. package/dist/worker/delivery/final-verification.js +194 -0
  44. package/dist/worker/delivery/git-transaction.js +354 -0
  45. package/dist/worker/delivery/package.js +502 -0
  46. package/dist/worker/feature/decision-loader.js +68 -0
  47. package/dist/worker/feature/discover.js +14 -0
  48. package/dist/worker/feature/next-action.js +74 -0
  49. package/dist/worker/feature/reducer.js +133 -0
  50. package/dist/worker/feature/review.js +502 -0
  51. package/dist/worker/feature/run.js +365 -0
  52. package/dist/worker/feature/types.js +1 -0
  53. package/dist/worker/follow-up/approve.js +270 -0
  54. package/dist/worker/follow-up/factory.js +234 -0
  55. package/dist/worker/follow-up/paths.js +25 -0
  56. package/dist/worker/follow-up/policy.js +26 -0
  57. package/dist/worker/follow-up/schema.js +93 -0
  58. package/dist/worker/follow-up/store.js +96 -0
  59. package/dist/worker/loop-agent/loop-agent-client.js +345 -101
  60. package/dist/worker/metrics/projector.js +139 -0
  61. package/dist/worker/observability/read-model.js +282 -15
  62. package/dist/worker/observe/paths.js +17 -5
  63. package/dist/worker/observe/routes.js +78 -20
  64. package/dist/worker/observe/server.js +8 -6
  65. package/dist/worker/observe/static/app.js +1045 -177
  66. package/dist/worker/observe/static/index.html +70 -43
  67. package/dist/worker/observe/static/styles.css +553 -610
  68. package/dist/worker/pool/run-store.js +14 -2
  69. package/dist/worker/pool/validation.js +59 -0
  70. package/dist/worker/preflight.js +49 -1
  71. package/dist/worker/report/morning-report.js +41 -6
  72. package/dist/worker/run-task/run-task.js +23 -13
  73. package/dist/worker/runner/run-ready.js +89 -11
  74. package/dist/worker/task-spec/schema.js +0 -1
  75. package/dist/workflows/dag/convergence/controller.js +1 -1
  76. package/dist/workflows/dag/executor-registry.js +0 -2
  77. package/dist/workflows/dag/governance-profile.js +10 -0
  78. package/dist/workflows/dag/init-hybrid.js +601 -26
  79. package/dist/workflows/dag/lifecycle.js +146 -0
  80. package/dist/workflows/dag/node-execution.js +64 -7
  81. package/dist/workflows/dag/prompt.js +16 -0
  82. package/dist/workflows/dag/report.js +2 -0
  83. package/dist/workflows/dag/runner.js +176 -119
  84. package/dist/workflows/dag/scheduler.js +7 -2
  85. package/dist/workflows/dag/skill-snapshot.js +527 -0
  86. package/dist/workflows/dag/types.js +45 -9
  87. package/dist/workflows/dag/validate.js +5 -8
  88. package/dist/workflows/loop/actions/dag-action.js +0 -2
  89. package/dist/workflows/loop/actions/shared.js +1 -1
  90. package/dist/workflows/loop/actions.js +14 -31
  91. package/dist/workflows/loop/benchmark.js +1 -1
  92. package/dist/workflows/loop/index.js +1 -1
  93. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  94. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  95. package/docs/README.md +35 -12
  96. package/docs/agent-dag-recovery-playbook.md +1 -1
  97. package/docs/architecture/README.md +26 -0
  98. package/docs/architecture/dag-execution.md +134 -0
  99. package/docs/architecture/evolution.md +52 -0
  100. package/docs/architecture/facts-and-state.md +58 -0
  101. package/docs/architecture/runtime-boundaries.md +41 -15
  102. package/docs/architecture/system-overview.md +93 -0
  103. package/docs/architecture/worker-and-feature.md +81 -0
  104. package/docs/cursor-prompt-sidecar.md +36 -0
  105. package/docs/decisions/README.md +13 -1
  106. package/docs/design/README.md +39 -13
  107. package/docs/development-principles.md +1 -1
  108. package/docs/exec-plans/active/README.md +2 -2
  109. package/docs/exec-plans/completed/README.md +21 -0
  110. package/docs/feature-workflow.md +44 -4
  111. package/docs/init-surface.manifest.json +63 -1
  112. package/docs/loop-agent-harness.md +65 -3
  113. package/docs/progress/README.md +27 -0
  114. package/docs/reports/README.md +74 -5
  115. package/docs/skills/README.md +2 -1
  116. package/docs/skills/vetted-skill-registry.md +2 -1
  117. package/docs/templates/agent-dag-report.schema.json +4 -2
  118. package/docs/templates/agent-dag.base.json +0 -5
  119. package/docs/templates/agent-dag.final-verification.json +0 -5
  120. package/docs/templates/agent-dag.schema.json +1 -2
  121. package/docs/templates/agent-dag.supervised-implementation.json +1 -6
  122. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
  123. package/docs/templates/backend-test-dag.json +213 -0
  124. package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
  125. package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -0
  126. package/docs/templates/frontend-design-contract.md +33 -0
  127. package/docs/templates/frontend-task-constraints.md +25 -0
  128. package/docs/templates/frontend-task-requirement.md +61 -0
  129. package/docs/templates/harness.schema.json +8 -5
  130. package/docs/templates/hybrid-dag.json +1 -6
  131. package/docs/templates/init-evolution-review.md +4 -2
  132. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  133. package/docs/templates/product-line/task.yaml +0 -1
  134. package/docs/templates/worker-dogfood-evidence.md +28 -0
  135. package/docs/templates/worker-dogfood-setup.md +20 -0
  136. package/docs/verification-matrix.md +17 -0
  137. package/examples/decision-gate-agent-dag.json +87 -33
  138. package/examples/example-dag.json +0 -5
  139. package/examples/hybrid-loop-agent-dag.json +0 -5
  140. package/harness.json +6 -11
  141. package/package.json +22 -44
  142. package/scripts/check-product-line-docs.sh +10 -3
  143. package/scripts/check-task-pool-root.sh +1 -1
  144. package/skills/agent-worker/SKILL.md +37 -0
  145. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  146. package/skills/frontend-design-review/SKILL.md +59 -0
  147. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  148. package/skills/frontend-implementation/SKILL.md +48 -0
  149. package/skills/frontend-implementation/references/code-standards.md +34 -0
  150. package/skills/frontend-implementation/references/design-spec.md +46 -0
  151. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  152. package/skills/frontend-review/SKILL.md +53 -0
  153. package/skills/frontend-review/references/review-findings.md +42 -0
  154. package/skills/frontend-verification/SKILL.md +40 -0
  155. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  156. package/skills/grill-me/SKILL.md +10 -0
  157. package/skills/grill-with-docs/SKILL.md +88 -0
  158. package/skills/grill-with-docs/adr-format.md +47 -0
  159. package/skills/grill-with-docs/context-format.md +60 -0
  160. package/skills/init-capability-evolution/SKILL.md +1 -0
  161. package/skills/loop-agent/SKILL.md +11 -9
  162. package/skills/loop-agent/references/command-reference.md +28 -15
  163. package/skills/loop-agent/references/docs-converge.md +126 -0
  164. package/skills/loop-agent/references/harness-policy.md +7 -7
  165. package/skills/loop-agent/references/hybrid-dag.md +13 -15
  166. package/skills/loop-agent/references/long-running-loop.md +4 -6
  167. package/skills/loop-agent/references/multi-worktree.md +6 -6
  168. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  169. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  170. package/skills/loop-agent/references/verification-and-failure-handling.md +8 -3
  171. package/skills/using-git-worktrees/SKILL.md +215 -0
  172. package/dist/commands/cursor-worker.js +0 -43
  173. package/dist/cursor-worker-entry.js +0 -8
  174. package/dist/executors/cursor-artifacts.js +0 -33
  175. package/dist/executors/cursor-execution-log.js +0 -81
  176. package/dist/executors/cursor-executor-artifacts.js +0 -134
  177. package/dist/executors/cursor-run.js +0 -115
  178. package/dist/executors/cursor-tool.js +0 -94
  179. package/dist/executors/cursor-worker-client.js +0 -223
  180. package/dist/executors/cursor-worker-protocol.js +0 -18
  181. package/dist/executors/cursor-worker-server.js +0 -54
  182. package/dist/executors/cursor-worker.js +0 -3
  183. package/dist/executors/cursor.js +0 -6
  184. package/dist/executors/dag-cursor-executor.js +0 -87
  185. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  186. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  187. package/docs/cursor-executor-usage.md +0 -25
  188. package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
@@ -1,15 +1,16 @@
1
- import { readFileSync } from "node:fs";
2
1
  import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
3
  import { Command } from "commander";
4
+ import { readPackageVersion } from "../shared/package-metadata.js";
4
5
  import { resolveAdapter } from "../adapters/index.js";
5
6
  import { runCloseout } from "../commands/closeout.js";
6
7
  import { runCoverageAudit } from "../commands/coverage-audit.js";
7
- import { runCursorWorker } from "../commands/cursor-worker.js";
8
8
  import { parseCursorPromptArgs, printCursorPromptUsage, runCursorPrompt, } from "../commands/cursor-prompt.js";
9
9
  import { runDagApprove } from "../commands/dag-approve.js";
10
10
  import { runDagFinalVerification } from "../commands/dag-final-verification.js";
11
11
  import { runDagInitHybrid } from "../commands/dag-init-hybrid.js";
12
12
  import { runDagReconcileTasks } from "../commands/dag-reconcile-tasks.js";
13
+ import { runDagReconcileRun } from "../commands/dag-reconcile-run.js";
13
14
  import { runDagReject } from "../commands/dag-reject.js";
14
15
  import { runDagCloseoutDraft, runDagReport } from "../commands/dag-report.js";
15
16
  import { runDagResume } from "../commands/dag-resume.js";
@@ -52,18 +53,6 @@ import { handleStandaloneOneShotCommandError } from "../shared/one-shot-prompt-a
52
53
  import { runDagDecisionInspect, runDagDecisionValidate, } from "../workflows/dag/decision-envelope.js";
53
54
  import { runDagDoctor, runDagStatus } from "../workflows/dag/lifecycle.js";
54
55
  import { CODE_AGENT_COMMAND_MANIFEST } from "./catalog.js";
55
- function readPackageVersion() {
56
- try {
57
- const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf-8");
58
- const parsed = JSON.parse(raw);
59
- if (typeof parsed.version === "string")
60
- return parsed.version;
61
- }
62
- catch {
63
- // Fall back only if the package metadata is unexpectedly unavailable.
64
- }
65
- return "0.0.0";
66
- }
67
56
  function resolveRepoRootOnly(command, defaultRepoRoot, behavior) {
68
57
  const options = collectGlobalOptions(command);
69
58
  const repoRoot = path.resolve(options.repoRoot ?? defaultRepoRoot);
@@ -230,9 +219,6 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
230
219
  case "harvest":
231
220
  await runHarvest(ctx.repoRoot, compactArgs([subcommand, ...rest]));
232
221
  return;
233
- case "cursor-worker":
234
- await runCursorWorker(ctx.repoRoot, compactArgs([subcommand, ...rest]));
235
- return;
236
222
  case "knowledge":
237
223
  await runKnowledge(ctx.repoRoot, compactArgs([subcommand, ...rest]));
238
224
  return;
@@ -300,6 +286,9 @@ async function runDagAction(repoRoot, subcommand, rest) {
300
286
  case "closeout-draft":
301
287
  await runDagCloseoutDraft(repoRoot, args);
302
288
  return;
289
+ case "reconcile-run":
290
+ await runDagReconcileRun(repoRoot, args);
291
+ return;
303
292
  case "reconcile-tasks":
304
293
  await runDagReconcileTasks(repoRoot, args);
305
294
  return;
@@ -319,7 +308,7 @@ async function runDagAction(repoRoot, subcommand, rest) {
319
308
  throw new Error("usage: dag decision <inspect|validate> --run-id <id> [--node-id <node-id>]");
320
309
  }
321
310
  default:
322
- throw new Error("usage: dag <init-hybrid|run-task|validate|workflow-plan|workflow-validate|workflow-compile|approve|reject|resume|status|doctor|report|closeout-draft|reconcile-tasks|final-verification|decision> ...");
311
+ throw new Error("usage: dag <init-hybrid|run-task|validate|workflow-plan|workflow-validate|workflow-compile|approve|reject|resume|status|doctor|report|closeout-draft|reconcile-run|reconcile-tasks|final-verification|decision> ...");
323
312
  }
324
313
  }
325
314
  function compactArgs(args) {
@@ -371,7 +360,6 @@ function configureRunDag(command) {
371
360
  .option("--dry-run", "validate and plan without executing")
372
361
  .option("--max-concurrent <n>", "maximum concurrent nodes")
373
362
  .option("--run-id <id>", "run id")
374
- .option("--no-cursor", "disable Cursor execution")
375
363
  .option("--canvas-path <path>", "absolute canvas output path")
376
364
  .option("--canvas <name>", "canvas name")
377
365
  .option("--canvases-dir <dir>", "canvas output directory")
@@ -383,7 +371,6 @@ function configureDagRunTask(command) {
383
371
  .option("-o, --output <path>", "output DAG path")
384
372
  .option("--profile <profile>", "auto|minimal|standard|reviewed|supervised")
385
373
  .option("--strict-models", "fail on model routing warnings")
386
- .option("--no-cursor", "disable Cursor execution")
387
374
  .option("--execute", "execute after creating the DAG")
388
375
  .option("--init-only", "initialize run artifacts without executing")
389
376
  .option("--dry-run", "validate and plan without executing")
@@ -450,6 +437,7 @@ function addDagCommand(program, defaultRepoRoot) {
450
437
  "doctor",
451
438
  "report",
452
439
  "closeout-draft",
440
+ "reconcile-run",
453
441
  "reconcile-tasks",
454
442
  "final-verification",
455
443
  ], defaultRepoRoot);
@@ -502,7 +490,7 @@ export function buildLoopAgentProgram(options) {
502
490
  program
503
491
  .name("loop-agent")
504
492
  .description("Loop Agent task and DAG automation CLI")
505
- .version(readPackageVersion(), "-V, --version", "display version")
493
+ .version(readPackageVersion(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))) ?? "0.0.0", "-V, --version", "display version")
506
494
  .showHelpAfterError()
507
495
  .option("--repo-root <path>", "repository root")
508
496
  .option("--adapter <name>", "harness adapter name")
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Cursor Prompt — one-shot CLI for Cursor SDK tasks.
2
+ * Cursor Prompt — one-shot CLI for Cursor SDK tasks (sidecar).
3
3
  *
4
4
  * Usage:
5
5
  * cursor-prompt "Fix lint errors" # one-shot
@@ -9,7 +9,7 @@
9
9
  * cursor-prompt --model gpt-5.5 "Deep analysis" # pick model
10
10
  * cursor-prompt --cwd /path --timeout 120000 "task" # cwd + timeout
11
11
  */
12
- import { executeCursorTask, DEFAULT_CURSOR_MODEL, DEFAULT_CURSOR_TIMEOUT_MS, buildCursorPrompt, classifyCursorFailure, mapRunStatusToExecutionStatus, } from "../executors/cursor-executor.js";
12
+ import { executeCursorTask, executeCursorPromptStream, DEFAULT_CURSOR_MODEL, DEFAULT_CURSOR_TIMEOUT_MS, } from "../sidecars/cursor-prompt/index.js";
13
13
  import { createOneShotPromptParseAccum, formatUnknownFlagError, parseOneShotCommonFlag, printOneShotHelpAndExit, resolveOneShotCwd, resolveOneShotTask, } from "../shared/one-shot-prompt-args.js";
14
14
  import { persistCursorRunLog, computeRunDir } from "../records/one-shot-runs.js";
15
15
  const CURSOR_PROMPT_USAGE = [
@@ -111,12 +111,17 @@ async function runCursorPromptBatch(task, cwd, model, timeoutMs) {
111
111
  const startedAt = Date.now();
112
112
  process.stderr.write(`[cursor-prompt] running "${task.slice(0, 80)}${task.length > 80 ? "..." : ""}" (model=${model}, cwd=${cwd})\n`);
113
113
  const runDir = (await computeRunDir(cwd, task)) ?? undefined;
114
- const result = await executeCursorTask({ task, cwd, model, timeoutMs, runDir });
115
- // Persist human-readable run log under .harness/runs/
114
+ const result = await executeCursorTask({
115
+ task,
116
+ cwd,
117
+ model,
118
+ timeoutMs,
119
+ runDir,
120
+ });
116
121
  await persistCursorRunLog(cwd, {
117
122
  task,
118
123
  model,
119
- channel: 'tool',
124
+ channel: "tool",
120
125
  cwd,
121
126
  result,
122
127
  runDir,
@@ -135,87 +140,42 @@ async function runCursorPromptBatch(task, cwd, model, timeoutMs) {
135
140
  }
136
141
  }
137
142
  async function runCursorPromptStreaming(task, cwd, model, timeoutMs) {
138
- const apiKey = process.env.CURSOR_API_KEY;
139
- if (!apiKey?.trim()) {
140
- process.stderr.write("[cursor-prompt] CURSOR_API_KEY not set\n");
141
- process.exit(1);
142
- }
143
- const runDir = (await computeRunDir(cwd, task)) ?? undefined;
144
- const prompt = buildCursorPrompt(task, { runDir });
145
143
  const startedAt = Date.now();
146
- process.stderr.write(`[cursor-prompt] streaming (model=${model}, cwd=${cwd})\n`);
147
- let timeoutHandle;
148
- try {
149
- const { Agent } = await import("@cursor/sdk");
150
- const agent = await Agent.create({
151
- apiKey,
152
- name: "pi-cursor-prompt",
153
- model: { id: model },
154
- local: { cwd },
155
- });
156
- const runPromise = (async () => {
157
- const run = await agent.send(prompt);
158
- for await (const event of run.stream()) {
159
- if (event.type === "assistant") {
160
- for (const block of event.message.content) {
161
- if (block.type === "text") {
162
- process.stdout.write(block.text);
163
- }
164
- }
165
- }
166
- }
167
- const result = await run.wait();
168
- await agent[Symbol.asyncDispose]();
169
- return result;
170
- })();
171
- const timeoutRace = new Promise((resolve) => {
172
- timeoutHandle = setTimeout(() => {
173
- resolve("timeout");
174
- }, timeoutMs);
175
- });
176
- const raced = await Promise.race([
177
- runPromise.then((v) => ({ kind: "done", value: v })),
178
- timeoutRace.then(() => ({ kind: "timeout" })),
179
- ]);
180
- if (timeoutHandle)
181
- clearTimeout(timeoutHandle);
182
- if (raced.kind === "timeout") {
183
- process.stderr.write(`\n[cursor-prompt] TIMEOUT after ${timeoutMs}ms\n`);
184
- process.exit(1);
185
- }
186
- const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
187
- const sdkResult = raced.value;
188
- process.stderr.write(`\n[cursor-prompt] done in ${elapsed}s, status=${sdkResult.status}\n`);
189
- const runStatus = sdkResult.status;
190
- const status = mapRunStatusToExecutionStatus(runStatus, false);
191
- const ok = status === 'completed';
192
- // Persist human-readable run log under .harness/runs/
193
- await persistCursorRunLog(cwd, {
194
- task,
195
- model,
196
- channel: 'tool',
197
- cwd,
198
- result: {
199
- ok,
200
- status,
201
- failureCategory: ok ? 'success' : classifyCursorFailure({ runStatus, stderr: '', stdout: '' }),
202
- model,
203
- durationMs: sdkResult.durationMs ?? Date.now() - startedAt,
204
- stdout: '', // Streaming output went directly to stdout
205
- stderr: '',
206
- },
207
- runDir,
208
- });
144
+ process.stderr.write(`[cursor-prompt] streaming (model=${model}, cwd=${cwd})
145
+ `);
146
+ const runDir = (await computeRunDir(cwd, task)) ?? undefined;
147
+ const result = await executeCursorPromptStream({
148
+ task,
149
+ cwd,
150
+ model,
151
+ timeoutMs,
152
+ runDir,
153
+ callbacks: {
154
+ onAssistantText: (text) => process.stdout.write(text),
155
+ },
156
+ });
157
+ await persistCursorRunLog(cwd, {
158
+ task,
159
+ model,
160
+ channel: "tool",
161
+ cwd,
162
+ result,
163
+ runDir,
164
+ });
165
+ const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
166
+ if (result.ok) {
167
+ process.stderr.write(`
168
+ [cursor-prompt] done in ${elapsed}s, status=${result.status}
169
+ `);
209
170
  }
210
- catch (error) {
211
- const message = error instanceof Error ? error.message : String(error);
212
- process.stderr.write(`\n[cursor-prompt] ERROR: ${message}\n`);
171
+ else {
172
+ const stderr = result.stderr || "(no output)";
173
+ process.stderr.write(`
174
+ [cursor-prompt] FAILED in ${elapsed}s (${result.failureCategory}):
175
+ ${stderr}
176
+ `);
213
177
  process.exit(1);
214
178
  }
215
- finally {
216
- if (timeoutHandle)
217
- clearTimeout(timeoutHandle);
218
- }
219
179
  }
220
180
  export function printCursorPromptUsage() {
221
181
  console.log(CURSOR_PROMPT_USAGE);
@@ -1,5 +1,7 @@
1
1
  import path from "node:path";
2
+ import { access } from "node:fs/promises";
2
3
  import { assertDagRunTransferTargetAvailable, getDagRunDir, readHumanEscalation, requirePausedDagRun, transferDagRunDir, writeDagRunState, writeHumanApprovalArtifact, } from "../workflows/dag/lifecycle.js";
4
+ import { assertSkillSnapshotCoversSpec, readSkillSnapshot, SKILL_SNAPSHOT_REL_PATH, } from "../workflows/dag/skill-snapshot.js";
3
5
  export function parseDagApproveArgs(args) {
4
6
  if (args.length === 0) {
5
7
  throw new Error("usage: dag approve --run-id <id> --option <A|B|...> [--notes \"...\"]");
@@ -59,6 +61,34 @@ export async function runDagApprove(repoRoot, rawArgs) {
59
61
  if (!optionIds.has(parsed.option)) {
60
62
  throw new Error(`option ${parsed.option} is not valid; expected one of: ${[...optionIds].join(", ")}`);
61
63
  }
64
+ // Fail closed before any approval/state/lifecycle mutation. Snapshot-backed
65
+ // runs must validate fully. A true pre-snapshot legacy run (no ref and no
66
+ // artifact) may still be approved so resume can perform the established
67
+ // legacy-resume-backfill. An artifact without a ref is an orphan and cannot
68
+ // be trusted or silently adopted.
69
+ if (state.skillSnapshotRef) {
70
+ try {
71
+ const snapshot = await readSkillSnapshot(runDir, state.skillSnapshotRef, {
72
+ expectedRunId: state.runId,
73
+ });
74
+ assertSkillSnapshotCoversSpec(snapshot, spec, state);
75
+ }
76
+ catch (error) {
77
+ throw new Error(`skill snapshot pre-approval validation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
78
+ }
79
+ }
80
+ else {
81
+ const snapshotPath = path.join(runDir, ...SKILL_SNAPSHOT_REL_PATH.split("/"));
82
+ try {
83
+ await access(snapshotPath);
84
+ throw new Error("skill snapshot artifact exists without a state ref; refusing approval");
85
+ }
86
+ catch (error) {
87
+ if (!isNotFound(error)) {
88
+ throw new Error(`skill snapshot pre-approval validation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
89
+ }
90
+ }
91
+ }
62
92
  const activeRunDir = getDagRunDir(repoRoot, "active", state.runId);
63
93
  await assertDagRunTransferTargetAvailable(activeRunDir);
64
94
  const approvedAt = new Date().toISOString();
@@ -100,3 +130,9 @@ export async function runDagApprove(repoRoot, rawArgs) {
100
130
  nextRecommendedAction: "Run is approved; use dag resume to continue.",
101
131
  }, null, 2));
102
132
  }
133
+ function isNotFound(error) {
134
+ return Boolean(error
135
+ && typeof error === "object"
136
+ && "code" in error
137
+ && error.code === "ENOENT");
138
+ }
@@ -0,0 +1,118 @@
1
+ import path from "node:path";
2
+ import { writeJsonAtomic } from "../infrastructure/harness/atomic-write.js";
3
+ import { assessDagRunLiveness, assessDagRunRecoveryEligibility, assertDagRunTransferTargetAvailable, getDagRunDir, locateDagRun, readDagRunState, transferDagRunDir, writeDagRunState, } from "../workflows/dag/lifecycle.js";
4
+ export function parseDagReconcileRunArgs(args) {
5
+ let runId;
6
+ let action;
7
+ let reason;
8
+ for (let index = 0; index < args.length; index += 1) {
9
+ const arg = args[index];
10
+ if (arg === "--run-id")
11
+ runId = args[++index];
12
+ else if (arg.startsWith("--run-id="))
13
+ runId = arg.slice("--run-id=".length);
14
+ else if (arg === "--action")
15
+ action = parseAction(args[++index]);
16
+ else if (arg.startsWith("--action="))
17
+ action = parseAction(arg.slice("--action=".length));
18
+ else if (arg === "--reason")
19
+ reason = args[++index];
20
+ else if (arg.startsWith("--reason="))
21
+ reason = arg.slice("--reason=".length);
22
+ else if (arg.startsWith("-"))
23
+ throw new Error(`unknown dag reconcile-run flag: ${arg}`);
24
+ else
25
+ throw new Error(`unexpected positional argument: ${arg}`);
26
+ }
27
+ if (!runId)
28
+ throw new Error("dag reconcile-run requires --run-id <id>");
29
+ if (action && !reason?.trim()) {
30
+ throw new Error("dag reconcile-run mutation requires --reason <text>");
31
+ }
32
+ return { runId, action, ...(reason?.trim() ? { reason: reason.trim() } : {}) };
33
+ }
34
+ function parseAction(value) {
35
+ if (value === "supersede" || value === "abandon")
36
+ return value;
37
+ throw new Error("dag reconcile-run --action must be supersede or abandon");
38
+ }
39
+ export async function runDagReconcileRun(repoRoot, rawArgs) {
40
+ const parsed = parseDagReconcileRunArgs(rawArgs);
41
+ const located = await locateDagRun(repoRoot, parsed.runId);
42
+ if (!located)
43
+ throw new Error(`dag run not found: ${parsed.runId}`);
44
+ const state = await readDagRunState(located.runDir);
45
+ const liveness = assessDagRunLiveness({ state });
46
+ const eligibility = assessDagRunRecoveryEligibility({
47
+ lifecycle: located.lifecycle,
48
+ state,
49
+ liveness: liveness.status,
50
+ });
51
+ if (!parsed.action) {
52
+ console.log(JSON.stringify({
53
+ action: "inspect",
54
+ runId: state.runId,
55
+ lifecycle: located.lifecycle,
56
+ status: state.status,
57
+ liveness: liveness.status,
58
+ eligibility,
59
+ nextRecommendedAction: eligibility.canReconcile
60
+ ? "Re-run with --action supersede|abandon and --reason <text>."
61
+ : "Do not reconcile this run; inspect runner and lifecycle evidence first.",
62
+ }, null, 2));
63
+ return;
64
+ }
65
+ if (!eligibility.canReconcile || !eligibility.allowedActions.includes(parsed.action)) {
66
+ throw new Error(`dag run ${state.runId} is not eligible for reconciliation: ${eligibility.reasons.join(", ") || "unknown reason"}`);
67
+ }
68
+ const reconciledAt = new Date().toISOString();
69
+ const completedRunDir = getDagRunDir(repoRoot, "completed", state.runId);
70
+ if (located.lifecycle !== "completed") {
71
+ await assertDagRunTransferTargetAvailable(completedRunDir);
72
+ }
73
+ const artifactRelativePath = path.join(".harness", "dag-runs", "completed", state.runId, "reconciliation.json").replace(/\\/g, "/");
74
+ await writeJsonAtomic(path.join(located.runDir, "reconciliation.json"), {
75
+ schemaVersion: 1,
76
+ runId: state.runId,
77
+ action: parsed.action,
78
+ reason: parsed.reason,
79
+ reconciledAt,
80
+ previousLifecycle: located.lifecycle,
81
+ previousStatus: state.status,
82
+ liveness: liveness.status,
83
+ originalState: state,
84
+ });
85
+ const previousStatus = state.status;
86
+ for (const node of Object.values(state.nodes)) {
87
+ if (node.status !== "RUNNING")
88
+ continue;
89
+ node.status = "ERROR";
90
+ node.finishedAt = reconciledAt;
91
+ node.failureCategory = `operator-${parsed.action}`;
92
+ }
93
+ state.status = parsed.action === "supersede" ? "superseded" : "abandoned";
94
+ state.finishedAt = reconciledAt;
95
+ state.failureCategory = `operator-${parsed.action}`;
96
+ state.reconciliation = {
97
+ action: parsed.action,
98
+ reason: parsed.reason,
99
+ reconciledAt,
100
+ previousStatus,
101
+ previousLifecycle: located.lifecycle,
102
+ liveness: liveness.status,
103
+ artifactPath: artifactRelativePath,
104
+ };
105
+ await writeDagRunState(located.runDir, state);
106
+ const runDir = located.lifecycle === "completed"
107
+ ? located.runDir
108
+ : await transferDagRunDir(located.runDir, completedRunDir);
109
+ console.log(JSON.stringify({
110
+ action: parsed.action,
111
+ runId: state.runId,
112
+ status: state.status,
113
+ lifecycle: "completed",
114
+ reason: parsed.reason,
115
+ reconciliationArtifactPath: artifactRelativePath,
116
+ runDir,
117
+ }, null, 2));
118
+ }
@@ -1,12 +1,11 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
- import { delegateTask } from '../task/delegate.js';
4
- import { runCursorDelegatedTask } from '../executors/cursor-run.js';
5
- import { buildCursorRecoverySteps } from '../executors/cursor-executor-artifacts.js';
6
- import { formatCursorFailureMessage } from '../executors/cursor-execution-log.js';
7
- import { loadHarnessManifest } from '../governance/harness.js';
8
- const DELEGATE_USAGE = 'usage: delegate <taskId> [--executor pi|cursor] [--base <branch>] [--branch <name>] '
9
- + '[--no-symlink] [--auto-run] [--no-auto-run]';
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { delegateTask } from "../task/delegate.js";
4
+ import { loadHarnessManifest } from "../governance/harness.js";
5
+ import { generateTaskDagUseCase } from "../application/dag/generate-task-dag.js";
6
+ import { validateDagUseCase } from "../application/dag/validate-dag.js";
7
+ import { runDagUseCase } from "../application/dag/run-dag.js";
8
+ const DELEGATE_USAGE = "usage: delegate <taskId> [--base <branch>] [--branch <name>] [--no-symlink] [--auto-run]";
10
9
  export function parseDelegateArgs(args) {
11
10
  if (args.length === 0) {
12
11
  throw new Error(DELEGATE_USAGE);
@@ -15,44 +14,37 @@ export function parseDelegateArgs(args) {
15
14
  let base;
16
15
  let branch;
17
16
  let symlinkNodeModules = true;
18
- let autoRun = true;
19
- let executor;
17
+ let autoRun = false;
20
18
  for (let i = 0; i < args.length; i += 1) {
21
19
  const arg = args[i];
22
- if (arg === '--base') {
20
+ if (arg === "--base") {
23
21
  base = args[i + 1];
24
22
  if (!base)
25
- throw new Error('--base requires a value');
23
+ throw new Error("--base requires a value");
26
24
  i += 1;
27
25
  }
28
- else if (arg === '--branch') {
26
+ else if (arg === "--branch") {
29
27
  branch = args[i + 1];
30
28
  if (!branch)
31
- throw new Error('--branch requires a value');
29
+ throw new Error("--branch requires a value");
32
30
  i += 1;
33
31
  }
34
- else if (arg === '--executor') {
35
- const value = args[i + 1];
36
- if (value !== 'pi' && value !== 'cursor') {
37
- throw new Error('--executor must be "pi" or "cursor"');
38
- }
39
- executor = value;
40
- i += 1;
32
+ else if (arg === "--executor") {
33
+ throw new Error("--executor was removed; delegate is worktree isolation only; use --auto-run for Pi DAG execution");
41
34
  }
42
- else if (arg === '--no-symlink') {
35
+ else if (arg === "--no-symlink") {
43
36
  symlinkNodeModules = false;
44
37
  }
45
- else if (arg === '--auto-run') {
38
+ else if (arg === "--auto-run") {
46
39
  autoRun = true;
47
40
  }
48
- else if (arg === '--no-auto-run') {
49
- autoRun = false;
41
+ else if (arg === "--no-auto-run") {
42
+ throw new Error("--no-auto-run was removed; delegate defaults to worktree-only (omit --auto-run)");
50
43
  }
51
- else if (arg === '--supervised') {
52
- throw new Error('--supervised was removed with the tmux white-box subagent lifecycle. '
53
- + 'Use `delegate <taskId> --executor cursor --auto-run` for in-process Cursor SDK.');
44
+ else if (arg === "--supervised") {
45
+ throw new Error("--supervised was removed with the tmux white-box subagent lifecycle. Use `delegate <taskId> --auto-run` for Pi DAG execution in the worktree.");
54
46
  }
55
- else if (!arg.startsWith('--')) {
47
+ else if (!arg.startsWith("--")) {
56
48
  if (taskId)
57
49
  throw new Error(`unexpected positional argument: ${arg}`);
58
50
  taskId = arg;
@@ -62,71 +54,77 @@ export function parseDelegateArgs(args) {
62
54
  }
63
55
  }
64
56
  if (!taskId) {
65
- throw new Error('delegate requires <taskId> as the first positional argument');
57
+ throw new Error("delegate requires <taskId> as the first positional argument");
66
58
  }
67
- return { taskId, base, branch, symlinkNodeModules, autoRun, executor };
59
+ return { taskId, base, branch, symlinkNodeModules, autoRun };
68
60
  }
69
- export function printCursorFailureRecovery(taskId, result, cursorResult) {
70
- console.error(`[delegate] cursor direct run failed: ${formatCursorFailureMessage(cursorResult.failureCategory)}`);
71
- console.error(` logs: .harness/tasks/${taskId}/logs/result.summary.md`);
72
- for (const line of buildCursorRecoverySteps({
73
- channel: 'delegate',
74
- taskId,
75
- sessionName: result.sessionName,
76
- worktreePath: result.worktreePath,
77
- failureCategory: cursorResult.failureCategory,
78
- })) {
79
- console.error(line);
80
- }
81
- }
82
- export function printCursorDelegateNextSteps(taskId, result) {
61
+ export function printDelegateNextSteps(taskId, result) {
62
+ const dagPath = path.join(os.tmpdir(), `${taskId}-dag.json`);
83
63
  const lines = [
84
- `[delegate] cursor worktree ready (SDK direct).`,
64
+ `[delegate] worktree ready (Pi-only).`,
85
65
  ` worktree: ${result.worktreePath}`,
86
- ` Next — run Cursor SDK in-process:`,
87
- ` npm run dev -- delegate ${taskId} --executor cursor --auto-run`,
66
+ ` Next — generate, validate, and run DAG in the worktree:`,
67
+ ` loop-agent dag run-task ${taskId} --profile auto --strict-models --output ${JSON.stringify(dagPath)}`,
68
+ ` loop-agent dag validate --dag ${JSON.stringify(dagPath)} --strict-models --strict-governance`,
69
+ ` loop-agent run-dag --dag ${JSON.stringify(dagPath)} --cwd ${JSON.stringify(result.worktreePath)}`,
70
+ ` Or: loop-agent delegate ${taskId} --auto-run`,
88
71
  ` After work completes:`,
89
- ` npm run dev -- harvest ${taskId}`,
72
+ ` loop-agent harvest ${taskId}`,
90
73
  ];
91
74
  for (const line of lines) {
92
75
  console.error(line);
93
76
  }
94
77
  }
95
- export function buildPiAutoRunMigrationMessage(taskId, worktreePath) {
96
- const dagPath = path.join(os.tmpdir(), `${taskId}-dag.json`);
97
- const cwd = worktreePath ?? '<repo-root>';
98
- return [
99
- 'delegate --executor pi --auto-run has been removed.',
100
- 'Use the DAG path instead:',
101
- ` 1. npm run dev -- dag run-task ${taskId} --profile auto --strict-models --output ${JSON.stringify(dagPath)}`,
102
- ` 2. npm run dev -- dag validate --dag ${JSON.stringify(dagPath)} --strict-models --strict-governance`,
103
- ` 3. npm run dev -- run-dag --dag ${JSON.stringify(dagPath)} --cwd ${JSON.stringify(cwd)}`,
104
- ].join('\n');
105
- }
106
- export async function runDelegate(repoRoot, rawArgs) {
78
+ const DEFAULT_RUN_DELEGATE_DEPENDENCIES = {
79
+ generateTaskDag: generateTaskDagUseCase,
80
+ validateDag: validateDagUseCase,
81
+ runDag: runDagUseCase,
82
+ };
83
+ export async function runDelegate(repoRoot, rawArgs, dependencies = DEFAULT_RUN_DELEGATE_DEPENDENCIES) {
107
84
  const parsed = parseDelegateArgs(rawArgs);
108
85
  const manifest = await loadHarnessManifest(repoRoot);
109
86
  const result = await delegateTask(repoRoot, manifest, parsed.taskId, {
110
87
  base: parsed.base,
111
88
  branch: parsed.branch,
112
89
  symlinkNodeModules: parsed.symlinkNodeModules,
113
- executor: parsed.executor,
114
90
  });
115
91
  if (parsed.autoRun) {
116
- if (result.executor === 'cursor') {
117
- const cursorResult = await runCursorDelegatedTask(result.worktreePath, parsed.taskId, manifest, {
118
- executionMode: 'direct',
119
- });
120
- if (!cursorResult.ok) {
121
- printCursorFailureRecovery(parsed.taskId, result, cursorResult);
122
- }
123
- }
124
- else if (result.executor === 'pi') {
125
- throw new Error(buildPiAutoRunMigrationMessage(parsed.taskId, result.worktreePath));
126
- }
127
- }
128
- else if (result.executor === 'cursor') {
129
- printCursorDelegateNextSteps(parsed.taskId, result);
92
+ const dagPath = path.join(os.tmpdir(), `${parsed.taskId}-delegate-dag.json`);
93
+ await dependencies.generateTaskDag({
94
+ repoRoot,
95
+ taskId: parsed.taskId,
96
+ outputPath: dagPath,
97
+ strictModels: true,
98
+ execute: false,
99
+ initOnly: false,
100
+ dryRun: false,
101
+ cwd: result.worktreePath,
102
+ profile: "auto",
103
+ profileExplicit: false,
104
+ });
105
+ await dependencies.validateDag({
106
+ repoRoot,
107
+ dagPath,
108
+ strictModelMatrix: true,
109
+ strictGovernance: true,
110
+ forbidExecutors: [],
111
+ });
112
+ const runSummary = await dependencies.runDag({
113
+ repoRoot,
114
+ dagPath,
115
+ cwd: result.worktreePath,
116
+ initOnly: false,
117
+ dryRun: false,
118
+ });
119
+ console.log(JSON.stringify({
120
+ ...result,
121
+ autoRun: {
122
+ dagPath,
123
+ runSummary,
124
+ },
125
+ }, null, 2));
126
+ return;
130
127
  }
128
+ printDelegateNextSteps(parsed.taskId, result);
131
129
  console.log(JSON.stringify(result, null, 2));
132
130
  }
@@ -2,14 +2,6 @@ import { access } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { checkPiAvailability, resolvePiBackend } from '../executors/pi-executor.js';
4
4
  import { checkPiSdkAvailability } from '../executors/pi-sdk-executor.js';
5
- import { resolveCursorExecutorConfig } from '../executors/config-core.js';
6
- import { isCursorWorkerEnabled, isCursorWorkerRunning } from '../executors/cursor-worker-client.js';
7
- function checkCursorAvailability() {
8
- if (!process.env.CURSOR_API_KEY?.trim()) {
9
- return { ok: false, detail: 'CURSOR_API_KEY not set' };
10
- }
11
- return { ok: true, detail: 'CURSOR_API_KEY configured' };
12
- }
13
5
  export async function runDoctor(adapter, repoRoot) {
14
6
  const manifest = await adapter.loadHarnessManifest(repoRoot);
15
7
  const requiredPaths = [];
@@ -36,15 +28,5 @@ export async function runDoctor(adapter, repoRoot) {
36
28
  if (backendMode === 'sdk-first' && !sdk.ok) {
37
29
  console.error(`doctor warning: CODE_AGENT_PI_BACKEND=sdk-first but pi SDK unavailable (${sdk.detail}); steps will fall back to CLI`);
38
30
  }
39
- const cursorCfg = resolveCursorExecutorConfig(manifest);
40
- if (cursorCfg) {
41
- const cursor = checkCursorAvailability();
42
- if (!cursor.ok) {
43
- console.error(`doctor warning: executors.cursor registered but ${cursor.detail}`);
44
- }
45
- if (isCursorWorkerEnabled()) {
46
- console.log(`doctor: CURSOR_USE_WORKER=1 (running=${isCursorWorkerRunning()})`);
47
- }
48
- }
49
31
  console.log('doctor ok');
50
32
  }