@tea-agent/loop-agent 0.39.0-next.9 → 0.39.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 (288) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +269 -69
  3. package/README.md +11 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/dag/generate-task-dag.js +9 -0
  6. package/dist/application/task-lifecycle/advance.js +16 -0
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +16 -5
  9. package/dist/cli/program.js +16 -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/dag-rerun.js +55 -1
  14. package/dist/commands/init-model-catalog.js +464 -0
  15. package/dist/commands/init-upgrade.js +265 -97
  16. package/dist/commands/init.js +428 -27
  17. package/dist/commands/inspect-next.js +8 -0
  18. package/dist/commands/task-advance.js +31 -0
  19. package/dist/executors/dag-pi-executor.js +124 -18
  20. package/dist/executors/pi-event-serializer.js +42 -1
  21. package/dist/executors/pi-executor.js +51 -0
  22. package/dist/executors/pi-extension-resolver.js +233 -0
  23. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  24. package/dist/executors/pi-sdk-executor.js +187 -59
  25. package/dist/executors/shell-executor.js +555 -34
  26. package/dist/executors/shell-write-guard.js +7 -0
  27. package/dist/shared/dag-failure-category.js +138 -0
  28. package/dist/shared/dag-prompt-override.js +27 -0
  29. package/dist/shared/operator/capabilities.js +181 -27
  30. package/dist/shared/playwright-cli-command-policy.js +15 -0
  31. package/dist/shared/update/console-notifier.js +100 -0
  32. package/dist/{cli → shared}/update/npm-client.js +70 -15
  33. package/dist/{cli → shared}/update/state.js +41 -13
  34. package/dist/task/config-types.js +28 -0
  35. package/dist/task/contract/apply.js +11 -1
  36. package/dist/task/contract/constants.js +3 -0
  37. package/dist/task/contract/diff.js +26 -0
  38. package/dist/task/contract/hash.js +3 -0
  39. package/dist/task/contract/observe.js +25 -4
  40. package/dist/task/contract/paths.js +2 -1
  41. package/dist/task/contract/project.js +20 -1
  42. package/dist/task/contract/schema.js +8 -1
  43. package/dist/task/contract/transaction.js +12 -0
  44. package/dist/task/source-prepare/build-draft.js +15 -0
  45. package/dist/task/source-prepare/completeness.js +188 -0
  46. package/dist/task/source-prepare/fragment-inventory.js +474 -0
  47. package/dist/task/source-prepare/index.js +3 -0
  48. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  49. package/dist/task/source-prepare/ledger-review.js +214 -0
  50. package/dist/task/source-prepare/ledger.js +545 -0
  51. package/dist/task/source-prepare/parse-intent.js +24 -4
  52. package/dist/task/source-prepare/prepare.js +262 -1
  53. package/dist/task/source-prepare/semantic-intake.js +19 -3
  54. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  55. package/dist/task/source-prepare/types.js +22 -0
  56. package/dist/worker/cli.js +18 -2
  57. package/dist/worker/console/chat/deferred-turn.js +91 -0
  58. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  59. package/dist/worker/console/chat/operation-card.js +8 -1
  60. package/dist/worker/console/chat/pi-console-config.js +81 -10
  61. package/dist/worker/console/chat/pi-runtime.js +165 -83
  62. package/dist/worker/console/chat/repo-walk.js +10 -1
  63. package/dist/worker/console/chat/routes.js +480 -55
  64. package/dist/worker/console/chat/semantic-activity.js +20 -8
  65. package/dist/worker/console/chat/session-store.js +146 -2
  66. package/dist/worker/console/chat/turn-process.js +10 -10
  67. package/dist/worker/console/console-handoff.js +82 -0
  68. package/dist/worker/console/console-update-and-init.js +115 -0
  69. package/dist/worker/console/console-update-runtime.js +84 -0
  70. package/dist/worker/console/dag-execution-receipt.js +33 -0
  71. package/dist/worker/console/draft-store.js +49 -0
  72. package/dist/worker/console/inspect-split.js +18 -0
  73. package/dist/worker/console/operation-run-facts.js +19 -3
  74. package/dist/worker/console/operation-store.js +1 -1
  75. package/dist/worker/console/operator-actions.js +320 -4
  76. package/dist/worker/console/operator-surface-health.js +1 -0
  77. package/dist/worker/console/operator-user-error.js +2 -2
  78. package/dist/worker/console/pi-readiness.js +5 -4
  79. package/dist/worker/console/prd-intake-bridge.js +13 -0
  80. package/dist/worker/console/recovery-cta.js +4 -4
  81. package/dist/worker/console/recovery-error-copy.js +4 -4
  82. package/dist/worker/console/routes.js +46 -2
  83. package/dist/worker/console/security.js +63 -9
  84. package/dist/worker/console/server.js +111 -7
  85. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
  86. package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
  87. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
  88. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
  89. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
  90. package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
  91. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
  101. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
  102. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
  103. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
  104. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
  105. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
  106. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
  110. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
  111. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
  112. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
  113. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
  114. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
  115. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
  116. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
  117. package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
  118. package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
  119. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
  120. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
  121. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
  122. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
  123. package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
  124. package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
  125. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
  126. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
  127. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
  128. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
  129. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
  130. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
  131. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
  132. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
  133. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
  134. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
  135. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
  136. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
  137. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
  138. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
  139. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
  140. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
  141. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
  142. package/dist/worker/console/static/index.html +2 -2
  143. package/dist/worker/console/static-src/app/console-types.js +40 -1
  144. package/dist/worker/console/static-src/app/useOperatorActions.js +6 -1
  145. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  146. package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
  147. package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
  148. package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
  149. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  150. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
  151. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  152. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  153. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  154. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  155. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  156. package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
  157. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
  158. package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
  159. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  160. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  161. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  162. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  163. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  164. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  165. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  166. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  167. package/dist/worker/observability/init-runtime-activity.js +26 -0
  168. package/dist/worker/observability/read-model.js +22 -0
  169. package/dist/worker/observe/node-input.js +160 -3
  170. package/dist/worker/observe/node-process.js +377 -0
  171. package/dist/worker/observe/routes.js +224 -3
  172. package/dist/worker/observe/shell-handler-keys.js +34 -0
  173. package/dist/worker/observe/static/api.js +67 -1
  174. package/dist/worker/observe/static/constants.js +8 -1
  175. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  176. package/dist/worker/observe/static/format.js +60 -0
  177. package/dist/worker/observe/static/index.html +5 -0
  178. package/dist/worker/observe/static/markdown-render.js +20 -1
  179. package/dist/worker/observe/static/operator-chrome.js +4 -0
  180. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  181. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  182. package/dist/worker/observe/static/state.js +13 -2
  183. package/dist/worker/observe/static/styles.css +85 -0
  184. package/dist/worker/observe/static/views/dag-inspector.js +912 -8
  185. package/dist/worker/observe/static/views/dag.js +8 -1
  186. package/dist/worker/observe/static/views/failures.js +5 -2
  187. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  188. package/dist/worker/observe/static/views/task.js +55 -0
  189. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
  190. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  191. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  192. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  193. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  194. package/dist/workflows/dag/backend-test-layout.js +133 -0
  195. package/dist/workflows/dag/backend-test-markdown-workflow.js +343 -33
  196. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  197. package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
  198. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  199. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  200. package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
  201. package/dist/workflows/dag/backend-test-writer-completeness.js +110 -61
  202. package/dist/workflows/dag/contract-output-registry.js +15 -0
  203. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  204. package/dist/workflows/dag/convergence/controller.js +141 -0
  205. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  206. package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
  207. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  208. package/dist/workflows/dag/failure-category.js +7 -116
  209. package/dist/workflows/dag/frontend-implementation-contract.js +465 -6
  210. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  211. package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
  212. package/dist/workflows/dag/frontend-repair.js +14 -0
  213. package/dist/workflows/dag/frontend-review-context.js +92 -10
  214. package/dist/workflows/dag/frontend-risk.js +15 -2
  215. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  216. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  217. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  218. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  219. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  220. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  221. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  222. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  223. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  224. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  225. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  226. package/dist/workflows/dag/init-hybrid.js +1019 -209
  227. package/dist/workflows/dag/node-execution.js +228 -15
  228. package/dist/workflows/dag/prompt.js +60 -1
  229. package/dist/workflows/dag/rerun-feedback.js +212 -0
  230. package/dist/workflows/dag/rerun-plan.js +224 -27
  231. package/dist/workflows/dag/rerun-run.js +61 -6
  232. package/dist/workflows/dag/rerun-task.js +61 -2
  233. package/dist/workflows/dag/retry-policy.js +109 -0
  234. package/dist/workflows/dag/structured-output-repair.js +712 -0
  235. package/dist/workflows/dag/types.js +185 -12
  236. package/dist/workflows/dag/validate.js +64 -20
  237. package/docs/README.md +1 -0
  238. package/docs/architecture/evolution.md +45 -0
  239. package/docs/init-surface.manifest.json +6 -0
  240. package/docs/operations/README.md +1 -1
  241. package/docs/operations/local-development-environment.md +1 -5
  242. package/docs/skills/vetted-skill-registry.md +14 -0
  243. package/docs/templates/README.md +2 -2
  244. package/docs/templates/agent-dag.schema.json +44 -0
  245. package/docs/templates/backend-test-dag.json +60 -35
  246. package/docs/templates/frontend-implementation-contract.schema.json +681 -24
  247. package/docs/templates/frontend-test-case-checklist.md +2 -2
  248. package/docs/templates/frontend-test-dag.json +8 -10
  249. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  250. package/docs/templates/init-managed-agents.md +13 -3
  251. package/harness.json +6 -4
  252. package/package.json +12 -4
  253. package/scripts/next-info.mjs +356 -0
  254. package/scripts/next-publish-gate.mjs +238 -0
  255. package/scripts/release-source-binding.mjs +251 -0
  256. package/skills/analyze-product-requirements/SKILL.md +8 -5
  257. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  258. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  259. package/skills/analyze-product-requirements/references/example.md +50 -0
  260. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  261. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  262. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  263. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  264. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  265. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  266. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  267. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  268. package/skills/codebase-scout/SKILL.md +1 -1
  269. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  270. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  271. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  272. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  273. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  274. package/skills/improve-codebase-architecture/deepening.md +37 -0
  275. package/skills/improve-codebase-architecture/html-report.md +123 -0
  276. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  277. package/skills/improve-codebase-architecture/language.md +53 -0
  278. package/skills/loop-agent/references/command-reference.md +11 -4
  279. package/skills/loop-agent/references/hybrid-dag.md +7 -0
  280. package/skills/playwright-cli/SKILL.md +23 -1
  281. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  282. package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
  283. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
  284. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
  285. package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
  286. package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
  287. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
  288. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
@@ -0,0 +1,212 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { parseJsonReviewVerdict } from "./output-protocol.js";
5
+ export const MAX_RERUN_FEEDBACK_CHARS = 12_000;
6
+ const RERUN_FEEDBACK_TRUNCATION_MARKER = "\n\n...[上一轮反馈已按长度上限截断]...\n\n";
7
+ const MAX_EVIDENCE_REFS = 8;
8
+ function sha256(value) {
9
+ return createHash("sha256").update(value).digest("hex");
10
+ }
11
+ function withDigest(payload) {
12
+ return {
13
+ ...payload,
14
+ digest: sha256(JSON.stringify(payload)),
15
+ };
16
+ }
17
+ function sourceBindingHash(spec) {
18
+ return spec.sourceBinding
19
+ ? sha256(JSON.stringify(spec.sourceBinding))
20
+ : undefined;
21
+ }
22
+ function canonicalNodeOutput(node) {
23
+ const assistantText = node?.assistantText?.trim();
24
+ return assistantText || node?.stdout?.trim() || "";
25
+ }
26
+ function requestRevision(text) {
27
+ const jsonVerdict = parseJsonReviewVerdict(text);
28
+ if (jsonVerdict.ok)
29
+ return jsonVerdict.verdict === "request-revision";
30
+ return text
31
+ .split(/\r?\n/)
32
+ .some((line) => {
33
+ const trimmed = line.trim();
34
+ const emphasized = trimmed.match(/^\*{1,3}\s*(VERDICT:[^*]+?)\s*\*{1,3}$/);
35
+ return (emphasized?.[1] ?? trimmed).trim() === "VERDICT: request-revision";
36
+ });
37
+ }
38
+ function pathInside(root, candidate) {
39
+ const relative = path.relative(root, candidate);
40
+ return (relative === "" ||
41
+ (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative)));
42
+ }
43
+ function safeRepositoryRelativePath(repoRoot, runDir, rawPath, fallback) {
44
+ let evidencePath = path.resolve(runDir, fallback);
45
+ if (rawPath) {
46
+ const absolute = path.isAbsolute(rawPath)
47
+ ? path.normalize(rawPath)
48
+ : path.resolve(runDir, rawPath);
49
+ if (pathInside(runDir, absolute)) {
50
+ evidencePath = absolute;
51
+ }
52
+ }
53
+ if (pathInside(repoRoot, evidencePath)) {
54
+ return path.relative(repoRoot, evidencePath).replaceAll(path.sep, "/");
55
+ }
56
+ return path.relative(runDir, evidencePath).replaceAll(path.sep, "/");
57
+ }
58
+ function nodeRecordCandidates(runDir, pass, ref, nodeId) {
59
+ const values = [
60
+ ref?.preservedNodeRecordPath,
61
+ pass ? path.join("convergence", `pass-${pass.pass}`, `${nodeId}.json`) : undefined,
62
+ `${nodeId}.json`,
63
+ ].filter((value) => Boolean(value));
64
+ const resolved = values
65
+ .map((value) => (path.isAbsolute(value) ? path.normalize(value) : path.resolve(runDir, value)))
66
+ .filter((value) => pathInside(runDir, value));
67
+ return [...new Set(resolved)];
68
+ }
69
+ async function readNodeRecord(runDir, pass, ref, nodeId, fallback) {
70
+ for (const candidate of nodeRecordCandidates(runDir, pass, ref, nodeId)) {
71
+ try {
72
+ const parsed = JSON.parse(await readFile(candidate, "utf8"));
73
+ if (parsed && typeof parsed === "object")
74
+ return parsed;
75
+ }
76
+ catch {
77
+ // Try the next canonical/preserved candidate, then the state projection.
78
+ }
79
+ }
80
+ return fallback;
81
+ }
82
+ function feedbackSourceCandidates(pass, state) {
83
+ const preferred = [];
84
+ if (pass?.reason.includes("verify"))
85
+ preferred.push("verify-pi");
86
+ if (pass?.reviewVerdict === "request-revision") {
87
+ preferred.push("review-verdict-recovery-pi", "review-pi");
88
+ }
89
+ preferred.push("verify-pi", "review-verdict-recovery-pi", "review-pi", "process-supervisor-pi");
90
+ preferred.push(...(pass?.artifactRefs.map((ref) => ref.nodeId) ?? []));
91
+ preferred.push(...Object.keys(state.nodes));
92
+ return [...new Set(preferred)];
93
+ }
94
+ function scrubAndBoundFeedbackText(value, runDir, cwd) {
95
+ let text = value
96
+ .replaceAll(runDir, "<parent-run-dir>")
97
+ .replaceAll(runDir.replaceAll("\\", "/"), "<parent-run-dir>")
98
+ .replaceAll(cwd, "<repo-root>")
99
+ .replaceAll(cwd.replaceAll("\\", "/"), "<repo-root>")
100
+ .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "")
101
+ .trim();
102
+ if (!text) {
103
+ return "上一轮验证节点返回 request-revision,但详细反馈正文不可用;请读取证据引用并重新核对当前代码。";
104
+ }
105
+ if (text.length <= MAX_RERUN_FEEDBACK_CHARS)
106
+ return text;
107
+ const remaining = MAX_RERUN_FEEDBACK_CHARS - RERUN_FEEDBACK_TRUNCATION_MARKER.length;
108
+ const prefixLength = Math.floor(remaining * 0.7);
109
+ const suffixLength = remaining - prefixLength;
110
+ text = `${text.slice(0, prefixLength)}${RERUN_FEEDBACK_TRUNCATION_MARKER}${text.slice(-suffixLength)}`;
111
+ return text;
112
+ }
113
+ function evidenceRefs(repoRoot, runDir, pass, sourceNodeId) {
114
+ const refs = pass?.artifactRefs ?? [];
115
+ const relevant = refs.filter((ref) => ref.nodeId === sourceNodeId ||
116
+ /(?:verify|review|supervisor|repair|gate)/i.test(ref.nodeId));
117
+ const selected = relevant.length > 0 ? relevant : refs;
118
+ const result = selected.slice(0, MAX_EVIDENCE_REFS).map((ref) => ({
119
+ nodeId: ref.nodeId,
120
+ preservedNodeRecordPath: safeRepositoryRelativePath(repoRoot, runDir, ref.preservedNodeRecordPath, path.join("convergence", `pass-${pass?.pass ?? 1}`, `${ref.nodeId}.json`)),
121
+ }));
122
+ if (result.length === 0) {
123
+ result.push({
124
+ nodeId: sourceNodeId,
125
+ preservedNodeRecordPath: safeRepositoryRelativePath(repoRoot, runDir, undefined, `${sourceNodeId}.json`),
126
+ });
127
+ }
128
+ return result;
129
+ }
130
+ /**
131
+ * Derive only the latest unresolved verify/review feedback from canonical
132
+ * parent-run facts. Provider-only failures intentionally return undefined.
133
+ */
134
+ export async function deriveDagRerunFeedback(input) {
135
+ const pass = input.state.convergence?.passHistory.at(-1);
136
+ let sourceNodeId;
137
+ let sourceNode;
138
+ for (const nodeId of feedbackSourceCandidates(pass, input.state)) {
139
+ const ref = pass?.artifactRefs.find((candidate) => candidate.nodeId === nodeId);
140
+ const node = await readNodeRecord(input.runDir, pass, ref, nodeId, input.state.nodes[nodeId]);
141
+ if (requestRevision(canonicalNodeOutput(node))) {
142
+ sourceNodeId = nodeId;
143
+ sourceNode = node;
144
+ break;
145
+ }
146
+ }
147
+ if (!sourceNodeId || !sourceNode)
148
+ return undefined;
149
+ const completedPasses = pass?.pass ?? Math.max(1, input.state.convergence?.currentPass ?? 1);
150
+ const maxPasses = Math.max(completedPasses, input.state.convergence?.maxPasses ?? 1);
151
+ const parentSourceBindingHash = sourceBindingHash(input.spec);
152
+ return withDigest({
153
+ schemaVersion: 1,
154
+ kind: "unresolved-terminal-feedback",
155
+ parentRunId: input.parentRunId,
156
+ taskId: input.taskId,
157
+ sourceNodeId,
158
+ verdict: "request-revision",
159
+ ...(sourceNode.failureCategory
160
+ ? { failureCategory: sourceNode.failureCategory }
161
+ : {}),
162
+ feedbackText: scrubAndBoundFeedbackText(canonicalNodeOutput(sourceNode), input.runDir, input.state.cwd),
163
+ evidenceRefs: evidenceRefs(input.state.cwd, input.runDir, pass, sourceNodeId),
164
+ attemptSummary: {
165
+ completedPasses,
166
+ maxPasses,
167
+ ...(input.state.convergence?.terminalReason
168
+ ? { terminalReason: input.state.convergence.terminalReason }
169
+ : input.state.terminalReason
170
+ ? { terminalReason: input.state.terminalReason }
171
+ : {}),
172
+ },
173
+ binding: {
174
+ ...(parentSourceBindingHash
175
+ ? { sourceCanonicalHash: parentSourceBindingHash }
176
+ : {}),
177
+ ...(input.spec.taskContractBinding?.canonicalHash
178
+ ? {
179
+ taskContractCanonicalHash: input.spec.taskContractBinding.canonicalHash,
180
+ }
181
+ : {}),
182
+ status: "unknown",
183
+ },
184
+ });
185
+ }
186
+ /** Bind parent feedback to the newly generated live task snapshot. */
187
+ export function bindDagRerunFeedback(feedback, currentSpec) {
188
+ const comparisons = [];
189
+ const currentSourceHash = sourceBindingHash(currentSpec);
190
+ if (feedback.binding.sourceCanonicalHash) {
191
+ comparisons.push(currentSourceHash !== undefined &&
192
+ feedback.binding.sourceCanonicalHash === currentSourceHash);
193
+ }
194
+ if (feedback.binding.taskContractCanonicalHash) {
195
+ comparisons.push(currentSpec.taskContractBinding?.canonicalHash !== undefined &&
196
+ feedback.binding.taskContractCanonicalHash ===
197
+ currentSpec.taskContractBinding.canonicalHash);
198
+ }
199
+ const status = comparisons.some((same) => !same)
200
+ ? "changed"
201
+ : comparisons.length > 0
202
+ ? "exact"
203
+ : "unknown";
204
+ const { digest: _digest, ...payload } = feedback;
205
+ return withDigest({
206
+ ...payload,
207
+ binding: {
208
+ ...feedback.binding,
209
+ status,
210
+ },
211
+ });
212
+ }
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
4
- import { isSafeReadOnlyPiRetryCandidate } from "./retry-policy.js";
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",
@@ -122,32 +123,56 @@ function hasDynamicRuntime(task) {
122
123
  task.dynamicCondition ||
123
124
  task.dynamicLoopUntil);
124
125
  }
125
- function shellHasDisallowedConfig(shell) {
126
- if ((shell.commands?.length ?? 0) > 0)
127
- return true;
126
+ /**
127
+ * Narrow admission for the standard DAG's generated final verification node.
128
+ * `verifyEvidence` alone is metadata, not proof that an arbitrary shell is
129
+ * read-only, so custom shell ids and non-canonical shapes stay blocked.
130
+ */
131
+ function isCanonicalGeneratedVerifyShell(task) {
132
+ if (task.executor !== "shell")
133
+ return false;
134
+ const shell = task.shell;
135
+ if (!shell)
136
+ return false;
137
+ const evidence = shell.verifyEvidence;
138
+ if (task.id !== "verify-shell")
139
+ return false;
140
+ if (task.role !== "verifier")
141
+ return false;
142
+ if (task.writePolicy !== "read-only")
143
+ return false;
144
+ if (!evidence)
145
+ return false;
146
+ if (evidence.phase !== "final")
147
+ return false;
148
+ if (evidence.quota !== "full")
149
+ return false;
150
+ if (evidence.commandSource !== "adapter")
151
+ return false;
152
+ if (evidence.finalFullRequired !== true)
153
+ return false;
154
+ if ((shell.commands?.length ?? 0) === 0)
155
+ return false;
156
+ if (evidence.commandCount !== shell.commands.length)
157
+ return false;
158
+ // Any side-effect-capable config disqualifies the node.
128
159
  if (shell.preset)
129
- return true;
160
+ return false;
130
161
  if (shell.projectGovernanceGate)
131
- return true;
162
+ return false;
132
163
  if (shell.frontendPrewriteGate)
133
- return true;
164
+ return false;
134
165
  if (shell.frontendVerificationBundle)
135
- return true;
166
+ return false;
136
167
  if (shell.frontendReviewContext)
137
- return true;
168
+ return false;
138
169
  if (shell.backendTestPipeline)
139
- return true;
140
- if (shell.verifyEvidence)
141
- return true;
142
- if (shell.nonZeroExitPolicy)
143
- return true;
170
+ return false;
171
+ if (shell.nonZeroExitPolicy === "record")
172
+ return false;
144
173
  if (shell.envAllowlist?.length)
145
- return true;
146
- if (shell.timeoutMs !== undefined)
147
- return true;
148
- if (shell.cwd)
149
- return true;
150
- return false;
174
+ return false;
175
+ return true;
151
176
  }
152
177
  function isSafeGateOnlyShell(task) {
153
178
  if (task.executor !== "shell")
@@ -155,9 +180,32 @@ function isSafeGateOnlyShell(task) {
155
180
  const shell = task.shell;
156
181
  if (!shell)
157
182
  return false;
158
- if (shellHasDisallowedConfig(shell))
183
+ if (!SAFE_SHELL_GATE_KEYS.some((key) => shell[key] !== undefined)) {
184
+ return false;
185
+ }
186
+ // No side-effect-capable config may ride along on a gate-only shell.
187
+ // Deterministic/read-only riders are admitted: projectGovernanceGate (repo
188
+ // governance context path), cwd, and timeoutMs never execute commands, so
189
+ // the governance-standard gate stays rerun-eligible.
190
+ if ((shell.commands?.length ?? 0) > 0)
159
191
  return false;
160
- return SAFE_SHELL_GATE_KEYS.some((key) => shell[key] !== undefined);
192
+ if (shell.preset)
193
+ return false;
194
+ if (shell.frontendPrewriteGate)
195
+ return false;
196
+ if (shell.frontendVerificationBundle)
197
+ return false;
198
+ if (shell.frontendReviewContext)
199
+ return false;
200
+ if (shell.backendTestPipeline)
201
+ return false;
202
+ if (shell.verifyEvidence)
203
+ return false;
204
+ if (shell.nonZeroExitPolicy)
205
+ return false;
206
+ if (shell.envAllowlist?.length)
207
+ return false;
208
+ return true;
161
209
  }
162
210
  function isStaticTask(task) {
163
211
  return task.executor === "static";
@@ -259,7 +307,10 @@ export function computeResetClosure(spec, effectiveFromNodeId) {
259
307
  ...collectReachableDescendants(spec, effectiveFromNodeId),
260
308
  ].sort();
261
309
  }
262
- export function assessResetClosureSafety(spec, resetNodeIds) {
310
+ function isNeverStartedNodeStatus(status) {
311
+ return status === undefined || status === "SKIPPED" || status === "PENDING";
312
+ }
313
+ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
263
314
  const tasks = taskById(spec);
264
315
  const blockingNodes = [];
265
316
  const reasonCodes = [];
@@ -267,7 +318,17 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
267
318
  const task = tasks.get(nodeId);
268
319
  if (!task)
269
320
  continue;
321
+ if (options.parentNodes !== undefined &&
322
+ isNeverStartedNodeStatus(options.parentNodes[nodeId]?.status)) {
323
+ continue;
324
+ }
270
325
  if (isWriterTask(task, spec)) {
326
+ if (nodeId === options.allowedTransportWriterNodeId)
327
+ continue;
328
+ if (nodeId === options.allowedExclusiveShellFromNodeId &&
329
+ task.executor === "shell") {
330
+ continue;
331
+ }
271
332
  blockingNodes.push({
272
333
  nodeId,
273
334
  reasonCode: "restart-subgraph-contains-writer",
@@ -305,7 +366,8 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
305
366
  continue;
306
367
  }
307
368
  if (task.executor === "shell") {
308
- if (!isSafeGateOnlyShell(task)) {
369
+ if (!isSafeGateOnlyShell(task) &&
370
+ !isCanonicalGeneratedVerifyShell(task)) {
309
371
  blockingNodes.push({
310
372
  nodeId,
311
373
  reasonCode: "restart-subgraph-contains-unsafe-shell",
@@ -320,6 +382,100 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
320
382
  reasonCodes: [...new Set(reasonCodes)],
321
383
  };
322
384
  }
385
+ /**
386
+ * Experimental prompt-override admission (fail-closed):
387
+ * - requires the internal switch (see shared/dag-prompt-override.ts);
388
+ * - the target must be a failed (ERROR) node inside the reset closure, so an
389
+ * override can only adjust how an already-failed node retries — never a
390
+ * finished, skipped, or not-yet-started node;
391
+ * - only `subtask_prompt` semantics change: "append" keeps the frozen base
392
+ * and adds a marked addendum block, "replace" swaps the whole prompt;
393
+ * base/final sha256 are recorded for provenance and bound into planHash.
394
+ */
395
+ function assessPromptOverrides(input) {
396
+ const blockingNodes = [];
397
+ const reasonCodes = [];
398
+ const tasks = taskById(input.spec);
399
+ const resetSet = new Set(input.resetNodeIds);
400
+ const seen = new Set();
401
+ const entries = [];
402
+ if (!input.enabled) {
403
+ reasonCodes.push("prompt-override-disabled");
404
+ }
405
+ for (const override of input.overrides) {
406
+ const nodeId = override.nodeId?.trim() ?? "";
407
+ if (seen.has(nodeId)) {
408
+ blockingNodes.push({
409
+ nodeId,
410
+ reasonCode: "prompt-override-duplicate-node",
411
+ });
412
+ reasonCodes.push("prompt-override-duplicate-node");
413
+ continue;
414
+ }
415
+ seen.add(nodeId);
416
+ const task = tasks.get(nodeId);
417
+ if (!task) {
418
+ blockingNodes.push({
419
+ nodeId,
420
+ reasonCode: "prompt-override-node-missing",
421
+ });
422
+ reasonCodes.push("prompt-override-node-missing");
423
+ continue;
424
+ }
425
+ if (!resetSet.has(nodeId)) {
426
+ blockingNodes.push({
427
+ nodeId,
428
+ reasonCode: "prompt-override-outside-reset-closure",
429
+ });
430
+ reasonCodes.push("prompt-override-outside-reset-closure");
431
+ continue;
432
+ }
433
+ if (input.state.nodes[nodeId]?.status !== "ERROR") {
434
+ blockingNodes.push({
435
+ nodeId,
436
+ reasonCode: "prompt-override-node-not-failed",
437
+ });
438
+ reasonCodes.push("prompt-override-node-not-failed");
439
+ continue;
440
+ }
441
+ const mode = override.mode === "append" ? "append" : "replace";
442
+ const prompt = override.prompt ?? "";
443
+ if (!prompt.trim() ||
444
+ prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
445
+ blockingNodes.push({
446
+ nodeId,
447
+ reasonCode: "prompt-override-invalid-prompt",
448
+ });
449
+ reasonCodes.push("prompt-override-invalid-prompt");
450
+ continue;
451
+ }
452
+ const basePromptSha256 = createHash("sha256")
453
+ .update(task.subtask_prompt, "utf8")
454
+ .digest("hex");
455
+ const finalPrompt = composeDagPromptOverridePrompt({
456
+ basePrompt: task.subtask_prompt,
457
+ text: prompt,
458
+ mode,
459
+ });
460
+ const promptSha256 = createHash("sha256")
461
+ .update(finalPrompt, "utf8")
462
+ .digest("hex");
463
+ if (promptSha256 === basePromptSha256) {
464
+ blockingNodes.push({
465
+ nodeId,
466
+ reasonCode: "prompt-override-unchanged",
467
+ });
468
+ reasonCodes.push("prompt-override-unchanged");
469
+ continue;
470
+ }
471
+ entries.push({ nodeId, mode, basePromptSha256, promptSha256 });
472
+ }
473
+ return {
474
+ entries: entries.length > 0 ? entries : undefined,
475
+ blockingNodes,
476
+ reasonCodes: [...new Set(reasonCodes)],
477
+ };
478
+ }
323
479
  export function computePlanHash(planWithoutHash) {
324
480
  const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
325
481
  return createHash("sha256").update(canonical, "utf8").digest("hex");
@@ -448,6 +604,20 @@ export async function evaluateDagRerunPlan(input) {
448
604
  : [];
449
605
  const resetSet = new Set(resetNodeIds);
450
606
  const importedNodeIds = allNodeIds.filter((nodeId) => !resetSet.has(nodeId));
607
+ const tasksByIdForPlan = taskById(input.parentSpec);
608
+ const effectiveTask = effectiveFromNodeId
609
+ ? tasksByIdForPlan.get(effectiveFromNodeId)
610
+ : undefined;
611
+ const effectiveRecord = effectiveFromNodeId
612
+ ? input.parentState.nodes[effectiveFromNodeId]
613
+ : undefined;
614
+ const transportWriterRestart = Boolean(effectiveFromNodeId &&
615
+ effectiveRecord?.status === "ERROR" &&
616
+ isTransportCrashedExclusiveWriter(effectiveTask, effectiveRecord.failureCategory));
617
+ const convergenceWriterRestart = Boolean(transportWriterRestart &&
618
+ input.parentSpec.convergence?.enabled === true &&
619
+ input.parentState.convergence?.enabled === true &&
620
+ !input.parentState.convergence.terminalReason);
451
621
  const controllerIdentityMatched = input.currentControllerFingerprint === undefined ||
452
622
  input.parentControllerFingerprint === undefined ||
453
623
  input.currentControllerFingerprint === input.parentControllerFingerprint;
@@ -482,11 +652,11 @@ export async function evaluateDagRerunPlan(input) {
482
652
  reasonCodes.push("parent-lifecycle-ineligible");
483
653
  blockedReasons.push("parent-lifecycle-ineligible");
484
654
  }
485
- if (input.parentSpec.convergence?.enabled) {
655
+ if (input.parentSpec.convergence?.enabled && !convergenceWriterRestart) {
486
656
  reasonCodes.push("convergence-rerun-unsupported");
487
657
  blockedReasons.push("convergence-rerun-unsupported");
488
658
  }
489
- if (input.parentState.convergence?.enabled) {
659
+ if (input.parentState.convergence?.enabled && !convergenceWriterRestart) {
490
660
  reasonCodes.push("convergence-rerun-unsupported");
491
661
  blockedReasons.push("convergence-rerun-unsupported");
492
662
  }
@@ -530,7 +700,17 @@ export async function evaluateDagRerunPlan(input) {
530
700
  blockedReasons.push("ambiguous-skipped-rewrite");
531
701
  }
532
702
  }
533
- const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds);
703
+ const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds, {
704
+ ...(transportWriterRestart && effectiveFromNodeId
705
+ ? { allowedTransportWriterNodeId: effectiveFromNodeId }
706
+ : {}),
707
+ parentNodes: input.parentState.nodes,
708
+ ...(effectiveFromNodeId &&
709
+ effectiveTask?.executor === "shell" &&
710
+ effectiveRecord?.status === "ERROR"
711
+ ? { allowedExclusiveShellFromNodeId: effectiveFromNodeId }
712
+ : {}),
713
+ });
534
714
  blockingNodes.push(...closureSafety.blockingNodes);
535
715
  for (const code of closureSafety.reasonCodes) {
536
716
  reasonCodes.push(code);
@@ -563,6 +743,22 @@ export async function evaluateDagRerunPlan(input) {
563
743
  blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
564
744
  }
565
745
  }
746
+ let promptOverrideEntries;
747
+ if (input.promptOverrides !== undefined && input.promptOverrides.length > 0) {
748
+ const overrideAssessment = assessPromptOverrides({
749
+ spec: input.parentSpec,
750
+ state: input.parentState,
751
+ resetNodeIds,
752
+ overrides: input.promptOverrides,
753
+ enabled: input.promptOverrideEnabled === true,
754
+ });
755
+ blockingNodes.push(...overrideAssessment.blockingNodes);
756
+ for (const code of overrideAssessment.reasonCodes) {
757
+ reasonCodes.push(code);
758
+ blockedReasons.push(code);
759
+ }
760
+ promptOverrideEntries = overrideAssessment.entries;
761
+ }
566
762
  const uniqueReasonCodes = [...new Set(reasonCodes)];
567
763
  const uniqueBlockedReasons = [...new Set(blockedReasons)];
568
764
  const eligible = uniqueBlockedReasons.length === 0 &&
@@ -606,6 +802,7 @@ export async function evaluateDagRerunPlan(input) {
606
802
  parentLifecycle: input.parentLifecycle,
607
803
  workerManaged: input.workerManaged,
608
804
  }),
805
+ ...(promptOverrideEntries ? { promptOverrides: promptOverrideEntries } : {}),
609
806
  };
610
807
  return {
611
808
  ...planWithoutHash,
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { access, copyFile, mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
+ import { composeDagPromptOverridePrompt, isDagRerunPromptOverrideEnabled, } from "../../shared/dag-prompt-override.js";
5
6
  import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
6
7
  import { readControllerIdentityArtifact, captureControllerIdentity } from "./controller-identity.js";
7
8
  import { DAG_RUNS_DIR, getDagRunDir, isDagNonExecutionLifecycle, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
@@ -190,6 +191,8 @@ export async function planDagRerun(input) {
190
191
  parentSpec: nonCompletedSpec,
191
192
  parentState: nonCompletedState,
192
193
  parentLifecycle,
194
+ promptOverrides: input.promptOverrides,
195
+ promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
193
196
  workerManaged: nonCompletedWorkerHints.workerManaged,
194
197
  workerAssociation: nonCompletedWorkerHints.workerManaged
195
198
  ? {
@@ -229,6 +232,8 @@ export async function planDagRerun(input) {
229
232
  parentTerminalWorkspace,
230
233
  currentWorkspace,
231
234
  skillSnapshotOk,
235
+ promptOverrides: input.promptOverrides,
236
+ promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
232
237
  workerManaged: workerHints.workerManaged,
233
238
  workerAssociation: workerHints.workerManaged
234
239
  ? {
@@ -384,15 +389,57 @@ function buildPendingNodeRecord(task) {
384
389
  ...(task.outputMode ? { outputMode: task.outputMode } : {}),
385
390
  };
386
391
  }
392
+ /**
393
+ * Stage the continuation spec with operator prompt overrides applied. The
394
+ * parent spec stays frozen; every override must match the plan entry (sha256
395
+ * of the composed final prompt and of the parent base prompt) so the
396
+ * two-phase planHash remains the single source of truth for what changed.
397
+ * "append" entries keep the frozen base and append the operator addendum in a
398
+ * marked block; "replace" entries swap the whole prompt.
399
+ */
400
+ function applyPromptOverridesToSpec(input) {
401
+ const planned = new Map((input.plan.promptOverrides ?? []).map((entry) => [entry.nodeId, entry]));
402
+ const spec = structuredClone(input.spec);
403
+ for (const override of input.overrides) {
404
+ const entry = planned.get(override.nodeId);
405
+ if (!entry) {
406
+ throw new Error(`prompt override is not present in the rerun plan: ${override.nodeId}`);
407
+ }
408
+ const task = spec.tasks.find((candidate) => candidate.id === override.nodeId);
409
+ if (!task) {
410
+ throw new Error(`prompt override node missing from spec: ${override.nodeId}`);
411
+ }
412
+ if (sha256Hex(task.subtask_prompt) !== entry.basePromptSha256) {
413
+ throw new Error(`prompt override base prompt drifted from plan: ${override.nodeId}`);
414
+ }
415
+ const finalPrompt = composeDagPromptOverridePrompt({
416
+ basePrompt: task.subtask_prompt,
417
+ text: override.prompt,
418
+ mode: override.mode === "append" ? "append" : "replace",
419
+ });
420
+ if (sha256Hex(finalPrompt) !== entry.promptSha256) {
421
+ throw new Error(`prompt override text does not match the planned sha256: ${override.nodeId}`);
422
+ }
423
+ task.subtask_prompt = finalPrompt;
424
+ }
425
+ return spec;
426
+ }
387
427
  async function stageContinuationRun(input) {
388
- const { ranks } = topoSortToRanks(input.parentSpec);
389
- const runId = await buildDagRunId(input.parentSpec, { cwd: input.cwd });
428
+ const specForRun = input.promptOverrides !== undefined && input.promptOverrides.length > 0
429
+ ? applyPromptOverridesToSpec({
430
+ spec: input.parentSpec,
431
+ plan: input.plan,
432
+ overrides: input.promptOverrides,
433
+ })
434
+ : input.parentSpec;
435
+ const { ranks } = topoSortToRanks(specForRun);
436
+ const runId = await buildDagRunId(specForRun, { cwd: input.cwd });
390
437
  const runDir = getDagRunDir(input.cwd, "active", runId);
391
438
  await prepareActiveRunDir(runDir);
392
- await writeRunSpec(runDir, input.parentSpec);
439
+ await writeRunSpec(runDir, specForRun);
393
440
  const resetSet = new Set(input.plan.resetNodeIds);
394
441
  const importedSet = new Set(input.plan.importedNodeIds);
395
- const tasksById = new Map(input.parentSpec.tasks.map((task) => [task.id, task]));
442
+ const tasksById = new Map(specForRun.tasks.map((task) => [task.id, task]));
396
443
  const importManifestNodes = [];
397
444
  const nodes = {};
398
445
  for (const nodeId of input.plan.importedNodeIds) {
@@ -433,6 +480,9 @@ async function stageContinuationRun(input) {
433
480
  reason: input.reason,
434
481
  requestId: input.requestId,
435
482
  planHash: input.plan.planHash,
483
+ ...(input.plan.promptOverrides
484
+ ? { promptOverrides: input.plan.promptOverrides }
485
+ : {}),
436
486
  createdAt,
437
487
  };
438
488
  const state = {
@@ -445,6 +495,9 @@ async function stageContinuationRun(input) {
445
495
  ranks,
446
496
  nodes,
447
497
  continuation,
498
+ ...(input.parentState.convergence
499
+ ? { convergence: structuredClone(input.parentState.convergence) }
500
+ : {}),
448
501
  ...(input.parentState.evaluation
449
502
  ? { evaluation: structuredClone(input.parentState.evaluation) }
450
503
  : {}),
@@ -479,7 +532,7 @@ async function stageContinuationRun(input) {
479
532
  await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
480
533
  await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
481
534
  await writeRunState(runDir, state);
482
- return { runId, runDir, state };
535
+ return { runId, runDir, state, spec: specForRun };
483
536
  }
484
537
  export async function executeDagRerun(input) {
485
538
  const existing = await readExistingOperatorRequest(input.cwd, input.requestId);
@@ -490,6 +543,7 @@ export async function executeDagRerun(input) {
490
543
  cwd: input.cwd,
491
544
  parentRunId: input.parentRunId,
492
545
  selectedNodeId: input.selectedNodeId,
546
+ promptOverrides: input.promptOverrides,
493
547
  });
494
548
  if (plan.planHash !== input.planHash) {
495
549
  return {
@@ -527,10 +581,11 @@ export async function executeDagRerun(input) {
527
581
  plan,
528
582
  reason: input.reason,
529
583
  requestId: input.requestId,
584
+ promptOverrides: input.promptOverrides,
530
585
  });
531
586
  const continuationOptions = {
532
587
  cwd: input.cwd,
533
- spec: parentSpec,
588
+ spec: staged.spec,
534
589
  state: staged.state,
535
590
  runDir: staged.runDir,
536
591
  ...(input.maxConcurrent !== undefined