@tea-agent/loop-agent 0.9.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 (183) hide show
  1. package/AGENTS.md +8 -0
  2. package/CHANGELOG.md +92 -11
  3. package/README.md +69 -5
  4. package/dist/application/dag/args.js +4 -14
  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 +1 -1
  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/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  34. package/dist/sidecars/cursor-prompt/index.js +3 -0
  35. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  36. package/dist/task/config-types.js +29 -12
  37. package/dist/task/delegate.js +9 -21
  38. package/dist/task/runtime.js +1 -2
  39. package/dist/worker/cli.js +243 -0
  40. package/dist/worker/closeout/apply.js +73 -0
  41. package/dist/worker/closeout/preview.js +30 -0
  42. package/dist/worker/delivery/final-verification.js +194 -0
  43. package/dist/worker/delivery/git-transaction.js +354 -0
  44. package/dist/worker/delivery/package.js +502 -0
  45. package/dist/worker/feature/decision-loader.js +68 -0
  46. package/dist/worker/feature/discover.js +14 -0
  47. package/dist/worker/feature/next-action.js +74 -0
  48. package/dist/worker/feature/reducer.js +133 -0
  49. package/dist/worker/feature/review.js +502 -0
  50. package/dist/worker/feature/run.js +365 -0
  51. package/dist/worker/feature/types.js +1 -0
  52. package/dist/worker/follow-up/approve.js +270 -0
  53. package/dist/worker/follow-up/factory.js +234 -0
  54. package/dist/worker/follow-up/paths.js +25 -0
  55. package/dist/worker/follow-up/policy.js +26 -0
  56. package/dist/worker/follow-up/schema.js +93 -0
  57. package/dist/worker/follow-up/store.js +96 -0
  58. package/dist/worker/loop-agent/loop-agent-client.js +329 -126
  59. package/dist/worker/metrics/projector.js +139 -0
  60. package/dist/worker/observability/read-model.js +282 -15
  61. package/dist/worker/observe/paths.js +17 -5
  62. package/dist/worker/observe/static/app.js +443 -61
  63. package/dist/worker/observe/static/index.html +3 -1
  64. package/dist/worker/observe/static/styles.css +85 -18
  65. package/dist/worker/pool/run-store.js +14 -2
  66. package/dist/worker/pool/validation.js +59 -0
  67. package/dist/worker/preflight.js +49 -1
  68. package/dist/worker/report/morning-report.js +41 -6
  69. package/dist/worker/run-task/run-task.js +23 -13
  70. package/dist/worker/runner/run-ready.js +89 -11
  71. package/dist/worker/task-spec/schema.js +0 -1
  72. package/dist/workflows/dag/convergence/controller.js +1 -1
  73. package/dist/workflows/dag/executor-registry.js +0 -2
  74. package/dist/workflows/dag/governance-profile.js +10 -0
  75. package/dist/workflows/dag/init-hybrid.js +601 -26
  76. package/dist/workflows/dag/lifecycle.js +146 -0
  77. package/dist/workflows/dag/node-execution.js +64 -7
  78. package/dist/workflows/dag/prompt.js +16 -0
  79. package/dist/workflows/dag/report.js +2 -0
  80. package/dist/workflows/dag/runner.js +176 -119
  81. package/dist/workflows/dag/scheduler.js +7 -2
  82. package/dist/workflows/dag/skill-snapshot.js +527 -0
  83. package/dist/workflows/dag/types.js +45 -9
  84. package/dist/workflows/dag/validate.js +5 -8
  85. package/dist/workflows/loop/actions/dag-action.js +0 -2
  86. package/dist/workflows/loop/actions/shared.js +1 -1
  87. package/dist/workflows/loop/actions.js +14 -31
  88. package/dist/workflows/loop/benchmark.js +1 -1
  89. package/dist/workflows/loop/index.js +1 -1
  90. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  91. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  92. package/docs/README.md +35 -16
  93. package/docs/agent-dag-recovery-playbook.md +1 -1
  94. package/docs/architecture/README.md +26 -0
  95. package/docs/architecture/dag-execution.md +134 -0
  96. package/docs/architecture/evolution.md +52 -0
  97. package/docs/architecture/facts-and-state.md +58 -0
  98. package/docs/architecture/runtime-boundaries.md +41 -15
  99. package/docs/architecture/system-overview.md +93 -0
  100. package/docs/architecture/worker-and-feature.md +81 -0
  101. package/docs/cursor-prompt-sidecar.md +36 -0
  102. package/docs/decisions/README.md +13 -1
  103. package/docs/design/README.md +39 -13
  104. package/docs/development-principles.md +1 -1
  105. package/docs/exec-plans/active/README.md +2 -2
  106. package/docs/exec-plans/completed/README.md +14 -1
  107. package/docs/feature-workflow.md +44 -4
  108. package/docs/init-surface.manifest.json +63 -1
  109. package/docs/loop-agent-harness.md +65 -3
  110. package/docs/progress/README.md +27 -0
  111. package/docs/reports/README.md +74 -8
  112. package/docs/skills/README.md +2 -1
  113. package/docs/skills/vetted-skill-registry.md +2 -1
  114. package/docs/templates/agent-dag-report.schema.json +4 -2
  115. package/docs/templates/agent-dag.base.json +0 -5
  116. package/docs/templates/agent-dag.final-verification.json +0 -5
  117. package/docs/templates/agent-dag.schema.json +1 -2
  118. package/docs/templates/agent-dag.supervised-implementation.json +1 -6
  119. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
  120. package/docs/templates/backend-test-dag.json +213 -0
  121. package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
  122. package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -0
  123. package/docs/templates/frontend-design-contract.md +33 -0
  124. package/docs/templates/frontend-task-constraints.md +25 -0
  125. package/docs/templates/frontend-task-requirement.md +61 -0
  126. package/docs/templates/harness.schema.json +8 -5
  127. package/docs/templates/hybrid-dag.json +1 -6
  128. package/docs/templates/init-evolution-review.md +4 -2
  129. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  130. package/docs/templates/product-line/task.yaml +0 -1
  131. package/docs/templates/worker-dogfood-evidence.md +28 -0
  132. package/docs/templates/worker-dogfood-setup.md +20 -0
  133. package/docs/verification-matrix.md +17 -0
  134. package/examples/decision-gate-agent-dag.json +87 -33
  135. package/examples/example-dag.json +0 -5
  136. package/examples/hybrid-loop-agent-dag.json +0 -5
  137. package/harness.json +6 -11
  138. package/package.json +22 -44
  139. package/scripts/check-product-line-docs.sh +10 -3
  140. package/skills/agent-worker/SKILL.md +37 -0
  141. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  142. package/skills/frontend-design-review/SKILL.md +59 -0
  143. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  144. package/skills/frontend-implementation/SKILL.md +48 -0
  145. package/skills/frontend-implementation/references/code-standards.md +34 -0
  146. package/skills/frontend-implementation/references/design-spec.md +46 -0
  147. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  148. package/skills/frontend-review/SKILL.md +53 -0
  149. package/skills/frontend-review/references/review-findings.md +42 -0
  150. package/skills/frontend-verification/SKILL.md +40 -0
  151. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  152. package/skills/grill-me/SKILL.md +10 -0
  153. package/skills/grill-with-docs/SKILL.md +88 -0
  154. package/skills/grill-with-docs/adr-format.md +47 -0
  155. package/skills/grill-with-docs/context-format.md +60 -0
  156. package/skills/init-capability-evolution/SKILL.md +1 -0
  157. package/skills/loop-agent/SKILL.md +11 -9
  158. package/skills/loop-agent/references/command-reference.md +28 -15
  159. package/skills/loop-agent/references/docs-converge.md +126 -0
  160. package/skills/loop-agent/references/harness-policy.md +7 -7
  161. package/skills/loop-agent/references/hybrid-dag.md +13 -15
  162. package/skills/loop-agent/references/long-running-loop.md +4 -6
  163. package/skills/loop-agent/references/multi-worktree.md +6 -6
  164. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  165. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  166. package/skills/using-git-worktrees/SKILL.md +215 -0
  167. package/dist/commands/cursor-worker.js +0 -43
  168. package/dist/cursor-worker-entry.js +0 -8
  169. package/dist/executors/cursor-artifacts.js +0 -33
  170. package/dist/executors/cursor-execution-log.js +0 -81
  171. package/dist/executors/cursor-executor-artifacts.js +0 -134
  172. package/dist/executors/cursor-run.js +0 -115
  173. package/dist/executors/cursor-tool.js +0 -94
  174. package/dist/executors/cursor-worker-client.js +0 -223
  175. package/dist/executors/cursor-worker-protocol.js +0 -18
  176. package/dist/executors/cursor-worker-server.js +0 -54
  177. package/dist/executors/cursor-worker.js +0 -3
  178. package/dist/executors/cursor.js +0 -6
  179. package/dist/executors/dag-cursor-executor.js +0 -87
  180. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  181. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  182. package/docs/cursor-executor-usage.md +0 -25
  183. package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
@@ -1,5 +1,6 @@
1
1
  import { access, mkdir, readFile, readdir, rename, } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { hostname as localHostname } from "node:os";
3
4
  import { writeJsonAtomic, } from "../../infrastructure/harness/atomic-write.js";
4
5
  import { parseDagSpec } from "./types.js";
5
6
  import { normalizeDagFailureCategory, } from "./failure-category.js";
@@ -161,10 +162,98 @@ export async function humanApprovalArtifactExists(runDir, nodeId) {
161
162
  return false;
162
163
  }
163
164
  }
165
+ export function assessDagRunLiveness(input) {
166
+ if (input.state.status !== "running")
167
+ return { status: "unknown" };
168
+ const runner = input.state.runner;
169
+ if (!runner)
170
+ return { status: "unknown" };
171
+ if (runner.hostname !== (input.hostname ?? localHostname()))
172
+ return { status: "unknown-host" };
173
+ const isAlive = input.isProcessAlive ?? ((pid) => {
174
+ try {
175
+ process.kill(pid, 0);
176
+ return true;
177
+ }
178
+ catch {
179
+ return false;
180
+ }
181
+ });
182
+ if (!isAlive(runner.pid))
183
+ return { status: "orphaned", runnerAlive: false };
184
+ const heartbeatMs = Date.parse(runner.heartbeatAt);
185
+ const nowMs = (input.now ?? new Date()).getTime();
186
+ if (!Number.isNaN(heartbeatMs) && nowMs - heartbeatMs > (input.staleThresholdMs ?? 90_000)) {
187
+ return { status: "stale", runnerAlive: true };
188
+ }
189
+ const activeNode = Object.values(input.state.nodes).find((node) => node.status === "RUNNING");
190
+ const nodeActivityMs = Date.parse(activeNode?.lastActivityAt ?? activeNode?.startedAt ?? "");
191
+ if (!Number.isNaN(nodeActivityMs) && nowMs - nodeActivityMs > (input.nodeQuietThresholdMs ?? 300_000)) {
192
+ return { status: "node-quiet", runnerAlive: true };
193
+ }
194
+ return { status: "active", runnerAlive: true };
195
+ }
196
+ export function deriveDagRunEffectiveStatus(input) {
197
+ if (input.state.status === "superseded")
198
+ return "superseded";
199
+ if (input.state.status === "abandoned")
200
+ return "abandoned";
201
+ if (input.lifecycle === "paused")
202
+ return "paused";
203
+ if (input.lifecycle === "completed") {
204
+ return input.state.status === "finished" ? "finished" : "failed";
205
+ }
206
+ if (input.state.status === "pending")
207
+ return "pending";
208
+ if (isTerminalDagRunStatus(input.state.status)) {
209
+ return input.state.status === "finished" ? "finished" : "failed";
210
+ }
211
+ if (input.liveness === "orphaned" || input.liveness === "stale")
212
+ return "interrupted";
213
+ if (input.liveness === "node-quiet")
214
+ return "running-quiet";
215
+ if (input.liveness === "unknown-host")
216
+ return "remote-unknown";
217
+ if (input.liveness === "active")
218
+ return "running";
219
+ return "unknown";
220
+ }
221
+ export function assessDagRunRecoveryEligibility(input) {
222
+ const reasons = [];
223
+ const canResume = input.lifecycle === "active"
224
+ && input.state.status === "running"
225
+ && Boolean(input.state.humanDecisionNodeId)
226
+ && Boolean(input.hasHumanApproval);
227
+ if (!canResume)
228
+ reasons.push("standard-resume-preconditions-not-met");
229
+ let canReconcile = true;
230
+ if (input.lifecycle === "completed" || isTerminalDagRunStatus(input.state.status)) {
231
+ canReconcile = false;
232
+ reasons.push("run-already-terminal");
233
+ }
234
+ if (input.lifecycle === "active"
235
+ && ["active", "node-quiet", "stale", "unknown-host", "unknown"].includes(input.liveness)) {
236
+ canReconcile = false;
237
+ reasons.push("runner-not-proven-dead-or-stopped");
238
+ }
239
+ if (input.lifecycle === "paused" && input.state.status !== "paused") {
240
+ reasons.push("lifecycle-status-mismatch");
241
+ }
242
+ if (!input.state.runner)
243
+ reasons.push("missing-runner-metadata");
244
+ return {
245
+ canResume,
246
+ canReconcile,
247
+ allowedActions: canReconcile ? ["supersede", "abandon"] : [],
248
+ reasons: [...new Set(reasons)],
249
+ };
250
+ }
164
251
  export const TERMINAL_RUN_STATUSES = new Set([
165
252
  "finished",
166
253
  "failed",
167
254
  "partial_failed",
255
+ "superseded",
256
+ "abandoned",
168
257
  ]);
169
258
  export function isTerminalDagRunStatus(status) {
170
259
  return TERMINAL_RUN_STATUSES.has(status);
@@ -248,6 +337,31 @@ export async function detectDagRunHealthIssues(input) {
248
337
  advisoryAction: "Use dag doctor; approve/reject/resume may fail until lifecycle facts are consistent.",
249
338
  });
250
339
  }
340
+ const liveness = assessDagRunLiveness({ state });
341
+ if (liveness.status === "orphaned") {
342
+ issues.push({
343
+ code: "runner-process-missing",
344
+ severity: "error",
345
+ message: `Runner PID ${state.runner?.pid} is not alive on host ${state.runner?.hostname}`,
346
+ advisoryAction: "Treat this run as orphaned; inspect artifacts and start a new run instead of resuming it.",
347
+ });
348
+ }
349
+ else if (liveness.status === "stale") {
350
+ issues.push({
351
+ code: "runner-heartbeat-stale",
352
+ severity: "warning",
353
+ message: `Runner heartbeat is stale since ${state.runner?.heartbeatAt}`,
354
+ advisoryAction: "Inspect node artifacts and process liveness before stopping or retrying.",
355
+ });
356
+ }
357
+ else if (liveness.status === "node-quiet") {
358
+ issues.push({
359
+ code: "node-activity-quiet",
360
+ severity: "warning",
361
+ message: "Runner heartbeat is fresh but the current RUNNING node has produced no state activity for more than 5 minutes",
362
+ advisoryAction: "Inspect the node session events and executor logs before deciding whether to wait or abort.",
363
+ });
364
+ }
251
365
  if (lifecycle === "active" &&
252
366
  state.status === "running" &&
253
367
  state.humanDecisionNodeId) {
@@ -340,6 +454,14 @@ export async function buildDagOperatorRunSummary(entry) {
340
454
  pendingNodes: [],
341
455
  finishedNodes: [],
342
456
  healthIssues,
457
+ effectiveStatus: entry.lifecycle === "paused" ? "paused" : "unknown",
458
+ stateConsistent: false,
459
+ recoveryEligibility: {
460
+ canResume: false,
461
+ canReconcile: false,
462
+ allowedActions: [],
463
+ reasons: ["missing-state-json"],
464
+ },
343
465
  nextRecommendedAction: deriveOperatorNextAction({
344
466
  lifecycle: entry.lifecycle,
345
467
  state: {
@@ -363,9 +485,22 @@ export async function buildDagOperatorRunSummary(entry) {
363
485
  runDir: entry.runDir,
364
486
  state,
365
487
  });
488
+ const liveness = assessDagRunLiveness({ state });
366
489
  const hasHumanApproval = state.humanDecisionNodeId
367
490
  ? await humanApprovalArtifactExists(entry.runDir, state.humanDecisionNodeId)
368
491
  : false;
492
+ const effectiveStatus = deriveDagRunEffectiveStatus({
493
+ lifecycle: entry.lifecycle,
494
+ state,
495
+ liveness: liveness.status,
496
+ });
497
+ const stateConsistent = !healthIssues.some((issue) => ["lifecycle-status-mismatch", "non-terminal-in-completed"].includes(issue.code));
498
+ const recoveryEligibility = assessDagRunRecoveryEligibility({
499
+ lifecycle: entry.lifecycle,
500
+ state,
501
+ liveness: liveness.status,
502
+ hasHumanApproval,
503
+ });
369
504
  return {
370
505
  runId: state.runId,
371
506
  title: state.title,
@@ -380,6 +515,10 @@ export async function buildDagOperatorRunSummary(entry) {
380
515
  pendingNodes: listPendingNodeIds(state),
381
516
  finishedNodes: listFinishedNodeIds(state),
382
517
  healthIssues,
518
+ liveness: liveness.status,
519
+ effectiveStatus,
520
+ stateConsistent,
521
+ recoveryEligibility,
383
522
  nextRecommendedAction: deriveOperatorNextAction({
384
523
  lifecycle: entry.lifecycle,
385
524
  state,
@@ -552,6 +691,13 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
552
691
  "",
553
692
  `- run id: ${runId}`,
554
693
  `- lifecycle: ${located.lifecycle}`,
694
+ `- effective status: ${summary.effectiveStatus}`,
695
+ `- raw status: ${summary.status}`,
696
+ `- state consistent: ${summary.stateConsistent ? "yes" : "no"}`,
697
+ `- liveness: ${summary.liveness ?? "unknown"}`,
698
+ `- can resume: ${summary.recoveryEligibility.canResume ? "yes" : "no"}`,
699
+ `- can reconcile: ${summary.recoveryEligibility.canReconcile ? "yes" : "no"}`,
700
+ `- health issues: ${summary.healthIssues.length > 0 ? summary.healthIssues.map((issue) => `${issue.code}: ${issue.message}`).join("; ") : "none"}`,
555
701
  `- failed node: ${failure.nodeId ?? "-"}`,
556
702
  `- raw failure: ${rawFailureCategory ?? "-"}`,
557
703
  `- normalized category: ${normalizedCategory}`,
@@ -3,6 +3,7 @@ import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHuman
3
3
  import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
4
4
  import { buildDagNodePromptEnvelope } from "./prompt.js";
5
5
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
6
+ import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
6
7
  import { resolveDagSkillInstructions, skillInstructionMetadata, } from "./skill-instructions.js";
7
8
  import { resolveDagNodeSkills } from "./skills.js";
8
9
  import { parseRepairArtifactFromText, validateRepairArtifactScope, } from "./repair-artifact.js";
@@ -17,7 +18,7 @@ export function buildNodePrompt(spec, task, upstream) {
17
18
  }
18
19
  export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd) {
19
20
  const skillNames = resolveDagNodeSkills(spec, task);
20
- const resolvedSkillInstructions = task.executor === "cursor" || task.executor === "pi"
21
+ const resolvedSkillInstructions = task.executor === "pi"
21
22
  ? await resolveDagSkillInstructions(skillNames, {
22
23
  cwd,
23
24
  includeLearnedPatterns: task.role === "implementer",
@@ -55,7 +56,7 @@ function assertRepairArtifactVerdictMatchesSupervisor(input) {
55
56
  }
56
57
  function findRepairTaskForGate(input) {
57
58
  return Array.from(input.tasksById.values()).find((candidate) => candidate.depends_on.includes(input.gateTask.id) &&
58
- candidate.id === "repair-cursor");
59
+ candidate.id === "repair-pi");
59
60
  }
60
61
  function parseSupervisorRepairArtifact(node) {
61
62
  const text = `${node?.assistantText ?? ""}\n${node?.stdout ?? ""}`;
@@ -111,17 +112,54 @@ export async function executeDagNode(input) {
111
112
  const { nodeId, tasksById, state, spec, cwd, runDir, executeNode } = input;
112
113
  const task = tasksById.get(nodeId);
113
114
  const node = state.nodes[nodeId];
115
+ const failSkillSnapshot = async (error) => {
116
+ const failedAt = new Date().toISOString();
117
+ node.startedAt ??= failedAt;
118
+ node.status = "ERROR";
119
+ node.stderr = error instanceof Error ? error.message : String(error);
120
+ node.failureCategory = "skill-snapshot-integrity";
121
+ node.finishedAt = failedAt;
122
+ node.lastActivityAt = node.finishedAt;
123
+ node.durationMs = Math.max(0, Date.now() - new Date(node.startedAt).getTime());
124
+ state.nodes[nodeId].nodeRecordPath = path.join(runDir, `${nodeId}.json`);
125
+ await writeNodeRecord(runDir, nodeId, state.nodes[nodeId]);
126
+ await input.persistState();
127
+ await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
128
+ };
129
+ const isDynamicTask = Boolean(task.dynamicExpansion
130
+ || task.dynamicReduction
131
+ || task.dynamicCondition
132
+ || task.dynamicLoopUntil);
133
+ let snapshotPrompt;
134
+ if (state.skillSnapshotRef) {
135
+ try {
136
+ const skillSnapshot = await readSkillSnapshot(runDir, state.skillSnapshotRef, {
137
+ expectedRunId: state.runId,
138
+ });
139
+ assertSkillSnapshotCoversSpec(skillSnapshot, spec, state);
140
+ if (!isDynamicTask) {
141
+ snapshotPrompt = buildNodePromptFromSnapshot({
142
+ spec,
143
+ task,
144
+ upstream: state.nodes,
145
+ snapshot: skillSnapshot,
146
+ });
147
+ }
148
+ }
149
+ catch (error) {
150
+ await failSkillSnapshot(error);
151
+ return;
152
+ }
153
+ }
114
154
  node.status = "RUNNING";
115
155
  node.startedAt = new Date().toISOString();
156
+ node.lastActivityAt = node.startedAt;
116
157
  if (task.shell?.verifyEvidence) {
117
158
  node.verifyEvidence = task.shell.verifyEvidence;
118
159
  }
119
160
  await input.persistState();
120
161
  await notifyNodeObserver(input.observer, "onNodeStart", nodeId, state);
121
- if (task.dynamicExpansion ||
122
- task.dynamicReduction ||
123
- task.dynamicCondition ||
124
- task.dynamicLoopUntil) {
162
+ if (isDynamicTask) {
125
163
  const started = Date.now();
126
164
  try {
127
165
  const result = await input.executeDynamicNode({
@@ -140,6 +178,7 @@ export async function executeDagNode(input) {
140
178
  node.stderr = result.stderr;
141
179
  node.failureCategory = result.failureCategory;
142
180
  node.finishedAt = new Date().toISOString();
181
+ node.lastActivityAt = node.finishedAt;
143
182
  node.status = result.ok ? "FINISHED" : "ERROR";
144
183
  }
145
184
  catch (error) {
@@ -163,7 +202,24 @@ export async function executeDagNode(input) {
163
202
  await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
164
203
  return;
165
204
  }
166
- const { prompt, resolvedSkills } = await buildNodePromptWithResolvedSkillInstructions(spec, task, state.nodes, cwd);
205
+ let prompt;
206
+ let resolvedSkills;
207
+ try {
208
+ if (snapshotPrompt) {
209
+ prompt = snapshotPrompt.prompt;
210
+ resolvedSkills = snapshotPrompt.resolvedSkills;
211
+ }
212
+ else {
213
+ ({ prompt, resolvedSkills } =
214
+ await buildNodePromptWithResolvedSkillInstructions(spec, task, state.nodes, cwd));
215
+ }
216
+ }
217
+ catch (error) {
218
+ if (!isDagSkillSnapshotIntegrityError(error))
219
+ throw error;
220
+ await failSkillSnapshot(error);
221
+ return;
222
+ }
167
223
  node.resolvedSkills = resolvedSkills;
168
224
  await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
169
225
  const model = resolveModelForTask(task, spec.executorModels);
@@ -202,6 +258,7 @@ export async function executeDagNode(input) {
202
258
  await notifyNodeObserver(input.observer, "onNodeOutput", nodeId, state, outputChunk);
203
259
  }
204
260
  node.finishedAt = new Date().toISOString();
261
+ node.lastActivityAt = node.finishedAt;
205
262
  node.status = result.ok ? "FINISHED" : "ERROR";
206
263
  if (result.ok) {
207
264
  const decisionRecord = await recordDecisionEnvelopeForNode({
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_DAG_OUTPUT_LANGUAGE, } from "./types.js";
1
2
  import { formatStdoutPreview, formatUpstreamArtifactPointerMap, } from "./upstream-artifacts.js";
2
3
  export const MAX_UPSTREAM_CHARS = 2_000;
3
4
  /** Shared bullets for DAG authoring templates, docs, and planner-node envelopes. */
@@ -41,6 +42,20 @@ function formatResolvedSkillInstructions(instructions) {
41
42
  .map((instruction) => instruction.promptText)
42
43
  .join("\n\n---\n\n");
43
44
  }
45
+ export function formatOutputLanguageBlock(language = DEFAULT_DAG_OUTPUT_LANGUAGE) {
46
+ if (language === "en") {
47
+ return [
48
+ "Write prose, analysis, reports, summaries, and documentation in English.",
49
+ "Keep code, commands, paths, identifiers, JSON keys, exact protocol tokens, verdict lines, and output-contract literals unchanged.",
50
+ "If the node task explicitly requires another language, follow the explicit task requirement.",
51
+ ].join("\n");
52
+ }
53
+ return [
54
+ "使用简体中文撰写说明、分析、报告、总结和文档正文。",
55
+ "代码、命令、路径、标识符、JSON 字段、精确协议 token、VERDICT 行以及 output contract 中要求的字面量保持原样,不要翻译。",
56
+ "如果当前节点任务明确要求其他语言,以节点的明确要求为准。",
57
+ ].join("\n");
58
+ }
44
59
  export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHARS) {
45
60
  const sections = [];
46
61
  for (const depId of task.depends_on) {
@@ -91,6 +106,7 @@ export function buildDagNodePromptEnvelope(input) {
91
106
  `<dag_objective>\n${objective}\n</dag_objective>`,
92
107
  `<success_criteria>\n${successCriteria}\n</success_criteria>`,
93
108
  `<global_constraints>\n${globalConstraints}\n</global_constraints>`,
109
+ `<output_language>\n${formatOutputLanguageBlock(spec.outputLanguage)}\n</output_language>`,
94
110
  [
95
111
  "<node_contract>",
96
112
  `Role: ${role}`,
@@ -20,6 +20,8 @@ const dagRunStatusSchema = z.enum([
20
20
  "partial_failed",
21
21
  "failed",
22
22
  "paused",
23
+ "superseded",
24
+ "abandoned",
23
25
  ]);
24
26
  const dagRecoveryActionSchema = z.enum(DAG_RECOVERY_ACTIONS);
25
27
  const dagProductLineFailureCategorySchema = z.enum(dagProductLineFailureCategoryValues);