@tea-agent/loop-agent 0.42.0-next.9 → 0.42.1

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 (236) hide show
  1. package/CHANGELOG.md +111 -45
  2. package/dist/application/dag/run-dag.js +8 -2
  3. package/dist/application/evaluation/budget.js +19 -1
  4. package/dist/application/task-lifecycle/advance.js +26 -8
  5. package/dist/application/task-lifecycle/observe.js +43 -29
  6. package/dist/application/task-lifecycle/plan-transitions.js +5 -4
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/program.js +1 -1
  9. package/dist/commands/dag-rerun-task.js +2 -0
  10. package/dist/commands/task-source-prepare.js +3 -1
  11. package/dist/executors/dag-pi-executor.js +2584 -600
  12. package/dist/executors/pi-executor.js +22 -1
  13. package/dist/executors/pi-extension-resolver.js +14 -2
  14. package/dist/executors/pi-sdk-executor.js +140 -39
  15. package/dist/executors/shell-executor.js +135 -55
  16. package/dist/shared/dag-failure-category.js +6 -0
  17. package/dist/shared/frontend-execution-policy.js +23 -0
  18. package/dist/task/config-types.js +4 -0
  19. package/dist/task/contract/apply.js +36 -2
  20. package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
  21. package/dist/task/source-prepare/ledger-review.js +6 -9
  22. package/dist/task/source-prepare/parse-intent.js +7 -0
  23. package/dist/task/source-prepare/semantic-intake.js +16 -26
  24. package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
  25. package/dist/task/source-references.js +48 -23
  26. package/dist/worker/console/chat/assistant-content.js +23 -2
  27. package/dist/worker/console/chat/browser-policy.js +143 -0
  28. package/dist/worker/console/chat/browser-routes.js +148 -0
  29. package/dist/worker/console/chat/chat-event-store.js +4 -2
  30. package/dist/worker/console/chat/explore-tools.js +13 -0
  31. package/dist/worker/console/chat/pi-runtime.js +173 -94
  32. package/dist/worker/console/chat/resource-loader.js +4 -1
  33. package/dist/worker/console/chat/routes.js +208 -66
  34. package/dist/worker/console/chat/scm-routes.js +217 -0
  35. package/dist/worker/console/chat/scm-service.js +283 -0
  36. package/dist/worker/console/chat/scm-tools.js +111 -0
  37. package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
  38. package/dist/worker/console/chat/session-catalog.js +32 -0
  39. package/dist/worker/console/chat/session-mode-view.js +48 -0
  40. package/dist/worker/console/chat/session-mode.js +218 -0
  41. package/dist/worker/console/chat/session-store.js +27 -7
  42. package/dist/worker/console/chat/shortcuts.js +6 -0
  43. package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
  44. package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
  45. package/dist/worker/console/chat/subagents/index.js +5 -0
  46. package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
  47. package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
  48. package/dist/worker/console/chat/subagents/types.js +13 -0
  49. package/dist/worker/console/chat/terminal-routes.js +216 -0
  50. package/dist/worker/console/chat/terminal-sessions.js +284 -0
  51. package/dist/worker/console/chat/terminal-tools.js +199 -0
  52. package/dist/worker/console/chat/tool-preview.js +21 -0
  53. package/dist/worker/console/chat/tools.js +13 -1
  54. package/dist/worker/console/chat/turn-process.js +1 -0
  55. package/dist/worker/console/interview/tools.js +1 -0
  56. package/dist/worker/console/server.js +2 -28
  57. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
  58. package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
  59. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
  60. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
  61. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
  62. package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
  63. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
  73. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
  74. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
  75. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
  76. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
  77. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
  78. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
  81. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
  82. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
  83. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
  84. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
  85. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
  86. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
  87. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
  88. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
  89. package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
  90. package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
  91. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
  92. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
  93. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
  94. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
  95. package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
  96. package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
  97. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
  98. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
  99. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
  100. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
  101. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
  102. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
  103. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
  104. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
  105. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
  106. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
  107. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
  108. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
  109. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
  110. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
  111. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
  112. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
  113. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
  114. package/dist/worker/console/static/index.html +2 -2
  115. package/dist/worker/console/static-src/chat-view-types.js +1 -1
  116. package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
  117. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
  118. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
  119. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  120. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
  121. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  122. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
  123. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  124. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
  125. package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
  126. package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
  127. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
  128. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  129. package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
  130. package/dist/worker/console/workspace-context.js +15 -1
  131. package/dist/worker/observe/node-transparency.js +81 -72
  132. package/dist/worker/observe/routes.js +20 -1
  133. package/dist/worker/observe/static/constants.js +22 -22
  134. package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
  135. package/dist/worker/observe/static/dag-history-labels.js +1 -0
  136. package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
  137. package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
  138. package/dist/worker/observe/static/dag-node-purpose.js +10 -10
  139. package/dist/worker/observe/static/dom.js +20 -1
  140. package/dist/worker/observe/static/format-pool.d.ts +2 -0
  141. package/dist/worker/observe/static/format-pool.js +6 -0
  142. package/dist/worker/observe/static/format.js +7 -0
  143. package/dist/worker/observe/static/index.html +4 -4
  144. package/dist/worker/observe/static/inspect-workspace.js +34 -7
  145. package/dist/worker/observe/static/inspector-submission.js +32 -0
  146. package/dist/worker/observe/static/kpi.js +1 -0
  147. package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
  148. package/dist/worker/observe/static/relations.js +7 -5
  149. package/dist/worker/observe/static/router.js +13 -0
  150. package/dist/worker/observe/static/run-processing.js +2 -0
  151. package/dist/worker/observe/static/shell-chrome.js +36 -3
  152. package/dist/worker/observe/static/state.js +35 -2
  153. package/dist/worker/observe/static/styles.css +431 -39
  154. package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
  155. package/dist/worker/observe/static/task-failure-labels.js +67 -0
  156. package/dist/worker/observe/static/task-history.js +12 -0
  157. package/dist/worker/observe/static/views/batch.js +6 -13
  158. package/dist/worker/observe/static/views/dag-graph.js +51 -4
  159. package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
  160. package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
  161. package/dist/worker/observe/static/views/dag.d.ts +6 -0
  162. package/dist/worker/observe/static/views/dag.js +64 -22
  163. package/dist/worker/observe/static/views/dags.js +2 -0
  164. package/dist/worker/observe/static/views/dashboard.js +21 -12
  165. package/dist/worker/observe/static/views/failures.js +21 -11
  166. package/dist/worker/observe/static/views/feature.js +11 -29
  167. package/dist/worker/observe/static/views/pool.js +37 -28
  168. package/dist/worker/observe/static/views/run.js +48 -5
  169. package/dist/worker/observe/static/views/session-timeline.js +194 -243
  170. package/dist/worker/observe/static/views/task.js +81 -62
  171. package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
  172. package/dist/workflows/dag/budget-enforcement.js +53 -3
  173. package/dist/workflows/dag/dag-retry-schema.js +3 -0
  174. package/dist/workflows/dag/frontend-capacity.js +9 -0
  175. package/dist/workflows/dag/frontend-contract-facts.js +130 -0
  176. package/dist/workflows/dag/frontend-design-policy.js +103 -17
  177. package/dist/workflows/dag/frontend-durable-tools.js +193 -0
  178. package/dist/workflows/dag/frontend-execution-groups.js +24 -0
  179. package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
  180. package/dist/workflows/dag/frontend-input-projection.js +76 -0
  181. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  182. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  183. package/dist/workflows/dag/frontend-plan-render.js +21 -3
  184. package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
  185. package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
  186. package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
  187. package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
  188. package/dist/workflows/dag/frontend-recovery-run.js +146 -16
  189. package/dist/workflows/dag/frontend-review-scopes.js +139 -0
  190. package/dist/workflows/dag/frontend-risk.js +2 -0
  191. package/dist/workflows/dag/frontend-session-budget.js +249 -0
  192. package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
  193. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  194. package/dist/workflows/dag/frontend-shape.js +46 -7
  195. package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
  196. package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
  197. package/dist/workflows/dag/frontend-verification-trace.js +38 -4
  198. package/dist/workflows/dag/frontend-writer-admission.js +2 -2
  199. package/dist/workflows/dag/init-hybrid.js +70 -35
  200. package/dist/workflows/dag/node-execution.js +225 -226
  201. package/dist/workflows/dag/prompt.js +4 -0
  202. package/dist/workflows/dag/recovery-lease.js +106 -16
  203. package/dist/workflows/dag/rerun-feedback.js +1 -0
  204. package/dist/workflows/dag/rerun-plan.js +96 -4
  205. package/dist/workflows/dag/rerun-run.js +28 -6
  206. package/dist/workflows/dag/rerun-task.js +221 -18
  207. package/dist/workflows/dag/retry-policy.js +27 -10
  208. package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
  209. package/dist/workflows/dag/runner.js +246 -19
  210. package/dist/workflows/dag/structured-output-repair.js +4 -1
  211. package/dist/workflows/dag/types.js +10 -3
  212. package/dist/workflows/dag/validate.js +10 -8
  213. package/dist/workflows/dag/workspace-checkpoint.js +66 -0
  214. package/docs/operations/README.md +2 -0
  215. package/docs/templates/agent-dag.schema.json +2 -2
  216. package/docs/templates/backend-test-dag.json +6 -4
  217. package/docs/templates/frontend-design-contract.md +4 -4
  218. package/docs/templates/frontend-implementation-contract.schema.json +68 -4
  219. package/docs/templates/frontend-implementation-dag.json +5 -5
  220. package/harness.json +1 -1
  221. package/package.json +8 -6
  222. package/skills/frontend-contract/SKILL.md +2 -1
  223. package/skills/frontend-contract/references/contract-protocol.md +19 -3
  224. package/skills/frontend-design-review/SKILL.md +12 -11
  225. package/skills/frontend-plan/SKILL.md +22 -2
  226. package/skills/frontend-plan/references/decision-contract.md +114 -5
  227. package/skills/frontend-plan/references/design-decisions.md +32 -0
  228. package/skills/frontend-review/SKILL.md +10 -11
  229. package/skills/frontend-scout/references/scout-evidence.md +4 -0
  230. package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
  231. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
  232. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
  233. package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
  234. package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
  235. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
  236. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
@@ -136,6 +136,10 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
136
136
  const consumedArtifactIds = new Set((task.consumesArtifacts ?? []).map((binding) => binding.artifactId));
137
137
  for (const depId of task.depends_on) {
138
138
  const record = upstream[depId];
139
+ if (task.id === "frontend-scout-pi" && depId === "frontend-contract-pi" && record?.status === "FINISHED") {
140
+ sections.push(`## Upstream output: ${depId}\nCommitted Contract obligations are supplied in frontend_scout_input. Do not reconstruct them from prose or reread the source.`);
141
+ continue;
142
+ }
139
143
  const stdout = record?.stdout?.trim() ? record.stdout : "";
140
144
  const assistantText = record?.assistantText?.trim()
141
145
  ? record.assistantText
@@ -1,6 +1,8 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import { hostname as localHostname } from "node:os";
3
4
  import path from "node:path";
5
+ import { setTimeout as delay } from "node:timers/promises";
4
6
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
7
  import { DAG_RUNS_DIR } from "./lifecycle.js";
6
8
  function recoveryLeasePath(cwd, parentRunId) {
@@ -23,6 +25,11 @@ function parseLease(raw, parentRunId) {
23
25
  record.kind !== "standalone-task-rerun") ||
24
26
  typeof record.leaseToken !== "string" ||
25
27
  record.leaseToken.length === 0 ||
28
+ typeof record.ownerPid !== "number" ||
29
+ !Number.isInteger(record.ownerPid) ||
30
+ record.ownerPid <= 0 ||
31
+ typeof record.ownerHostname !== "string" ||
32
+ record.ownerHostname.length === 0 ||
26
33
  typeof record.acquiredAt !== "string" ||
27
34
  typeof record.executionRef !== "undefined" &&
28
35
  typeof record.executionRef !== "string") {
@@ -30,6 +37,60 @@ function parseLease(raw, parentRunId) {
30
37
  }
31
38
  return record;
32
39
  }
40
+ const LEASE_MUTATION_LOCK_STALE_MS = 60_000;
41
+ const LEASE_MUTATION_LOCK_RETRIES = 200;
42
+ async function withLeaseMutationLock(leasePath, operation) {
43
+ const lockPath = `${leasePath}.mutation-lock`;
44
+ for (let attempt = 0; attempt < LEASE_MUTATION_LOCK_RETRIES; attempt += 1) {
45
+ try {
46
+ await mkdir(lockPath);
47
+ }
48
+ catch (error) {
49
+ if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST")) {
50
+ throw error;
51
+ }
52
+ try {
53
+ const lockStat = await stat(lockPath);
54
+ if (Date.now() - lockStat.mtimeMs >= LEASE_MUTATION_LOCK_STALE_MS) {
55
+ await rm(lockPath, { recursive: true, force: true });
56
+ continue;
57
+ }
58
+ }
59
+ catch (statError) {
60
+ if (statError &&
61
+ typeof statError === "object" &&
62
+ "code" in statError &&
63
+ statError.code === "ENOENT") {
64
+ continue;
65
+ }
66
+ throw statError;
67
+ }
68
+ await delay(10);
69
+ continue;
70
+ }
71
+ try {
72
+ return await operation();
73
+ }
74
+ finally {
75
+ await rm(lockPath, { recursive: true, force: true });
76
+ }
77
+ }
78
+ throw new Error("dag recovery lease mutation lock is busy");
79
+ }
80
+ function defaultIsProcessAlive(pid) {
81
+ try {
82
+ process.kill(pid, 0);
83
+ return true;
84
+ }
85
+ catch {
86
+ return false;
87
+ }
88
+ }
89
+ function leaseOwnerIsAlive(input) {
90
+ if (input.lease.ownerHostname !== input.hostname)
91
+ return true;
92
+ return input.isProcessAlive(input.lease.ownerPid);
93
+ }
33
94
  export async function acquireDagRecoveryLease(input) {
34
95
  const leasePath = recoveryLeasePath(input.cwd, input.parentRunId);
35
96
  const lease = {
@@ -38,13 +99,15 @@ export async function acquireDagRecoveryLease(input) {
38
99
  requestId: input.requestId,
39
100
  kind: input.kind,
40
101
  leaseToken: randomBytes(16).toString("hex"),
102
+ ownerPid: process.pid,
103
+ ownerHostname: localHostname(),
41
104
  acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
42
105
  };
43
106
  await mkdir(path.dirname(leasePath), { recursive: true });
44
107
  try {
45
108
  // `wx` is the cross-process compare-and-create primitive. The lease is
46
- // deliberately immutable for a parent run: a later recovery must follow
47
- // the committed child lineage rather than fork another DAG.
109
+ // parent-scoped for the full recovery lineage. Ownership may only be
110
+ // replaced by claimDagRecoveryLease after the recorded owner is proven dead.
48
111
  await writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, {
49
112
  encoding: "utf8",
50
113
  flag: "wx",
@@ -59,22 +122,49 @@ export async function acquireDagRecoveryLease(input) {
59
122
  return { kind: "held", leasePath, lease: existing };
60
123
  }
61
124
  }
125
+ /**
126
+ * Reclaim a lease only after its recorded same-host owner is proven dead.
127
+ * The mutation lock serializes reclaim with execution binding, so a live owner
128
+ * can never race a stale-owner takeover and launch a second recovery.
129
+ */
130
+ export async function claimDagRecoveryLease(input) {
131
+ const hostname = input.hostname ?? localHostname();
132
+ const isProcessAlive = input.isProcessAlive ?? defaultIsProcessAlive;
133
+ return withLeaseMutationLock(input.leasePath, async () => {
134
+ const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
135
+ if (existing.leaseToken !== input.lease.leaseToken ||
136
+ leaseOwnerIsAlive({ lease: existing, hostname, isProcessAlive })) {
137
+ return { kind: "held", leasePath: input.leasePath, lease: existing };
138
+ }
139
+ const claimed = {
140
+ ...existing,
141
+ leaseToken: randomBytes(16).toString("hex"),
142
+ ownerPid: process.pid,
143
+ ownerHostname: hostname,
144
+ acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
145
+ };
146
+ await writeJsonAtomic(input.leasePath, claimed);
147
+ return { kind: "acquired", leasePath: input.leasePath, lease: claimed };
148
+ });
149
+ }
62
150
  /** Persist the operation/run identity before the caller is allowed to execute it. */
63
151
  export async function bindDagRecoveryLeaseExecution(input) {
64
152
  if (!input.executionRef.trim()) {
65
153
  throw new Error("dag recovery lease executionRef is required");
66
154
  }
67
- const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
68
- if (existing.leaseToken !== input.lease.leaseToken) {
69
- throw new Error("dag recovery lease ownership drift");
70
- }
71
- if (existing.executionRef && existing.executionRef !== input.executionRef) {
72
- throw new Error("dag recovery lease execution identity drift");
73
- }
74
- const bound = {
75
- ...existing,
76
- executionRef: input.executionRef,
77
- };
78
- await writeJsonAtomic(input.leasePath, bound);
79
- return bound;
155
+ return withLeaseMutationLock(input.leasePath, async () => {
156
+ const existing = parseLease(JSON.parse(await readFile(input.leasePath, "utf8")), input.lease.parentRunId);
157
+ if (existing.leaseToken !== input.lease.leaseToken) {
158
+ throw new Error("dag recovery lease ownership drift");
159
+ }
160
+ if (existing.executionRef && existing.executionRef !== input.executionRef) {
161
+ throw new Error("dag recovery lease execution identity drift");
162
+ }
163
+ const bound = {
164
+ ...existing,
165
+ executionRef: input.executionRef,
166
+ };
167
+ await writeJsonAtomic(input.leasePath, bound);
168
+ return bound;
169
+ });
80
170
  }
@@ -408,6 +408,7 @@ export async function deriveDagRerunFeedback(input) {
408
408
  return (node?.status === "ERROR" &&
409
409
  [
410
410
  "empty-output",
411
+ "output-limit",
411
412
  "invalid-output",
412
413
  "writer-thinking-exhausted",
413
414
  "writer-budget-exhausted",
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { z } from "zod";
4
4
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
5
+ import { workspaceDriftedPaths, } from "./workspace-checkpoint.js";
5
6
  import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
6
7
  import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
7
8
  import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
@@ -45,6 +46,7 @@ const highRiskContinuationAuthorityV1Schema = z
45
46
  tokens: z.number().int().nonnegative().nullable(),
46
47
  wallTimeMs: z.number().nonnegative(),
47
48
  executorCalls: z.number().int().nonnegative(),
49
+ providerRequests: z.number().int().nonnegative().optional(),
48
50
  repairPasses: z.number().int().nonnegative(),
49
51
  peakContextChars: z.number().int().nonnegative(),
50
52
  missingTokenNodeIds: z.array(z.string()),
@@ -1110,6 +1112,46 @@ function deriveSuggestedAction(input) {
1110
1112
  }
1111
1113
  return "manual";
1112
1114
  }
1115
+ /**
1116
+ * Gitignore-style-lite matcher for readSet/writeSet path patterns in scoped
1117
+ * workspace-drift evaluation: literal paths match exactly; `**` spans path
1118
+ * segments (a trailing `/**` also matches when nothing follows), `*` stays
1119
+ * within one segment, `?` is one non-separator char.
1120
+ */
1121
+ function pathMatchesWorkspacePattern(pathValue, pattern) {
1122
+ const normalizedPath = pathValue.replace(/\\/g, "/").replace(/^\.\//, "");
1123
+ const trimmed = pattern.trim().replace(/\\/g, "/").replace(/^\.\//, "");
1124
+ if (!trimmed || trimmed === "/")
1125
+ return false;
1126
+ if (trimmed === normalizedPath)
1127
+ return true;
1128
+ let source = "";
1129
+ for (let index = 0; index < trimmed.length; index += 1) {
1130
+ const char = trimmed[index];
1131
+ if (char === "*") {
1132
+ if (trimmed[index + 1] === "*") {
1133
+ if (trimmed[index + 2] === "/") {
1134
+ source += "(?:.*/)?";
1135
+ index += 2;
1136
+ }
1137
+ else {
1138
+ source += ".*";
1139
+ index += 1;
1140
+ }
1141
+ }
1142
+ else {
1143
+ source += "[^/]*";
1144
+ }
1145
+ }
1146
+ else if (char === "?") {
1147
+ source += "[^/]";
1148
+ }
1149
+ else {
1150
+ source += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1151
+ }
1152
+ }
1153
+ return new RegExp(`^${source}$`).test(normalizedPath);
1154
+ }
1113
1155
  function deriveRisk(reasonCodes, resetCount) {
1114
1156
  if (reasonCodes.includes("restart-subgraph-contains-writer") ||
1115
1157
  reasonCodes.includes("restart-subgraph-contains-unsafe-shell") ||
@@ -1213,7 +1255,14 @@ export async function evaluateDagRerunPlan(input) {
1213
1255
  blockedReasons.push("parent-lifecycle-ineligible");
1214
1256
  }
1215
1257
  else if (input.parentState.status !== "failed" &&
1216
- input.parentState.status !== "partial_failed") {
1258
+ input.parentState.status !== "partial_failed" &&
1259
+ // A superseded parent is a terminal operator judgment about the run as
1260
+ // a whole, not about its deterministic read-only compute: its FINISHED
1261
+ // upstream nodes are still valid fact donors for the imported set, and
1262
+ // every unsafe case (writers in the reset subgraph, decision gates,
1263
+ // stale checkpoints, unresolved ERROR nodes outside the closure) keeps
1264
+ // its own dedicated block below.
1265
+ input.parentState.status !== "superseded") {
1217
1266
  reasonCodes.push("parent-lifecycle-ineligible");
1218
1267
  blockedReasons.push("parent-lifecycle-ineligible");
1219
1268
  }
@@ -1241,8 +1290,41 @@ export async function evaluateDagRerunPlan(input) {
1241
1290
  else if (input.currentWorkspace?.fingerprint &&
1242
1291
  input.parentTerminalWorkspace.fingerprint !==
1243
1292
  input.currentWorkspace.fingerprint) {
1244
- reasonCodes.push("workspace-drift");
1245
- blockedReasons.push("workspace-drift");
1293
+ // Whole-fingerprint drift is refined to content-level paths: the rerun
1294
+ // re-derives (readSet) and rewrites (writeSet) the reset subgraph's own
1295
+ // paths, so drift confined to those paths is recomputed by the rerun
1296
+ // itself and must not force a full standalone rerun. Drift anywhere
1297
+ // else — especially inputs of imported fact-reuse nodes — keeps the
1298
+ // strict block. Indeterminate checkpoints fail closed.
1299
+ const driftedPaths = workspaceDriftedPaths(input.parentTerminalWorkspace, input.currentWorkspace);
1300
+ const importedReadPaths = importedNodeIds.flatMap((nodeId) => {
1301
+ const task = tasksByIdForPlan.get(nodeId);
1302
+ return task?.readSet ?? [];
1303
+ });
1304
+ // An omitted or empty readSet is unrestricted, not proof that an
1305
+ // imported node read nothing. Localize drift only when every reused
1306
+ // node has an explicit reading boundary.
1307
+ const importedReadSetsKnown = importedNodeIds.every((nodeId) => (tasksByIdForPlan.get(nodeId)?.readSet?.length ?? 0) > 0);
1308
+ const scopedDrift = importedReadSetsKnown &&
1309
+ driftedPaths !== undefined &&
1310
+ driftedPaths.length > 0 &&
1311
+ driftedPaths.every((driftPath) => !importedReadPaths.some((pattern) => pathMatchesWorkspacePattern(driftPath, pattern)) &&
1312
+ resetNodeIds.some((nodeId) => {
1313
+ const task = tasksByIdForPlan.get(nodeId);
1314
+ if (!task)
1315
+ return false;
1316
+ return [
1317
+ ...(task.writeSet ?? []),
1318
+ ...(task.readSet ?? []),
1319
+ ].some((pattern) => pathMatchesWorkspacePattern(driftPath, pattern));
1320
+ }));
1321
+ if (scopedDrift) {
1322
+ reasonCodes.push("workspace-drift-scoped");
1323
+ }
1324
+ else {
1325
+ reasonCodes.push("workspace-drift");
1326
+ blockedReasons.push("workspace-drift");
1327
+ }
1246
1328
  }
1247
1329
  else if (!input.currentWorkspace?.fingerprint) {
1248
1330
  reasonCodes.push("workspace-checkpoint-missing");
@@ -1293,8 +1375,18 @@ export async function evaluateDagRerunPlan(input) {
1293
1375
  }
1294
1376
  for (const nodeId of importedNodeIds) {
1295
1377
  const record = input.parentState.nodes[nodeId];
1296
- if (!record)
1378
+ if (!record) {
1379
+ reasonCodes.push("parent-facts-invalid");
1380
+ blockedReasons.push("parent-facts-invalid");
1381
+ blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
1297
1382
  continue;
1383
+ }
1384
+ if (record.status === "PENDING" || record.status === "RUNNING") {
1385
+ reasonCodes.push("parent-facts-invalid");
1386
+ blockedReasons.push("parent-facts-invalid");
1387
+ blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
1388
+ continue;
1389
+ }
1298
1390
  if (record.status === "ERROR") {
1299
1391
  reasonCodes.push("parent-facts-invalid");
1300
1392
  blockedReasons.push("parent-facts-invalid");
@@ -88,11 +88,18 @@ const operatorRequestEnvelopeSchema = z
88
88
  })
89
89
  .strict();
90
90
  async function readExistingOperatorRequest(cwd, requestId) {
91
+ const requestPath = operatorRequestPath(cwd, requestId);
91
92
  try {
92
- const raw = JSON.parse(await readFile(operatorRequestPath(cwd, requestId), "utf-8"));
93
+ const raw = JSON.parse(await readFile(requestPath, "utf-8"));
93
94
  const parsed = operatorRequestEnvelopeSchema.safeParse(raw);
94
- if (!parsed.success)
95
- return undefined;
95
+ if (!parsed.success) {
96
+ throw new Error("REQUEST_ID_REUSE_CONFLICT: persisted operator request envelope is invalid or belongs to a different request kind");
97
+ }
98
+ if (parsed.data.requestId !== requestId ||
99
+ parsed.data.parentRunId !== parsed.data.result.plan.parentRunId ||
100
+ parsed.data.newRunId !== parsed.data.result.newRunId) {
101
+ throw new Error("REQUEST_ID_REUSE_CONFLICT: persisted operator request envelope binding mismatch");
102
+ }
96
103
  const result = parsed.data.result;
97
104
  return {
98
105
  ...result,
@@ -100,10 +107,18 @@ async function readExistingOperatorRequest(cwd, requestId) {
100
107
  idempotentReplay: true,
101
108
  };
102
109
  }
103
- catch {
104
- // not found or unreadable
110
+ catch (error) {
111
+ if (error &&
112
+ typeof error === "object" &&
113
+ "code" in error &&
114
+ error.code === "ENOENT") {
115
+ return undefined;
116
+ }
117
+ if (error instanceof Error && error.message.startsWith("REQUEST_ID_REUSE_CONFLICT:")) {
118
+ throw error;
119
+ }
120
+ throw new Error(`REQUEST_ID_REUSE_CONFLICT: invalid persisted operator request envelope: ${error instanceof Error ? error.message : String(error)}`);
105
121
  }
106
- return undefined;
107
122
  }
108
123
  /**
109
124
  * Idempotent replay of a previously persisted operator request, if any.
@@ -769,6 +784,13 @@ async function stageContinuationRun(input) {
769
784
  if (!parentRecord) {
770
785
  throw new Error(`parent node record missing for imported node: ${nodeId}`);
771
786
  }
787
+ if (parentRecord.status !== "FINISHED" &&
788
+ !(parentRecord.status === "SKIPPED" &&
789
+ (parentRecord.skippedReason?.includes("condition") ||
790
+ parentRecord.skippedReason?.includes("runIf") ||
791
+ parentRecord.skippedReason?.includes("run-if")))) {
792
+ throw new Error(`parent node is not a settled fact donor for imported node: ${nodeId}`);
793
+ }
772
794
  const { manifestNode, importedRecord } = await importNodeFacts({
773
795
  parentRunDir: input.parentRunDir,
774
796
  newRunDir: runDir,