@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
@@ -0,0 +1,84 @@
1
+ import os from "node:os";
2
+ import { readPackageVersion } from "../../shared/package-metadata.js";
3
+ import { createNpmUpdateClient } from "../../shared/update/npm-client.js";
4
+ import { acquireUpdateLock, updateLockPath } from "../../shared/update/state.js";
5
+ import { readInitRuntimeActivity } from "../observability/init-runtime-activity.js";
6
+ import { performConsoleUpdateAndInit, } from "./console-update-and-init.js";
7
+ import { TERMINAL_OPERATION_STATES } from "./operation-store.js";
8
+ export function createConsoleUpdateExecutor(input) {
9
+ return async (channel) => {
10
+ const currentVersion = readPackageVersion(input.packageRoot);
11
+ if (!currentVersion) {
12
+ return {
13
+ state: "failed",
14
+ errorCode: "PACKAGE_IDENTITY_UNAVAILABLE",
15
+ errorMessage: "Console package version cannot be proven",
16
+ maintenance: {
17
+ package: { status: "failed", channel },
18
+ initialization: { status: "not-run", pendingPaths: [] },
19
+ handoff: { status: "not-needed" },
20
+ },
21
+ };
22
+ }
23
+ const npm = createNpmUpdateClient({
24
+ currentVersion,
25
+ packageRoot: input.packageRoot,
26
+ cwd: input.repoRoot,
27
+ });
28
+ return performConsoleUpdateAndInit({
29
+ currentVersion,
30
+ preflight: async () => {
31
+ const [activity, operations] = await Promise.all([
32
+ readInitRuntimeActivity(input.repoRoot),
33
+ input.operations.list(),
34
+ ]);
35
+ if (activity.workerProjectionError ||
36
+ activity.activeDagRunIds.length > 0 ||
37
+ activity.activeWorkerTasks > 0 ||
38
+ activity.activeWorkerBatches > 0) {
39
+ return {
40
+ ok: false,
41
+ code: "ACTIVE_RUNTIME",
42
+ message: "active DAG/Worker runtime exists or cannot be proven inactive",
43
+ };
44
+ }
45
+ if (input.activeChatTurnCount() > 0) {
46
+ return {
47
+ ok: false,
48
+ code: "CHAT_TURN_ACTIVE",
49
+ message: "an Operator Chat turn is active",
50
+ };
51
+ }
52
+ const liveOtherOperation = operations.some((operation) => operation.action !== "consoleUpdateAndInit" &&
53
+ !TERMINAL_OPERATION_STATES.has(operation.state));
54
+ return liveOtherOperation
55
+ ? {
56
+ ok: false,
57
+ code: "OPERATION_ACTIVE",
58
+ message: "another Console operation is active",
59
+ }
60
+ : { ok: true };
61
+ },
62
+ acquireLock: () => acquireUpdateLock(updateLockPath(os.homedir())),
63
+ resolveVersion: (selectedChannel) => npm.distTagVersionCheck(selectedChannel),
64
+ canAutoInstall: () => npm.canAutoInstall(),
65
+ installExact: (version, selectedChannel) => npm.installExact(version, selectedChannel),
66
+ verifyInstalled: (version) => npm.verifyInstalled(version),
67
+ applySafeInit: async () => {
68
+ const result = await input.client.runExternal("loop-agent", ["init", "update", "--apply-safe", "--repo-root", input.repoRoot], {
69
+ cwd: input.repoRoot,
70
+ artifactName: "console-update-safe-init",
71
+ timeoutMs: 120_000,
72
+ });
73
+ return result.ok
74
+ ? { ok: true, pendingPaths: [] }
75
+ : {
76
+ ok: false,
77
+ message: result.stderr || result.stdout || "safe initialization failed",
78
+ pendingPaths: [],
79
+ };
80
+ },
81
+ requestHandoff: async ({ targetVersion }) => input.requestHandoff(targetVersion),
82
+ }, channel);
83
+ };
84
+ }
@@ -35,6 +35,32 @@ import { hashDagBytes, } from "./dag-confirmation.js";
35
35
  * framework invocations that don't embed the word "verify".
36
36
  */
37
37
  export const STRUCTURED_VERIFICATION_COMMAND_RE = /\b(vitest|jest|playwright|cypress|pytest|py-test|go\s+test|cargo\s+test|npm\s+run\s+(lint|typecheck|test|verify)|npm\s+(test|run\s+test)|check-repo\.sh|loop-agent-standard-verify)\b/i;
38
+ /**
39
+ * Deterministic shell preset keys whose executors are themselves structured
40
+ * verification/gate materializers: their shell.commands[] is empty and the
41
+ * preset alone carries the verification authority (hash-bound artifact
42
+ * gates, evidence validators, report finalize). Mirrors the named-shell
43
+ * handler registry in shell-executor.ts so preset-only workflows
44
+ * (frontend-test finalize/reports, backend-test pipeline, verify-evidence
45
+ * gates) pass the G2 autonomous execution gate without depending on node-id
46
+ * naming conventions.
47
+ */
48
+ export const STRUCTURED_VERIFICATION_SHELL_PRESETS = new Set([
49
+ "frontendTestCaseChecklist",
50
+ "frontendTestCaseManifest",
51
+ "frontendTestEvidenceValidation",
52
+ "frontendTestResultFinalize",
53
+ "frontendTestL5Report",
54
+ "frontendTestHtmlReport",
55
+ "frontendTestReports",
56
+ "backendTestPipeline",
57
+ "verifyEvidence",
58
+ "repairArtifactGate",
59
+ "jacocoCoverage",
60
+ "requirementCoverageGate",
61
+ "jsonArtifactGate",
62
+ "verdictGate",
63
+ ]);
38
64
  export const DEFAULT_EXECUTION_RECEIPT_TTL_MS = 30 * 60 * 1000;
39
65
  function normalizePathEntry(entry) {
40
66
  return entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
@@ -157,6 +183,13 @@ export function deriveEligibilityFactsFromDagSpec(input) {
157
183
  return false;
158
184
  if (/verify|verification/i.test(task.id ?? ""))
159
185
  return true;
186
+ // Preset-only verification shells (empty commands[]): the preset key is
187
+ // the verification authority (frontend-test finalize, backend-test
188
+ // pipeline, evidence/artifact gates).
189
+ const shellRecord = task.shell;
190
+ const hasVerificationPreset = Object.keys(shellRecord).some((key) => STRUCTURED_VERIFICATION_SHELL_PRESETS.has(key));
191
+ if (hasVerificationPreset)
192
+ return true;
160
193
  const commands = stringList(task.shell.commands);
161
194
  return commands.some((command) => STRUCTURED_VERIFICATION_COMMAND_RE.test(command));
162
195
  });
@@ -1,5 +1,51 @@
1
1
  import path from "node:path";
2
2
  import { readJsonIfExists, sha256Json, writeSecureJson, } from "./app-data.js";
3
+ /**
4
+ * 从任意上游载荷安全投影 ledger 摘要(未知形状丢弃,绝不抛错、绝不信任字段外的数据)。
5
+ * 供 DraftStore / prd-intake-bridge 把 prepare/advance 结果中的 ledger 摘要带入草稿。
6
+ */
7
+ export function summarizeLedgerPayload(value) {
8
+ if (!value || typeof value !== "object")
9
+ return undefined;
10
+ const record = value;
11
+ if (record.present !== true)
12
+ return undefined;
13
+ const num = (key) => {
14
+ const raw = record[key];
15
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : 0;
16
+ };
17
+ const gateRaw = record.gateStatus;
18
+ const gateStatus = gateRaw === "pass" || gateRaw === "fail" || gateRaw === "unknown"
19
+ ? gateRaw
20
+ : "unknown";
21
+ const failureCode = typeof record.failureCode === "string" && record.failureCode
22
+ ? record.failureCode
23
+ : null;
24
+ const rawExclusions = Array.isArray(record.exclusions) ? record.exclusions : [];
25
+ const exclusions = [];
26
+ for (const item of rawExclusions) {
27
+ if (!item || typeof item !== "object")
28
+ continue;
29
+ const entry = item;
30
+ const fragmentId = typeof entry.fragmentId === "string" ? entry.fragmentId : "";
31
+ const reason = typeof entry.reason === "string" ? entry.reason : "";
32
+ if (!fragmentId)
33
+ continue;
34
+ exclusions.push({ fragmentId, reason });
35
+ }
36
+ return {
37
+ present: true,
38
+ fragmentCount: num("fragmentCount"),
39
+ canonicalRequirementCount: num("canonicalRequirementCount"),
40
+ contextOnlyCount: num("contextOnlyCount"),
41
+ duplicateCount: num("duplicateCount"),
42
+ unresolvedCount: num("unresolvedCount"),
43
+ conflictCount: num("conflictCount"),
44
+ gateStatus,
45
+ failureCode,
46
+ exclusions,
47
+ };
48
+ }
3
49
  function draftPath(appData, taskId) {
4
50
  const safe = taskId.replace(/[^a-zA-Z0-9._-]+/g, "_");
5
51
  return path.join(appData.drafts, `${safe}.json`);
@@ -86,6 +132,9 @@ export function toCanonicalDraftForCli(draft) {
86
132
  if (draft.interviewAssessmentRef) {
87
133
  canonical.interviewAssessmentRef = draft.interviewAssessmentRef;
88
134
  }
135
+ // AC-BLOCK-002:canonical draft JSON 绝不携带 ledgerSummary 字段(strict
136
+ // taskContractDraftSchema 拒绝未知键)。摘要仅留在 prd-intake-bridge 响应与
137
+ // console-types 的 LedgerSummaryView(UI 视图模型),不进入持久化 draft。
89
138
  return canonical;
90
139
  }
91
140
  export class DraftStore {
@@ -0,0 +1,19 @@
1
+ import { parseFrontendHumanDecisionProjection } from "../../workflows/dag/frontend-human-decision.js";
2
+ /** Worker/Console persistence seam. Canonical validation and freshness remain in the workflow service. */
3
+ export function createFrontendHumanDecisionAdapter(store) {
4
+ return {
5
+ async load(operationId) {
6
+ const record = await store.get(operationId);
7
+ if (!record || record.revision === undefined || !record.frontendHumanDecision)
8
+ return undefined;
9
+ return { revision: record.revision, projection: parseFrontendHumanDecisionProjection(record.frontendHumanDecision) };
10
+ },
11
+ materialize(input) {
12
+ return store.materializeFrontendHumanDecision(input);
13
+ },
14
+ consume(input) {
15
+ return store.consumeFrontendHumanDecision(input);
16
+ },
17
+ initializeSplit: store.initializeFrontendHumanApprovedSplit.bind(store),
18
+ };
19
+ }
@@ -0,0 +1,20 @@
1
+ import { FrontendSplitOrchestrator, frontendSplitProjectionSchema, } from "../../workflows/dag/frontend-split-application-service.js";
2
+ /** Worker/Console boundary adapter. All orchestration policy remains canonical. */
3
+ export function createFrontendSplitOperationAdapter(dependencies) {
4
+ const ports = {
5
+ load: async (operationId) => {
6
+ const record = await dependencies.store.get(operationId);
7
+ return record?.frontendSplit === undefined ? undefined : frontendSplitProjectionSchema.parse(record.frontendSplit);
8
+ },
9
+ compareAndSwap: (input) => dependencies.store.compareAndSwapFrontendSplit(input),
10
+ materialize: async (request) => {
11
+ const result = await dependencies.materializer.materialize(request);
12
+ return { taskId: result.taskId, operationId: result.operationId };
13
+ },
14
+ schedule: dependencies.schedule,
15
+ auditStale: dependencies.auditStale,
16
+ loadChildCloseoutFacts: (input) => dependencies.store.loadChildCloseoutFacts(input),
17
+ settleAggregate: (input) => dependencies.store.settleFrontendSplitAggregate({ ...input, prepareAggregate: dependencies.prepareAggregate }),
18
+ };
19
+ return new FrontendSplitOrchestrator(ports);
20
+ }
@@ -11,6 +11,9 @@ export { createClosedResourceLoader, createInterviewResourceLoader, tryActivateT
11
11
  export { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
12
12
  export { openConsoleAppData, defaultConsoleAppDataRoot } from "./app-data.js";
13
13
  export { OperationStore } from "./operation-store.js";
14
+ export { createFrontendSplitOperationAdapter } from "./frontend-split-operation-adapter.js";
15
+ export { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
16
+ export { FrontendSplitTaskMaterializer } from "../materialize/frontend-split-task-materializer.js";
14
17
  export { createOperationEventStore } from "./operation-sse.js";
15
18
  export { DagConfirmationStore } from "./dag-confirmation.js";
16
19
  export { DraftStore } from "./draft-store.js";
@@ -80,3 +80,21 @@ export function pickInspectNode(nodes, preferredNodeId) {
80
80
  });
81
81
  return running ?? nodes[0];
82
82
  }
83
+ /**
84
+ * Terminal DagRun effective/status values (mirrors observe read-model
85
+ * TERMINAL_RUN_STATUSES). A run that reached one of these states no longer
86
+ * needs live polling in the console split panel.
87
+ */
88
+ export const TERMINAL_DAG_RUN_STATUSES = new Set([
89
+ "finished",
90
+ "failed",
91
+ "partial_failed",
92
+ "superseded",
93
+ "abandoned",
94
+ ]);
95
+ export function isTerminalDagRun(dag) {
96
+ if (!dag)
97
+ return false;
98
+ const status = String(dag.effectiveStatus ?? dag.status ?? "").toLowerCase();
99
+ return TERMINAL_DAG_RUN_STATUSES.has(status);
100
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * DSH's native directory-picker adapter.
3
+ *
4
+ * The published DSH host package uses the modern Win32 IFileOpenDialog in a
5
+ * dedicated child process (plus osascript/Zenity/KDialog on other platforms),
6
+ * so the Console does not pay PowerShell startup cost or lose the chooser
7
+ * behind the browser window.
8
+ */
9
+ import { pickNativeDirectory, } from "@deepseek-ai/dsh-host-directory-picker-native";
10
+ /** Open the host OS single-folder chooser. Cancellation is a null result. */
11
+ export async function pickNativeWorkspaceDirectory(signal, internals = {}) {
12
+ return pickNativeDirectory(signal, internals);
13
+ }
@@ -13,6 +13,7 @@
13
13
  * - A run summary is a projection only: it can never change the terminal
14
14
  * operation state decided by the command envelope/exit facts.
15
15
  */
16
+ import { normalizeDagFailureCategory } from "../../shared/dag-failure-category.js";
16
17
  import { scrubSecrets } from "./chat/explore-tools.js";
17
18
  const RUN_SUMMARY_MAX_NODES = 20;
18
19
  const RUN_SUMMARY_MAX_TEXT_CHARS = 600;
@@ -72,7 +73,13 @@ function safeNode(value) {
72
73
  const status = boundedRedactedText(rec.status, 100);
73
74
  if (!id || !status)
74
75
  return undefined;
75
- return { id, status };
76
+ const rawCategory = boundedRedactedText(rec.failureCategory, 200);
77
+ if (!rawCategory)
78
+ return { id, status };
79
+ const category = normalizeDagFailureCategory(rawCategory, status);
80
+ if (category === "success" || category === "skipped")
81
+ return { id, status };
82
+ return { id, status, failureCategory: category };
76
83
  }
77
84
  /** Independent allowlist projection of a structured run summary. */
78
85
  export function safeRunSummaryForProjection(value) {
@@ -117,13 +124,22 @@ export function safeRunSummaryForProjection(value) {
117
124
  }
118
125
  }
119
126
  const verdict = boundedRedactedText(rec.verdict);
120
- const failureCategory = boundedRedactedText(rec.failureCategory);
127
+ const rawFailureCategory = boundedRedactedText(rec.failureCategory, 200);
128
+ // Normalize the run-level category too (2026-08-23): raw codes like
129
+ // missing-api-key / nonzero-exit never matched the browser advice table.
130
+ const failureCategory = rawFailureCategory !== undefined
131
+ ? normalizeDagFailureCategory(rawFailureCategory, status)
132
+ : undefined;
121
133
  return {
122
134
  runId,
123
135
  status,
124
136
  nodes,
125
137
  ...(verdict !== undefined ? { verdict } : {}),
126
- ...(failureCategory !== undefined ? { failureCategory } : {}),
138
+ ...(failureCategory !== undefined &&
139
+ failureCategory !== "success" &&
140
+ failureCategory !== "skipped"
141
+ ? { failureCategory }
142
+ : {}),
127
143
  ...(outOfBounds.length > 0 ? { outOfBounds } : {}),
128
144
  ...(next ? { next } : {}),
129
145
  };
@@ -1,7 +1,11 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import path from "node:path";
3
+ import { assertFrontendSplitProjectionSuccessor, frontendSplitProjectionSchema, } from "../../workflows/dag/frontend-split-application-service.js";
4
+ import { buildFrontendHumanDecisionProjection, consumeFrontendHumanDecision, materializeFrontendHumanDecision, parseFrontendHumanDecisionProjection, parseFrontendHumanDecisionReceipt, } from "../../workflows/dag/frontend-human-decision.js";
5
+ import { buildFrontendSplitAggregateCloseout, computeFrontendSplitDigest, finalizeFrontendSplitAggregate, frontendCommittedFactRefSchema, frontendSplitChildCloseoutFactsSchema, initializeFrontendSplitParent, } from "../../workflows/dag/frontend-split-orchestrator.js";
6
+ import { frontendCloseoutFactsSchema, frontendTestHandoffSchema, } from "../../workflows/dag/frontend-closeout.js";
3
7
  import { listJsonFiles, readJsonIfExists, sha256Json, writeSecureJson, } from "./app-data.js";
4
- export const TERMINAL_OPERATION_STATES = new Set(["succeeded", "failed", "timed-out"]);
8
+ export const TERMINAL_OPERATION_STATES = new Set(["succeeded", "failed", "timed-out", "restarting"]);
5
9
  function operationFile(appData, operationId) {
6
10
  return path.join(appData.operations, `${operationId}.json`);
7
11
  }
@@ -15,16 +19,21 @@ export function computeActionPayloadHash(action, actionParams) {
15
19
  export function newOperationId() {
16
20
  return `op_${randomBytes(12).toString("hex")}`;
17
21
  }
22
+ const sharedWriteChains = new Map();
18
23
  export class OperationStore {
19
24
  appData;
20
- /**
21
- * Per-key write lock. Concurrent update/save/create on the same operation
22
- * or clientRequestId must not race rename() into the same JSON path
23
- * (Windows EPERM) or last-write-wins lost patches.
24
- */
25
- writeChains = new Map();
25
+ /** Shared per-store-root, per-key write lock. Multiple OperationStore instances
26
+ * in one worker process must observe the same CAS critical section. */
27
+ writeChains;
26
28
  constructor(appData) {
27
29
  this.appData = appData;
30
+ const root = path.resolve(appData.operations);
31
+ let chains = sharedWriteChains.get(root);
32
+ if (!chains) {
33
+ chains = new Map();
34
+ sharedWriteChains.set(root, chains);
35
+ }
36
+ this.writeChains = chains;
28
37
  }
29
38
  serialize(key, op) {
30
39
  const prev = this.writeChains.get(key) ?? Promise.resolve();
@@ -66,6 +75,7 @@ export class OperationStore {
66
75
  payloadHash,
67
76
  state: "queued",
68
77
  createdAt: now,
78
+ revision: 1,
69
79
  taskId: input.taskId,
70
80
  cliArgs: input.cliArgs,
71
81
  ...(input.externalCommand
@@ -77,11 +87,61 @@ export class OperationStore {
77
87
  return { kind: "created", operation };
78
88
  });
79
89
  }
90
+ /**
91
+ * Idempotently materialize a split child/integration operation with a
92
+ * caller-specified deterministic operationId (from the split manifest).
93
+ * Unlike create() (random op_<hex> id) and save/update (no create), this is
94
+ * the durable cross-restart idempotency boundary for the production
95
+ * FrontendSplitTaskMaterializer backend: an existing operation file with the
96
+ * same taskId + payloadHash is returned as {created:false}; any drift fails
97
+ * closed instead of overwriting or silently diverging.
98
+ */
99
+ async ensureSplitMaterializedOperation(input) {
100
+ return this.serialize(input.operationId, async () => {
101
+ const existing = await this.get(input.operationId);
102
+ if (existing) {
103
+ if (existing.taskId !== input.taskId ||
104
+ existing.payloadHash !== input.payloadHash) {
105
+ throw new Error("frontend split operation materialization payload drift");
106
+ }
107
+ return { operation: existing, created: false };
108
+ }
109
+ const now = new Date().toISOString();
110
+ const operation = {
111
+ schemaVersion: 1,
112
+ operationId: input.operationId,
113
+ clientRequestId: `split:${input.operationId}`,
114
+ action: input.action,
115
+ actionParams: input.actionParams,
116
+ payloadHash: input.payloadHash,
117
+ state: "queued",
118
+ createdAt: now,
119
+ revision: 1,
120
+ taskId: input.taskId,
121
+ ...(input.cliArgs ? { cliArgs: input.cliArgs } : {}),
122
+ };
123
+ await writeSecureJson(operationFile(this.appData, input.operationId), operation);
124
+ return { operation, created: true };
125
+ });
126
+ }
80
127
  async get(operationId) {
81
128
  return readJsonIfExists(operationFile(this.appData, operationId));
82
129
  }
83
130
  async save(operation) {
84
131
  return this.serialize(operation.operationId, async () => {
132
+ const current = await this.get(operation.operationId);
133
+ if (current?.revision !== undefined && operation.revision !== current.revision)
134
+ throw new Error("operation revision may not be changed by save");
135
+ if (operation.frontendHumanDecision !== undefined || current?.frontendHumanDecision !== undefined) {
136
+ if (!current || JSON.stringify(operation) !== JSON.stringify(current))
137
+ throw new Error("human-decision projection may only be mutated by canonical CAS");
138
+ return current;
139
+ }
140
+ if (operation.frontendSplit !== undefined || current?.frontendSplit !== undefined) {
141
+ if (!current || JSON.stringify(operation) !== JSON.stringify(current))
142
+ throw new Error("split projection may only be mutated by compareAndSwapFrontendSplit");
143
+ return current;
144
+ }
85
145
  await writeSecureJson(operationFile(this.appData, operation.operationId), operation);
86
146
  return operation;
87
147
  });
@@ -92,6 +152,10 @@ export class OperationStore {
92
152
  if (!current) {
93
153
  throw new Error(`operation not found: ${operationId}`);
94
154
  }
155
+ if ("frontendHumanDecision" in patch || (current.frontendHumanDecision !== undefined && ("state" in patch || "frontendExecution" in patch)))
156
+ throw new Error("human-decision projection may only be mutated by canonical CAS");
157
+ if ("frontendSplit" in patch || "revision" in patch || (current.frontendSplit !== undefined && ("state" in patch || "frontendExecution" in patch)))
158
+ throw new Error("split projection may only be mutated by compareAndSwapFrontendSplit");
95
159
  const next = {
96
160
  ...current,
97
161
  ...patch,
@@ -106,6 +170,162 @@ export class OperationStore {
106
170
  return next;
107
171
  });
108
172
  }
173
+ async materializeFrontendHumanDecision(input) {
174
+ return this.serialize(input.operationId, async () => {
175
+ const current = await this.get(input.operationId);
176
+ if (!current || current.revision === undefined || current.revision !== input.expectedRecordRevision) {
177
+ const fallback = current?.frontendHumanDecision ?? buildFrontendHumanDecisionProjection({ status: "active", operationId: input.operationId, runId: input.envelope.runId, revision: Math.max(1, input.expectedRecordRevision) });
178
+ return { outcome: "rejected", reason: "record revision conflict or legacy record", projection: fallback };
179
+ }
180
+ const projection = current.frontendHumanDecision ?? buildFrontendHumanDecisionProjection({ status: "active", operationId: current.operationId, runId: input.envelope.runId, revision: current.revision });
181
+ const result = materializeFrontendHumanDecision({ projection, envelope: input.envelope, expectedRecordRevision: input.expectedRecordRevision, expectedProjectionDigest: input.expectedProjectionDigest });
182
+ if (result.outcome !== "applied")
183
+ return result;
184
+ const record = { ...current, revision: result.projection.revision, frontendHumanDecision: result.projection, state: "running" };
185
+ await writeSecureJson(operationFile(this.appData, current.operationId), record);
186
+ return result;
187
+ });
188
+ }
189
+ async consumeFrontendHumanDecision(input) {
190
+ return this.serialize(input.operationId, async () => {
191
+ const current = await this.get(input.operationId);
192
+ if (!current || current.revision === undefined || !current.frontendHumanDecision) {
193
+ const fallback = buildFrontendHumanDecisionProjection({ status: "active", operationId: input.operationId, runId: input.receipt.runId, revision: Math.max(1, input.expectedRecordRevision) });
194
+ return { outcome: "rejected", reason: "record has no mutable human-decision projection", projection: fallback };
195
+ }
196
+ const projection = parseFrontendHumanDecisionProjection(current.frontendHumanDecision);
197
+ if (current.revision !== input.expectedRecordRevision)
198
+ return { outcome: "rejected", reason: "record revision conflict", projection };
199
+ const result = consumeFrontendHumanDecision({ projection, receipt: input.receipt, expectedRecordRevision: input.expectedRecordRevision, expectedProjectionDigest: input.expectedProjectionDigest, currentTargetDigest: input.currentTargetDigest, now: input.now });
200
+ if (result.outcome !== "applied")
201
+ return result;
202
+ const record = { ...current, revision: result.projection.revision, frontendHumanDecision: result.projection };
203
+ await writeSecureJson(operationFile(this.appData, current.operationId), record);
204
+ return result;
205
+ });
206
+ }
207
+ async initializeFrontendHumanApprovedSplit(input) {
208
+ return this.serialize(input.operationId, async () => {
209
+ const current = await this.get(input.operationId);
210
+ const fallback = current?.frontendHumanDecision ?? buildFrontendHumanDecisionProjection({ status: "active", operationId: input.operationId, runId: input.receipt.runId, revision: Math.max(1, input.expectedRecordRevision) });
211
+ if (!current || current.revision === undefined || current.revision !== input.expectedRecordRevision || !current.frontendHumanDecision || !current.frontendExecution || current.frontendSplit)
212
+ return { outcome: "rejected", reason: "record is not an authoritative pending single-task split", projection: fallback };
213
+ const humanProjection = parseFrontendHumanDecisionProjection(current.frontendHumanDecision);
214
+ if (humanProjection.status !== "blocked-human" || humanProjection.envelope.kind !== "split-materialization-approval" || humanProjection.digest !== input.expectedProjectionDigest)
215
+ return { outcome: "rejected", reason: "pending split envelope conflict", projection: humanProjection };
216
+ const receipt = parseFrontendHumanDecisionReceipt(input.receipt);
217
+ if (receipt.kind !== "split-materialization-approval")
218
+ return { outcome: "rejected", reason: "receipt is not a split approval", projection: humanProjection };
219
+ if (JSON.stringify(current.frontendExecution) !== JSON.stringify(input.execution) || input.execution.kind !== "single-task")
220
+ return { outcome: "rejected", reason: "single-task execution conflict", projection: humanProjection };
221
+ const consumed = consumeFrontendHumanDecision({ projection: humanProjection, receipt, expectedRecordRevision: input.expectedRecordRevision, expectedProjectionDigest: input.expectedProjectionDigest, currentTargetDigest: input.currentTargetDigest, now: input.now });
222
+ if (consumed.outcome !== "applied")
223
+ return consumed;
224
+ const initialized = initializeFrontendSplitParent({
225
+ execution: input.execution,
226
+ manifest: input.manifest,
227
+ expectedOperationRevision: input.execution.operationRevision,
228
+ successorOperationRevision: consumed.projection.revision,
229
+ envelopeTargetDigest: humanProjection.envelope.targetDigest,
230
+ activeWriterOperationId: input.activeWriterOperationId,
231
+ activeProviderOperationId: input.activeProviderOperationId,
232
+ authorization: { mode: "human", envelope: humanProjection.envelope, receipt },
233
+ });
234
+ if (initialized.outcome !== "applied")
235
+ return { outcome: "rejected", reason: initialized.reason, projection: humanProjection };
236
+ const splitProjection = frontendSplitProjectionSchema.parse({ revision: consumed.projection.revision, execution: initialized.execution, manifest: input.manifest, parent: initialized.parent });
237
+ const { frontendExecution: _singleTask, ...withoutSingleTask } = current;
238
+ const record = { ...withoutSingleTask, revision: consumed.projection.revision, frontendHumanDecision: consumed.projection, frontendSplit: splitProjection, state: "running" };
239
+ await writeSecureJson(operationFile(this.appData, current.operationId), record);
240
+ return { outcome: "applied", projection: splitProjection, humanDecision: consumed.projection };
241
+ });
242
+ }
243
+ async compareAndSwapFrontendSplit(input) {
244
+ return this.serialize(input.operationId, async () => {
245
+ const current = await this.get(input.operationId);
246
+ if (!current || current.revision === undefined || current.revision !== input.expectedRevision)
247
+ return { outcome: "conflict" };
248
+ const currentProjection = current.frontendSplit;
249
+ if (currentProjection) {
250
+ if (currentProjection.execution.digest !== input.expectedExecutionDigest || currentProjection.parent.digest !== input.expectedParentDigest)
251
+ return { outcome: "conflict" };
252
+ }
253
+ else if (input.expectedExecutionDigest || input.expectedParentDigest)
254
+ return { outcome: "conflict" };
255
+ const next = currentProjection
256
+ ? assertFrontendSplitProjectionSuccessor(currentProjection, input.next)
257
+ : frontendSplitProjectionSchema.parse(input.next);
258
+ if (next.revision !== current.revision + 1 || next.parent.operationId !== input.operationId)
259
+ throw new Error("split CAS next revision or operation binding mismatch");
260
+ const state = next.parent.operationState === "success" ? "succeeded" : next.parent.operationTerminal !== undefined ? "failed" : current.state;
261
+ const record = { ...current, revision: next.revision, frontendSplit: next, state };
262
+ await writeSecureJson(operationFile(this.appData, input.operationId), record);
263
+ return { outcome: "applied", projection: next };
264
+ });
265
+ }
266
+ async loadChildCloseoutFacts(input) {
267
+ const factsDir = input.closeoutRef.ref;
268
+ const facts = await readJsonIfExists(path.join(factsDir, "frontend-closeout-facts.json"));
269
+ if (facts === undefined)
270
+ return frontendSplitChildCloseoutFactsSchema.parse({ childId: input.childId, residualRisks: [], frontendTestHandoffRef: undefined });
271
+ const parsedFacts = frontendCloseoutFactsSchema.parse(facts);
272
+ if (computeFrontendSplitDigest(parsedFacts) !== input.closeoutRef.digest)
273
+ throw new Error(`child closeout facts digest mismatch: ${input.childId}`);
274
+ const handoff = await readJsonIfExists(path.join(factsDir, "frontend-test-handoff.json"));
275
+ const frontendTestHandoffRef = handoff === undefined ? undefined : computeFrontendSplitDigest(frontendTestHandoffSchema.parse(handoff));
276
+ return frontendSplitChildCloseoutFactsSchema.parse({ childId: input.childId, residualRisks: parsedFacts.residualRisks ?? [], frontendTestHandoffRef });
277
+ }
278
+ async settleFrontendSplitAggregate(input) {
279
+ return this.serialize(input.operationId, async () => {
280
+ const record = await this.get(input.operationId);
281
+ if (!record || record.revision === undefined || record.revision !== input.expectedRevision || !record.frontendSplit)
282
+ return { outcome: "conflict" };
283
+ const authoritative = frontendSplitProjectionSchema.parse(record.frontendSplit);
284
+ if (authoritative.parent.operationId !== input.operationId
285
+ || authoritative.revision !== input.expectedRevision
286
+ || authoritative.execution.digest !== input.expectedExecutionDigest
287
+ || authoritative.parent.digest !== input.expectedParentDigest
288
+ || authoritative.manifest.digest !== input.expectedManifestDigest
289
+ || authoritative.candidateBinding?.digest !== input.expectedCandidateBindingDigest
290
+ || authoritative.integrationBinding?.digest !== input.expectedIntegrationBindingDigest)
291
+ return { outcome: "conflict" };
292
+ if (!authoritative.candidateBinding)
293
+ return { outcome: "conflict" };
294
+ const expectedAggregate = buildFrontendSplitAggregateCloseout(authoritative.parent, authoritative.manifest, authoritative.candidateBinding, authoritative.integrationBinding, input.childCloseoutFacts);
295
+ if (JSON.stringify(expectedAggregate) !== JSON.stringify(input.aggregate))
296
+ return { outcome: "conflict" };
297
+ const transaction = await input.prepareAggregate({
298
+ operationId: authoritative.parent.operationId,
299
+ aggregate: expectedAggregate,
300
+ });
301
+ try {
302
+ const committedRef = frontendCommittedFactRefSchema.parse(transaction.committedRef);
303
+ const finalized = finalizeFrontendSplitAggregate(authoritative.parent, expectedAggregate, committedRef);
304
+ const next = assertFrontendSplitProjectionSuccessor(authoritative, { ...authoritative, revision: authoritative.revision + 1, parent: finalized.parent, execution: finalized.execution, aggregateCloseout: { value: expectedAggregate, committedRef } });
305
+ const nextRecord = { ...record, revision: next.revision, frontendSplit: next, state: "succeeded" };
306
+ await transaction.commit({
307
+ parentRecordPath: operationFile(this.appData, record.operationId),
308
+ parentRecord: nextRecord,
309
+ });
310
+ return { outcome: "applied", projection: next };
311
+ }
312
+ catch (error) {
313
+ await transaction.rollback();
314
+ throw error;
315
+ }
316
+ });
317
+ }
318
+ async initializeFrontendSplit(expectedRevision, projectionValue) {
319
+ const projection = frontendSplitProjectionSchema.parse(projectionValue);
320
+ return this.serialize(projection.parent.operationId, async () => {
321
+ const current = await this.get(projection.parent.operationId);
322
+ if (!current || current.revision === undefined || current.revision !== expectedRevision || current.frontendSplit !== undefined || projection.revision !== expectedRevision + 1)
323
+ return { outcome: "conflict" };
324
+ const record = { ...current, revision: projection.revision, frontendSplit: projection };
325
+ await writeSecureJson(operationFile(this.appData, current.operationId), record);
326
+ return { outcome: "applied", projection };
327
+ });
328
+ }
109
329
  async list() {
110
330
  const files = await listJsonFiles(this.appData.operations);
111
331
  const ops = [];