@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.11

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 (338) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +349 -97
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +12 -0
  6. package/dist/application/task-lifecycle/recommendations.js +13 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +23 -5
  9. package/dist/cli/program.js +21 -2
  10. package/dist/cli/update/notifier.js +2 -2
  11. package/dist/cli/update/runtime-activity.js +1 -29
  12. package/dist/cli.js +2 -1
  13. package/dist/commands/client-recovery.js +3 -0
  14. package/dist/commands/dag-follow-up.js +138 -0
  15. package/dist/commands/dag-rerun.js +55 -1
  16. package/dist/commands/init-model-catalog.js +464 -0
  17. package/dist/commands/init-upgrade.js +265 -97
  18. package/dist/commands/init.js +455 -28
  19. package/dist/commands/inspect-next.js +8 -0
  20. package/dist/commands/task-advance.js +19 -0
  21. package/dist/executors/dag-pi-executor.js +2370 -64
  22. package/dist/executors/pi-executor.js +11 -2
  23. package/dist/executors/pi-extension-resolver.js +233 -0
  24. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  25. package/dist/executors/pi-read-budget-policy.js +239 -0
  26. package/dist/executors/pi-sdk-executor.js +225 -33
  27. package/dist/executors/pi-writer-tool-policy.js +57 -1
  28. package/dist/executors/shell-executor.js +1258 -224
  29. package/dist/executors/shell-write-guard.js +14 -0
  30. package/dist/executors/workspace-write-snapshot.js +68 -0
  31. package/dist/infrastructure/harness/atomic-write.js +23 -0
  32. package/dist/shared/dag-failure-category.js +150 -0
  33. package/dist/shared/dag-prompt-override.js +27 -0
  34. package/dist/shared/openspec-spec.js +70 -4
  35. package/dist/shared/operator/capabilities.js +39 -0
  36. package/dist/shared/playwright-cli-command-policy.js +15 -0
  37. package/dist/shared/update/console-notifier.js +100 -0
  38. package/dist/{cli → shared}/update/npm-client.js +70 -15
  39. package/dist/{cli → shared}/update/state.js +41 -13
  40. package/dist/task/config-types.js +50 -5
  41. package/dist/task/contract/apply.js +11 -1
  42. package/dist/task/contract/constants.js +2 -0
  43. package/dist/task/contract/hash.js +3 -0
  44. package/dist/task/contract/observe.js +25 -4
  45. package/dist/task/contract/paths.js +2 -1
  46. package/dist/task/contract/project.js +18 -1
  47. package/dist/task/contract/schema.js +4 -1
  48. package/dist/task/contract/transaction.js +12 -0
  49. package/dist/task/frontend-project-capability.js +199 -18
  50. package/dist/task/source-prepare/completeness.js +188 -0
  51. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  52. package/dist/task/source-prepare/index.js +3 -0
  53. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  54. package/dist/task/source-prepare/ledger-review.js +214 -0
  55. package/dist/task/source-prepare/ledger.js +545 -0
  56. package/dist/task/source-prepare/parse-intent.js +24 -4
  57. package/dist/task/source-prepare/prepare.js +298 -1
  58. package/dist/task/source-prepare/semantic-intake.js +25 -5
  59. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  60. package/dist/task/source-prepare/types.js +22 -0
  61. package/dist/task/source-references.js +22 -1
  62. package/dist/worker/cli.js +18 -2
  63. package/dist/worker/console/chat/chat-event-store.js +75 -5
  64. package/dist/worker/console/chat/context-panel.js +7 -0
  65. package/dist/worker/console/chat/deferred-turn.js +12 -0
  66. package/dist/worker/console/chat/operation-card.js +8 -1
  67. package/dist/worker/console/chat/pi-console-config.js +81 -10
  68. package/dist/worker/console/chat/pi-runtime.js +331 -18
  69. package/dist/worker/console/chat/provider-error.js +98 -0
  70. package/dist/worker/console/chat/routes.js +395 -59
  71. package/dist/worker/console/chat/session-stats.js +179 -0
  72. package/dist/worker/console/chat/session-store.js +93 -63
  73. package/dist/worker/console/chat/todos.js +115 -0
  74. package/dist/worker/console/chat/tool-preview.js +42 -0
  75. package/dist/worker/console/chat/turn-process.js +20 -55
  76. package/dist/worker/console/chat/user-questions.js +266 -0
  77. package/dist/worker/console/chat/workspace-landing.js +79 -0
  78. package/dist/worker/console/console-handoff.js +82 -0
  79. package/dist/worker/console/console-update-and-init.js +115 -0
  80. package/dist/worker/console/console-update-runtime.js +84 -0
  81. package/dist/worker/console/dag-execution-receipt.js +33 -0
  82. package/dist/worker/console/draft-store.js +49 -0
  83. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  84. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  85. package/dist/worker/console/index.js +3 -0
  86. package/dist/worker/console/inspect-split.js +18 -0
  87. package/dist/worker/console/native-directory-picker.js +13 -0
  88. package/dist/worker/console/operation-run-facts.js +19 -3
  89. package/dist/worker/console/operation-store.js +227 -7
  90. package/dist/worker/console/operator-actions.js +388 -0
  91. package/dist/worker/console/operator-surface-health.js +1 -0
  92. package/dist/worker/console/operator-user-error.js +2 -2
  93. package/dist/worker/console/prd-intake-bridge.js +13 -0
  94. package/dist/worker/console/routes.js +355 -3
  95. package/dist/worker/console/security.js +67 -13
  96. package/dist/worker/console/server.js +609 -164
  97. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
  98. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BZp6JAp7.js} +1 -1
  99. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
  100. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
  101. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
  102. package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
  103. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
  112. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
  113. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
  114. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
  115. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
  117. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
  118. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
  123. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
  124. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
  125. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
  126. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
  127. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
  128. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
  129. package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
  130. package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
  131. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
  132. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
  133. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
  134. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
  135. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-BsjbDoXi.js} +1 -1
  136. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-0B7NnWKk.js} +5 -5
  137. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
  138. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
  139. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
  140. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
  141. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
  142. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
  143. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
  144. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
  145. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
  146. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
  147. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
  148. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
  149. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
  150. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
  151. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
  152. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
  153. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
  154. package/dist/worker/console/static/index.html +2 -2
  155. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  156. package/dist/worker/console/static-src/app/console-types.js +68 -4
  157. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  158. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  159. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  160. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  161. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  162. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  163. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  164. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  165. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  166. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  167. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  168. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  169. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  170. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  171. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  172. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  173. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  174. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  175. package/dist/worker/console/static-src/operator-chat/useChatStream.js +183 -48
  176. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  177. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  178. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  179. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  180. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  181. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  182. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  183. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  184. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  185. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  186. package/dist/worker/console/workspace-context.js +344 -0
  187. package/dist/worker/console/workspace-registry.js +214 -0
  188. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  189. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  190. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  191. package/dist/worker/observability/init-runtime-activity.js +26 -0
  192. package/dist/worker/observability/read-model.js +22 -0
  193. package/dist/worker/observe/node-input.js +160 -3
  194. package/dist/worker/observe/node-process.js +377 -0
  195. package/dist/worker/observe/routes.js +224 -3
  196. package/dist/worker/observe/server.js +4 -0
  197. package/dist/worker/observe/shell-handler-keys.js +34 -0
  198. package/dist/worker/observe/static/api.js +90 -4
  199. package/dist/worker/observe/static/app.js +18 -70
  200. package/dist/worker/observe/static/constants.js +8 -1
  201. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  202. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  203. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  204. package/dist/worker/observe/static/format.js +60 -0
  205. package/dist/worker/observe/static/index.html +6 -1
  206. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  207. package/dist/worker/observe/static/markdown-render.js +20 -1
  208. package/dist/worker/observe/static/operator-chrome.css +145 -20
  209. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  210. package/dist/worker/observe/static/operator-chrome.js +330 -96
  211. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  212. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  213. package/dist/worker/observe/static/router.d.ts +46 -0
  214. package/dist/worker/observe/static/router.js +25 -1
  215. package/dist/worker/observe/static/state.d.ts +50 -0
  216. package/dist/worker/observe/static/state.js +184 -1
  217. package/dist/worker/observe/static/styles.css +395 -0
  218. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  219. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  220. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  221. package/dist/worker/observe/static/views/dag.js +14 -5
  222. package/dist/worker/observe/static/views/dashboard.js +6 -4
  223. package/dist/worker/observe/static/views/failures.js +5 -2
  224. package/dist/worker/observe/static/views/night.js +2 -2
  225. package/dist/worker/observe/static/views/pool.js +1 -0
  226. package/dist/worker/observe/static/views/run.js +3 -1
  227. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  228. package/dist/worker/observe/static/views/task.js +56 -1
  229. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  230. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  231. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  232. package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
  233. package/dist/workflows/dag/contract-output-registry.js +15 -0
  234. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  235. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  236. package/dist/workflows/dag/dynamic-runtime/map.js +2 -1
  237. package/dist/workflows/dag/failure-category.js +7 -116
  238. package/dist/workflows/dag/frontend-closeout.js +221 -0
  239. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  240. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  241. package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
  242. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  243. package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
  244. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  245. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  246. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  247. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  248. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  249. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  250. package/dist/workflows/dag/frontend-repair.js +7 -424
  251. package/dist/workflows/dag/frontend-review-context.js +288 -14
  252. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  253. package/dist/workflows/dag/frontend-risk.js +15 -2
  254. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  255. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  256. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  257. package/dist/workflows/dag/frontend-shape.js +427 -0
  258. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  259. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  260. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  261. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  262. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  263. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  264. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  265. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  266. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  267. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  268. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  269. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  270. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  271. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  272. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  273. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  274. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  275. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  276. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  277. package/dist/workflows/dag/init-hybrid.js +1072 -660
  278. package/dist/workflows/dag/interrupt-request.js +10 -3
  279. package/dist/workflows/dag/lifecycle.js +3 -3
  280. package/dist/workflows/dag/node-execution.js +552 -58
  281. package/dist/workflows/dag/prompt.js +44 -19
  282. package/dist/workflows/dag/recovery-lease.js +80 -0
  283. package/dist/workflows/dag/report.js +37 -1
  284. package/dist/workflows/dag/rerun-feedback.js +124 -1
  285. package/dist/workflows/dag/rerun-plan.js +144 -5
  286. package/dist/workflows/dag/rerun-run.js +81 -6
  287. package/dist/workflows/dag/rerun-task.js +29 -0
  288. package/dist/workflows/dag/retry-policy.js +318 -8
  289. package/dist/workflows/dag/runner.js +429 -122
  290. package/dist/workflows/dag/scheduler.js +114 -16
  291. package/dist/workflows/dag/structured-output-repair.js +712 -0
  292. package/dist/workflows/dag/types.js +338 -31
  293. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  294. package/dist/workflows/dag/validate.js +92 -32
  295. package/docs/README.md +2 -0
  296. package/docs/architecture/evolution.md +45 -0
  297. package/docs/init-surface.manifest.json +9 -0
  298. package/docs/operations/README.md +1 -1
  299. package/docs/operations/local-development-environment.md +21 -0
  300. package/docs/templates/README.md +2 -1
  301. package/docs/templates/agent-dag-report.schema.json +8 -2
  302. package/docs/templates/agent-dag.schema.json +63 -1
  303. package/docs/templates/backend-test-dag.json +265 -237
  304. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  305. package/docs/templates/frontend-test-case-checklist.md +2 -2
  306. package/docs/templates/frontend-test-dag.json +8 -10
  307. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  308. package/docs/templates/init-managed-agents.md +13 -3
  309. package/docs/templates/spec-registry.schema.json +45 -0
  310. package/harness.json +1 -1
  311. package/package.json +7 -1
  312. package/scripts/next-info.mjs +356 -0
  313. package/scripts/next-publish-gate.mjs +238 -0
  314. package/scripts/release-source-binding.mjs +251 -0
  315. package/skills/codebase-scout/SKILL.md +1 -1
  316. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  317. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  318. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  319. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  320. package/skills/frontend-design-review/SKILL.md +20 -41
  321. package/skills/frontend-implementation/SKILL.md +3 -4
  322. package/skills/frontend-implementation/references/design-spec.md +15 -10
  323. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  324. package/skills/frontend-review/SKILL.md +22 -14
  325. package/skills/frontend-review/references/review-findings.md +6 -7
  326. package/skills/loop-agent/references/command-reference.md +12 -4
  327. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  328. package/skills/playwright-cli/SKILL.md +23 -1
  329. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  330. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  331. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  332. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  333. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  334. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  335. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  336. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
  337. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  338. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -45,9 +45,42 @@ function formatResolvedSkillInstructions(instructions) {
45
45
  if (!instructions || instructions.length === 0)
46
46
  return "(none)";
47
47
  return instructions
48
- .map((instruction) => instruction.promptText)
48
+ .map((instruction) => formatSkillChecklist(instruction))
49
49
  .join("\n\n---\n\n");
50
50
  }
51
+ /** Keep skill provenance, but turn bounded inline manuals into a small action list. */
52
+ function formatSkillChecklist(instruction) {
53
+ const metadata = [
54
+ `Skill: ${instruction.name}`,
55
+ `Resolution: ${instruction.resolution}`,
56
+ ...(instruction.path ? [`Path: ${instruction.path}`] : []),
57
+ ...(instruction.sha256 ? [`Sha256: ${instruction.sha256}`] : []),
58
+ "Injection mode: checklist",
59
+ ];
60
+ if (instruction.resolution !== "resolved")
61
+ return [...metadata, instruction.promptText].join("\n");
62
+ const source = instruction.promptText.split("Instructions:\n")[1] ?? "";
63
+ const candidates = source
64
+ .split(/\r?\n/)
65
+ .map((line) => line.trim())
66
+ .filter((line) => /^(?:#{1,3}\s+|[-*]\s+|\d+[.)]\s+)/.test(line) || line.length > 0)
67
+ .map((line) => line.replace(/^#{1,3}\s+/, "").replace(/^[-*]\s+/, "").replace(/^\d+[.)]\s+/, ""))
68
+ .filter((line) => line.length > 0);
69
+ const checklist = [];
70
+ let chars = 0;
71
+ for (const item of candidates) {
72
+ const bounded = item.slice(0, 240);
73
+ if (checklist.length >= 10 || chars + bounded.length > 1_600)
74
+ break;
75
+ checklist.push(`- ${bounded}`);
76
+ chars += bounded.length;
77
+ }
78
+ return [
79
+ ...metadata,
80
+ "Checklist (apply these actions; consult the immutable resolved file only when a listed action requires detail):",
81
+ ...(checklist.length > 0 ? checklist : ["- Follow the resolved skill's declared workflow."]),
82
+ ].join("\n");
83
+ }
51
84
  export function formatOutputLanguageBlock(language = DEFAULT_DAG_OUTPUT_LANGUAGE) {
52
85
  if (language === "en") {
53
86
  return [
@@ -67,7 +100,6 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
67
100
  const toleratedFailures = new Set(task.failureAwareDependsOn ?? []);
68
101
  for (const depId of task.depends_on) {
69
102
  const record = upstream[depId];
70
- const artifactFirst = task.artifactFirstUpstreamNodeIds?.includes(depId);
71
103
  const stdout = record?.stdout?.trim() ? record.stdout : "";
72
104
  const assistantText = record?.assistantText?.trim()
73
105
  ? record.assistantText
@@ -119,23 +151,16 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
119
151
  }
120
152
  if (!record || record.status !== "FINISHED")
121
153
  continue;
122
- if (artifactFirst) {
123
- const lines = [
124
- `## Upstream artifact: ${depId}`,
125
- "Artifact-first input: do not infer fields from a preview. Read the hash-bound payload only when a field is required; do not edit runner evidence.",
126
- ];
127
- if (record.structuredArtifactPath &&
128
- record.structuredArtifactSha256 &&
129
- record.structuredArtifactSchemaId) {
130
- lines.push(`- payload: ${record.structuredArtifactPath}`, `- schema: ${record.structuredArtifactSchemaId}`, `- sha256: ${record.structuredArtifactSha256}`);
131
- if (record.frontendPlanOutput) {
132
- lines.push(`- payload kind: ${record.frontendPlanOutput.payloadKind}`, `- citations: ${record.frontendPlanOutput.citationsArtifactPath}`, `- citations sha256: ${record.frontendPlanOutput.citationsArtifactSha256}`);
133
- }
134
- }
135
- else {
136
- lines.push("- artifact: unavailable (do not reconstruct the contract from node prose; treat this as an upstream artifact integrity failure)");
137
- }
138
- sections.push(lines.join("\n"));
154
+ if (record.structuredArtifactPath &&
155
+ record.structuredArtifactSchemaId ===
156
+ "frontend-implementation-contract-v1") {
157
+ sections.push([
158
+ `## Upstream output: ${depId}`,
159
+ "Validated structured artifact (read-only runner evidence — use read tool to fetch; do not edit). Do not infer the contract from stdout.",
160
+ `- path: ${record.structuredArtifactPath}`,
161
+ `- schema: ${record.structuredArtifactSchemaId}`,
162
+ `- sha256: ${record.structuredArtifactSha256 ?? "unknown"}`,
163
+ ].join("\n"));
139
164
  continue;
140
165
  }
141
166
  if (!upstreamText)
@@ -0,0 +1,80 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
+ import { DAG_RUNS_DIR } from "./lifecycle.js";
6
+ function recoveryLeasePath(cwd, parentRunId) {
7
+ // Never derive a filesystem name directly from an externally supplied legacy
8
+ // run id. The content remains auditable while the filename is traversal-safe.
9
+ const key = createHash("sha256").update(parentRunId).digest("hex");
10
+ return path.join(cwd, DAG_RUNS_DIR, ".recovery-leases", `${key}.json`);
11
+ }
12
+ function parseLease(raw, parentRunId) {
13
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
14
+ throw new Error("invalid dag recovery lease: expected object");
15
+ }
16
+ const record = raw;
17
+ if (record.schemaVersion !== 1 ||
18
+ record.parentRunId !== parentRunId ||
19
+ typeof record.requestId !== "string" ||
20
+ record.requestId.length === 0 ||
21
+ (record.kind !== "auto-frontend-recovery" &&
22
+ record.kind !== "rerun-from-node" &&
23
+ record.kind !== "standalone-task-rerun") ||
24
+ typeof record.leaseToken !== "string" ||
25
+ record.leaseToken.length === 0 ||
26
+ typeof record.acquiredAt !== "string" ||
27
+ typeof record.executionRef !== "undefined" &&
28
+ typeof record.executionRef !== "string") {
29
+ throw new Error("invalid dag recovery lease: schema mismatch");
30
+ }
31
+ return record;
32
+ }
33
+ export async function acquireDagRecoveryLease(input) {
34
+ const leasePath = recoveryLeasePath(input.cwd, input.parentRunId);
35
+ const lease = {
36
+ schemaVersion: 1,
37
+ parentRunId: input.parentRunId,
38
+ requestId: input.requestId,
39
+ kind: input.kind,
40
+ leaseToken: randomBytes(16).toString("hex"),
41
+ acquiredAt: (input.now ?? (() => new Date()))().toISOString(),
42
+ };
43
+ await mkdir(path.dirname(leasePath), { recursive: true });
44
+ try {
45
+ // `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.
48
+ await writeFile(leasePath, `${JSON.stringify(lease, null, 2)}\n`, {
49
+ encoding: "utf8",
50
+ flag: "wx",
51
+ });
52
+ return { kind: "acquired", leasePath, lease };
53
+ }
54
+ catch (error) {
55
+ if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST")) {
56
+ throw error;
57
+ }
58
+ const existing = parseLease(JSON.parse(await readFile(leasePath, "utf8")), input.parentRunId);
59
+ return { kind: "held", leasePath, lease: existing };
60
+ }
61
+ }
62
+ /** Persist the operation/run identity before the caller is allowed to execute it. */
63
+ export async function bindDagRecoveryLeaseExecution(input) {
64
+ if (!input.executionRef.trim()) {
65
+ throw new Error("dag recovery lease executionRef is required");
66
+ }
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;
80
+ }
@@ -125,6 +125,8 @@ const dagNodeArtifactsReportSchema = z
125
125
  humanEscalationMd: dagArtifactRefSchema,
126
126
  humanApproval: dagArtifactRefSchema,
127
127
  humanRejection: dagArtifactRefSchema,
128
+ changeManifest: dagArtifactRefSchema,
129
+ changeManifestChangedFiles: z.array(z.string()).optional(),
128
130
  })
129
131
  .strict();
130
132
  const dagNodeReportRowSchema = z
@@ -259,6 +261,20 @@ export async function probeArtifact(filePath) {
259
261
  export async function buildNodeArtifactsReport(runDir, nodeId) {
260
262
  const nodeDir = path.join(runDir, nodeId);
261
263
  const artifact = (name) => probeArtifact(path.join(nodeDir, name));
264
+ const changeManifestRef = await artifact("change-manifest.json");
265
+ let changeManifestChangedFiles;
266
+ if (changeManifestRef.exists) {
267
+ try {
268
+ const manifest = JSON.parse(await readFile(path.join(nodeDir, "change-manifest.json"), "utf-8"));
269
+ changeManifestChangedFiles = Array.isArray(manifest.changedFiles)
270
+ ? manifest.changedFiles
271
+ .filter((item) => typeof item === "string")
272
+ : undefined;
273
+ }
274
+ catch {
275
+ // unreadable manifest → leave undefined
276
+ }
277
+ }
262
278
  return {
263
279
  resultSummary: await artifact("result.summary.md"),
264
280
  promptRedacted: await artifact("prompt.redacted.md"),
@@ -270,6 +286,8 @@ export async function buildNodeArtifactsReport(runDir, nodeId) {
270
286
  humanEscalationMd: await artifact("human-escalation.md"),
271
287
  humanApproval: await artifact("human-approval.json"),
272
288
  humanRejection: await artifact("human-rejection.json"),
289
+ changeManifest: changeManifestRef,
290
+ changeManifestChangedFiles,
273
291
  };
274
292
  }
275
293
  function taskById(spec) {
@@ -726,6 +744,12 @@ function formatArtifactFlags(artifacts) {
726
744
  flags.push("approval");
727
745
  if (artifacts.humanRejection.exists)
728
746
  flags.push("rejection");
747
+ if (artifacts.changeManifest.exists) {
748
+ const count = artifacts.changeManifestChangedFiles?.length;
749
+ flags.push(count !== undefined
750
+ ? `changed:${count}`
751
+ : "change-manifest");
752
+ }
729
753
  return flags.length > 0 ? flags.join(",") : "-";
730
754
  }
731
755
  function formatTimestamp(value) {
@@ -749,7 +773,19 @@ function summarizeNodeStatuses(nodes) {
749
773
  return `${total} total / ${finished} finished / ${failed} failed / ${skipped} skipped / ${pending} pending`;
750
774
  }
751
775
  function hasArtifact(artifacts) {
752
- return Object.values(artifacts).some((artifact) => artifact.exists);
776
+ return [
777
+ artifacts.resultSummary,
778
+ artifacts.promptRedacted,
779
+ artifacts.skills,
780
+ artifacts.nodeRecord,
781
+ artifacts.decisionEnvelope,
782
+ artifacts.decisionEnvelopeValidation,
783
+ artifacts.humanEscalationJson,
784
+ artifacts.humanEscalationMd,
785
+ artifacts.humanApproval,
786
+ artifacts.humanRejection,
787
+ artifacts.changeManifest,
788
+ ].some((artifact) => artifact.exists);
753
789
  }
754
790
  function failureRows(run) {
755
791
  return run.nodes.filter((node) => node.status === "ERROR" ||
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { parseJsonReviewVerdict } from "./output-protocol.js";
5
+ import { FRONTEND_DESIGN_REVIEW_NODE_ID, FRONTEND_REVIEW_NODE_ID, readCommittedDesignRequestFact, readCommittedReviewRequestFact, } from "./frontend-review-findings.js";
5
6
  export const MAX_RERUN_FEEDBACK_CHARS = 12_000;
6
7
  const RERUN_FEEDBACK_TRUNCATION_MARKER = "\n\n...[上一轮反馈已按长度上限截断]...\n\n";
7
8
  const MAX_EVIDENCE_REFS = 8;
@@ -35,6 +36,44 @@ function requestRevision(text) {
35
36
  return (emphasized?.[1] ?? trimmed).trim() === "VERDICT: request-revision";
36
37
  });
37
38
  }
39
+ /**
40
+ * Deterministically render a typed `request_review_changes` fact into feedback
41
+ * text. Unlike the legacy text channel (VERDICT: request-revision), the
42
+ * frontend review terminal is a committed typed fact; the findings are the
43
+ * actionable body the next round must consume.
44
+ */
45
+ export function formatTypedReviewRequestFeedback(fact) {
46
+ const lines = [];
47
+ lines.push(`frontend review committed request_review_changes (issueCategory=${typeof fact.issueCategory === "string" ? fact.issueCategory : "unknown"})`);
48
+ if (Array.isArray(fact.findings) && fact.findings.length > 0) {
49
+ lines.push("review findings:");
50
+ for (const finding of fact.findings) {
51
+ if (!finding || typeof finding !== "object")
52
+ continue;
53
+ const record = finding;
54
+ const severity = typeof record.severity === "string" ? record.severity : "";
55
+ const issue = typeof record.issue === "string" ? record.issue : "";
56
+ const location = [
57
+ typeof record.file === "string" ? record.file : "",
58
+ typeof record.line === "number" ? String(record.line) : "",
59
+ ]
60
+ .filter(Boolean)
61
+ .join(":");
62
+ const prefix = location ? `[${location}] ` : "";
63
+ const requiredChange = typeof record.requiredChange === "string" &&
64
+ record.requiredChange.length > 0
65
+ ? ` (requiredChange: ${record.requiredChange})`
66
+ : "";
67
+ lines.push(`- ${prefix}${severity}: ${issue}${requiredChange}`);
68
+ }
69
+ }
70
+ if (Array.isArray(fact.evidenceRefs) && fact.evidenceRefs.length > 0) {
71
+ lines.push(`evidenceRefs: ${fact.evidenceRefs
72
+ .filter((ref) => typeof ref === "string")
73
+ .join(", ")}`);
74
+ }
75
+ return lines.join("\n");
76
+ }
38
77
  function pathInside(root, candidate) {
39
78
  const relative = path.relative(root, candidate);
40
79
  return (relative === "" ||
@@ -127,6 +166,41 @@ function evidenceRefs(repoRoot, runDir, pass, sourceNodeId) {
127
166
  }
128
167
  return result;
129
168
  }
169
+ /**
170
+ * Read the committed frontend review request fact (typed terminal channel).
171
+ * Returns { nodeId, fact } when the review node committed `request_review_changes`.
172
+ */
173
+ async function readTypedReviewRequestFact(runDir) {
174
+ const fact = await readCommittedReviewRequestFact({ runDir });
175
+ if (!fact)
176
+ return undefined;
177
+ return {
178
+ nodeId: FRONTEND_REVIEW_NODE_ID,
179
+ fact: {
180
+ issueCategory: fact.issueCategory,
181
+ evidenceRefs: fact.evidenceRefs,
182
+ findings: fact.findings,
183
+ },
184
+ };
185
+ }
186
+ /**
187
+ * Read the committed frontend design request fact (typed terminal channel).
188
+ * Returns { nodeId, fact } when the design review node committed
189
+ * `request_design_changes` — the design analog of request_review_changes.
190
+ */
191
+ async function readTypedDesignRequestFact(runDir) {
192
+ const fact = await readCommittedDesignRequestFact({ runDir });
193
+ if (!fact)
194
+ return undefined;
195
+ return {
196
+ nodeId: FRONTEND_DESIGN_REVIEW_NODE_ID,
197
+ fact: {
198
+ issueCategory: fact.issueCategory,
199
+ evidenceRefs: fact.evidenceRefs,
200
+ findings: fact.findings,
201
+ },
202
+ };
203
+ }
130
204
  /**
131
205
  * Derive only the latest unresolved verify/review feedback from canonical
132
206
  * parent-run facts. Provider-only failures intentionally return undefined.
@@ -144,8 +218,57 @@ export async function deriveDagRerunFeedback(input) {
144
218
  break;
145
219
  }
146
220
  }
147
- if (!sourceNodeId || !sourceNode)
221
+ if (!sourceNodeId || !sourceNode) {
222
+ // Typed terminal channel: the frontend review/design review commit their
223
+ // verdicts as typed facts (request_review_changes / request_design_changes)
224
+ // instead of VERDICT text, and the frontend DAG has no convergence
225
+ // passHistory. Fall back to reading the typed event stores so findings
226
+ // still flow into the next round as unresolved feedback. Prefer review;
227
+ // if absent, fall through to the design review (a design rejection also
228
+ // carries actionable findings the next plan round must consume).
229
+ const typedFact = (await readTypedReviewRequestFact(input.runDir)) ??
230
+ (await readTypedDesignRequestFact(input.runDir));
231
+ if (typedFact) {
232
+ const parentSourceBindingHash = sourceBindingHash(input.spec);
233
+ return withDigest({
234
+ schemaVersion: 1,
235
+ kind: "unresolved-terminal-feedback",
236
+ parentRunId: input.parentRunId,
237
+ taskId: input.taskId,
238
+ sourceNodeId: typedFact.nodeId,
239
+ verdict: "request-revision",
240
+ failureCategory: typedFact.nodeId === FRONTEND_REVIEW_NODE_ID
241
+ ? "review-request-changes"
242
+ : "design-request-changes",
243
+ feedbackText: scrubAndBoundFeedbackText(formatTypedReviewRequestFeedback(typedFact.fact), input.runDir, input.state.cwd),
244
+ evidenceRefs: [
245
+ {
246
+ nodeId: typedFact.nodeId,
247
+ preservedNodeRecordPath: `${typedFact.nodeId}.json`,
248
+ },
249
+ ],
250
+ attemptSummary: {
251
+ completedPasses: 1,
252
+ maxPasses: 1,
253
+ ...(input.state.terminalReason
254
+ ? { terminalReason: input.state.terminalReason }
255
+ : {}),
256
+ },
257
+ binding: {
258
+ ...(parentSourceBindingHash
259
+ ? { sourceCanonicalHash: parentSourceBindingHash }
260
+ : {}),
261
+ ...(input.spec.taskContractBinding?.canonicalHash
262
+ ? {
263
+ taskContractCanonicalHash: input.spec.taskContractBinding.canonicalHash,
264
+ }
265
+ : {}),
266
+ status: "unknown",
267
+ },
268
+ });
269
+ }
148
270
  return undefined;
271
+ }
149
272
  const completedPasses = pass?.pass ?? Math.max(1, input.state.convergence?.currentPass ?? 1);
150
273
  const maxPasses = Math.max(completedPasses, input.state.convergence?.maxPasses ?? 1);
151
274
  const parentSourceBindingHash = sourceBindingHash(input.spec);
@@ -3,6 +3,7 @@ import { readFile } from "node:fs/promises";
3
3
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
4
4
  import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
5
5
  import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
6
+ import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
6
7
  const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
7
8
  const SAFE_SHELL_GATE_KEYS = [
8
9
  "verdictGate",
@@ -159,12 +160,16 @@ function isCanonicalGeneratedVerifyShell(task) {
159
160
  return false;
160
161
  if (shell.projectGovernanceGate)
161
162
  return false;
162
- if (shell.frontendPrewriteGate)
163
+ if (shell.frontendDesignPolicy)
164
+ return false;
165
+ if (shell.frontendWriterAdmission)
163
166
  return false;
164
167
  if (shell.frontendVerificationBundle)
165
168
  return false;
166
169
  if (shell.frontendReviewContext)
167
170
  return false;
171
+ if (shell.frontendCloseout)
172
+ return false;
168
173
  if (shell.backendTestPipeline)
169
174
  return false;
170
175
  if (shell.nonZeroExitPolicy === "record")
@@ -190,12 +195,16 @@ function isSafeGateOnlyShell(task) {
190
195
  return false;
191
196
  if (shell.preset)
192
197
  return false;
193
- if (shell.frontendPrewriteGate)
198
+ if (shell.frontendDesignPolicy)
199
+ return false;
200
+ if (shell.frontendWriterAdmission)
194
201
  return false;
195
202
  if (shell.frontendVerificationBundle)
196
203
  return false;
197
204
  if (shell.frontendReviewContext)
198
205
  return false;
206
+ if (shell.frontendCloseout)
207
+ return false;
199
208
  if (shell.backendTestPipeline)
200
209
  return false;
201
210
  if (shell.verifyEvidence)
@@ -306,6 +315,9 @@ export function computeResetClosure(spec, effectiveFromNodeId) {
306
315
  ...collectReachableDescendants(spec, effectiveFromNodeId),
307
316
  ].sort();
308
317
  }
318
+ function isNeverStartedNodeStatus(status) {
319
+ return status === undefined || status === "SKIPPED" || status === "PENDING";
320
+ }
309
321
  export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
310
322
  const tasks = taskById(spec);
311
323
  const blockingNodes = [];
@@ -314,9 +326,17 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
314
326
  const task = tasks.get(nodeId);
315
327
  if (!task)
316
328
  continue;
329
+ if (options.parentNodes !== undefined &&
330
+ isNeverStartedNodeStatus(options.parentNodes[nodeId]?.status)) {
331
+ continue;
332
+ }
317
333
  if (isWriterTask(task, spec)) {
318
334
  if (nodeId === options.allowedTransportWriterNodeId)
319
335
  continue;
336
+ if (nodeId === options.allowedExclusiveShellFromNodeId &&
337
+ task.executor === "shell") {
338
+ continue;
339
+ }
320
340
  blockingNodes.push({
321
341
  nodeId,
322
342
  reasonCode: "restart-subgraph-contains-writer",
@@ -370,6 +390,100 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
370
390
  reasonCodes: [...new Set(reasonCodes)],
371
391
  };
372
392
  }
393
+ /**
394
+ * Experimental prompt-override admission (fail-closed):
395
+ * - requires the internal switch (see shared/dag-prompt-override.ts);
396
+ * - the target must be a failed (ERROR) node inside the reset closure, so an
397
+ * override can only adjust how an already-failed node retries — never a
398
+ * finished, skipped, or not-yet-started node;
399
+ * - only `subtask_prompt` semantics change: "append" keeps the frozen base
400
+ * and adds a marked addendum block, "replace" swaps the whole prompt;
401
+ * base/final sha256 are recorded for provenance and bound into planHash.
402
+ */
403
+ function assessPromptOverrides(input) {
404
+ const blockingNodes = [];
405
+ const reasonCodes = [];
406
+ const tasks = taskById(input.spec);
407
+ const resetSet = new Set(input.resetNodeIds);
408
+ const seen = new Set();
409
+ const entries = [];
410
+ if (!input.enabled) {
411
+ reasonCodes.push("prompt-override-disabled");
412
+ }
413
+ for (const override of input.overrides) {
414
+ const nodeId = override.nodeId?.trim() ?? "";
415
+ if (seen.has(nodeId)) {
416
+ blockingNodes.push({
417
+ nodeId,
418
+ reasonCode: "prompt-override-duplicate-node",
419
+ });
420
+ reasonCodes.push("prompt-override-duplicate-node");
421
+ continue;
422
+ }
423
+ seen.add(nodeId);
424
+ const task = tasks.get(nodeId);
425
+ if (!task) {
426
+ blockingNodes.push({
427
+ nodeId,
428
+ reasonCode: "prompt-override-node-missing",
429
+ });
430
+ reasonCodes.push("prompt-override-node-missing");
431
+ continue;
432
+ }
433
+ if (!resetSet.has(nodeId)) {
434
+ blockingNodes.push({
435
+ nodeId,
436
+ reasonCode: "prompt-override-outside-reset-closure",
437
+ });
438
+ reasonCodes.push("prompt-override-outside-reset-closure");
439
+ continue;
440
+ }
441
+ if (input.state.nodes[nodeId]?.status !== "ERROR") {
442
+ blockingNodes.push({
443
+ nodeId,
444
+ reasonCode: "prompt-override-node-not-failed",
445
+ });
446
+ reasonCodes.push("prompt-override-node-not-failed");
447
+ continue;
448
+ }
449
+ const mode = override.mode === "append" ? "append" : "replace";
450
+ const prompt = override.prompt ?? "";
451
+ if (!prompt.trim() ||
452
+ prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
453
+ blockingNodes.push({
454
+ nodeId,
455
+ reasonCode: "prompt-override-invalid-prompt",
456
+ });
457
+ reasonCodes.push("prompt-override-invalid-prompt");
458
+ continue;
459
+ }
460
+ const basePromptSha256 = createHash("sha256")
461
+ .update(task.subtask_prompt, "utf8")
462
+ .digest("hex");
463
+ const finalPrompt = composeDagPromptOverridePrompt({
464
+ basePrompt: task.subtask_prompt,
465
+ text: prompt,
466
+ mode,
467
+ });
468
+ const promptSha256 = createHash("sha256")
469
+ .update(finalPrompt, "utf8")
470
+ .digest("hex");
471
+ if (promptSha256 === basePromptSha256) {
472
+ blockingNodes.push({
473
+ nodeId,
474
+ reasonCode: "prompt-override-unchanged",
475
+ });
476
+ reasonCodes.push("prompt-override-unchanged");
477
+ continue;
478
+ }
479
+ entries.push({ nodeId, mode, basePromptSha256, promptSha256 });
480
+ }
481
+ return {
482
+ entries: entries.length > 0 ? entries : undefined,
483
+ blockingNodes,
484
+ reasonCodes: [...new Set(reasonCodes)],
485
+ };
486
+ }
373
487
  export function computePlanHash(planWithoutHash) {
374
488
  const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
375
489
  return createHash("sha256").update(canonical, "utf8").digest("hex");
@@ -594,9 +708,17 @@ export async function evaluateDagRerunPlan(input) {
594
708
  blockedReasons.push("ambiguous-skipped-rewrite");
595
709
  }
596
710
  }
597
- const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds, transportWriterRestart && effectiveFromNodeId
598
- ? { allowedTransportWriterNodeId: effectiveFromNodeId }
599
- : {});
711
+ const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds, {
712
+ ...(transportWriterRestart && effectiveFromNodeId
713
+ ? { allowedTransportWriterNodeId: effectiveFromNodeId }
714
+ : {}),
715
+ parentNodes: input.parentState.nodes,
716
+ ...(effectiveFromNodeId &&
717
+ effectiveTask?.executor === "shell" &&
718
+ effectiveRecord?.status === "ERROR"
719
+ ? { allowedExclusiveShellFromNodeId: effectiveFromNodeId }
720
+ : {}),
721
+ });
600
722
  blockingNodes.push(...closureSafety.blockingNodes);
601
723
  for (const code of closureSafety.reasonCodes) {
602
724
  reasonCodes.push(code);
@@ -629,6 +751,22 @@ export async function evaluateDagRerunPlan(input) {
629
751
  blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
630
752
  }
631
753
  }
754
+ let promptOverrideEntries;
755
+ if (input.promptOverrides !== undefined && input.promptOverrides.length > 0) {
756
+ const overrideAssessment = assessPromptOverrides({
757
+ spec: input.parentSpec,
758
+ state: input.parentState,
759
+ resetNodeIds,
760
+ overrides: input.promptOverrides,
761
+ enabled: input.promptOverrideEnabled === true,
762
+ });
763
+ blockingNodes.push(...overrideAssessment.blockingNodes);
764
+ for (const code of overrideAssessment.reasonCodes) {
765
+ reasonCodes.push(code);
766
+ blockedReasons.push(code);
767
+ }
768
+ promptOverrideEntries = overrideAssessment.entries;
769
+ }
632
770
  const uniqueReasonCodes = [...new Set(reasonCodes)];
633
771
  const uniqueBlockedReasons = [...new Set(blockedReasons)];
634
772
  const eligible = uniqueBlockedReasons.length === 0 &&
@@ -672,6 +810,7 @@ export async function evaluateDagRerunPlan(input) {
672
810
  parentLifecycle: input.parentLifecycle,
673
811
  workerManaged: input.workerManaged,
674
812
  }),
813
+ ...(promptOverrideEntries ? { promptOverrides: promptOverrideEntries } : {}),
675
814
  };
676
815
  return {
677
816
  ...planWithoutHash,