@tea-agent/loop-agent 0.43.0-next.9 → 0.44.0-next.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 (287) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +108 -2308
  3. package/README.md +7 -0
  4. package/dist/application/dag/args.js +8 -0
  5. package/dist/application/task-lifecycle/advance.js +38 -7
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +2 -0
  8. package/dist/cli/program.js +3 -1
  9. package/dist/cli/update/init-surface-notifier.js +2 -0
  10. package/dist/cli/update/policy.js +2 -0
  11. package/dist/commands/client-recovery.js +20 -55
  12. package/dist/commands/cursor-prompt.js +2 -0
  13. package/dist/commands/dag-approve.js +2 -0
  14. package/dist/commands/dag-final-verification.js +2 -0
  15. package/dist/commands/dag-init-hybrid.js +2 -0
  16. package/dist/commands/dag-reconcile-tasks.js +2 -0
  17. package/dist/commands/dag-reject.js +2 -0
  18. package/dist/commands/dag-report.js +2 -0
  19. package/dist/commands/dag-rerun.js +2 -0
  20. package/dist/commands/dag-resume.js +2 -0
  21. package/dist/commands/dag-workflow-compile.js +2 -0
  22. package/dist/commands/dag-workflow-plan.js +2 -0
  23. package/dist/commands/dag-workflow-validate.js +2 -0
  24. package/dist/commands/delegate.js +2 -0
  25. package/dist/commands/examples.js +2 -0
  26. package/dist/commands/import-prd.js +2 -0
  27. package/dist/commands/init-model-catalog.js +2 -0
  28. package/dist/commands/init.js +19 -13
  29. package/dist/commands/instructions.js +2 -0
  30. package/dist/commands/knowledge.js +2 -0
  31. package/dist/commands/loop-benchmark.js +2 -0
  32. package/dist/commands/pi-prompt.js +2 -0
  33. package/dist/commands/pi-reuse-benchmark.js +2 -0
  34. package/dist/commands/promote-run.js +2 -0
  35. package/dist/commands/stats.js +1 -1
  36. package/dist/commands/study-init.js +2 -0
  37. package/dist/commands/task-advance.js +34 -0
  38. package/dist/commands/task-source-prepare.js +2 -0
  39. package/dist/commands/worktree-create.js +2 -0
  40. package/dist/commands/worktree-remove.js +2 -0
  41. package/dist/executors/dag-pi-executor.js +1650 -198
  42. package/dist/executors/pi-executor.js +85 -3
  43. package/dist/executors/pi-sdk-executor.js +18 -0
  44. package/dist/executors/shell-executor.js +93 -7
  45. package/dist/executors/shell-verification.js +3 -0
  46. package/dist/executors/shell-write-guard.js +27 -32
  47. package/dist/governance/exec-plans.js +6 -3
  48. package/dist/infrastructure/console/app-data.js +6 -0
  49. package/dist/shared/artifacts-core.js +2 -0
  50. package/dist/shared/backend-dogfood-preflight.js +47 -0
  51. package/dist/shared/dag-failure-category.js +3 -0
  52. package/dist/shared/git-progress.js +2 -0
  53. package/dist/shared/one-shot-prompt-args.js +2 -0
  54. package/dist/shared/operator/capabilities.js +180 -0
  55. package/dist/shared/package-metadata.js +6 -4
  56. package/dist/shared/pi-context-pressure/extension.js +26 -16
  57. package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
  58. package/dist/shared/pi-provider-recovery.js +182 -0
  59. package/dist/shared/pi-retry-settings.js +40 -0
  60. package/dist/shared/reference-context.js +2 -0
  61. package/dist/task/config-types.js +29 -0
  62. package/dist/task/contract/project.js +9 -0
  63. package/dist/task/contract/recover.js +6 -0
  64. package/dist/task/contract/schema.js +17 -0
  65. package/dist/task/source-prepare/artifact-meta.js +9 -2
  66. package/dist/task/source-prepare/build-draft.js +60 -0
  67. package/dist/task/source-prepare/fragment-inventory.js +4 -1
  68. package/dist/task/source-prepare/parse-intent.js +36 -11
  69. package/dist/task/task-demand-routing.js +5 -2
  70. package/dist/worker/console/chat/browser-automation.js +523 -0
  71. package/dist/worker/console/chat/browser-policy.js +6 -36
  72. package/dist/worker/console/chat/browser-routes.js +188 -56
  73. package/dist/worker/console/chat/explore-tools.js +5 -2
  74. package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
  75. package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
  76. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +184 -0
  77. package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
  78. package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
  79. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +94 -0
  80. package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
  81. package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
  82. package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
  83. package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
  84. package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
  85. package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
  86. package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
  87. package/dist/worker/console/chat/pi-runtime.js +208 -1138
  88. package/dist/worker/console/chat/routes.js +27 -7
  89. package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
  90. package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
  91. package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
  92. package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
  93. package/dist/worker/console/chat/session-mode-view.js +5 -4
  94. package/dist/worker/console/chat/session-mode.js +18 -12
  95. package/dist/worker/console/chat/session-store.js +42 -9
  96. package/dist/worker/console/chat/tool-preview.js +115 -0
  97. package/dist/worker/console/chat/tools.js +10 -0
  98. package/dist/worker/console/chat/turn-order.js +13 -0
  99. package/dist/worker/console/chat/turn-process.js +32 -30
  100. package/dist/worker/console/console-update-and-init.js +1 -19
  101. package/dist/worker/console/console-update-runtime.js +5 -20
  102. package/dist/worker/console/operator-actions.js +63 -1
  103. package/dist/worker/console/prd-intake-bridge.js +54 -1
  104. package/dist/worker/console/routes.js +8 -0
  105. package/dist/worker/console/scheduled-goal-host.js +98 -0
  106. package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
  107. package/dist/worker/console/server.js +24 -1
  108. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D_fNbWc_.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
  109. package/dist/worker/console/static/assets/{arc-_HuqLW6m.js → arc-eoQiMvuk.js} +1 -1
  110. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Djy2qY1m.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
  111. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-bB-1TSyY.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
  112. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5Hmghrz.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
  113. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
  114. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Duyl39oy.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
  115. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-x6mdnSyf.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
  116. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-WlBuFj7W.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
  117. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CTF429HX.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
  118. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dwd6r-SF.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
  119. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C-Br82D1.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
  120. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CuGp0DeE.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
  121. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CJhFX6Lh.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
  122. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DpGOa_jT.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
  123. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DXJaTynd.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
  124. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
  125. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
  126. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CRBZWjE4.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
  127. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BdXcxtlb.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
  128. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-07UKsOts.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
  129. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-D38p9__S.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
  130. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-nSiBNNjH.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
  131. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BFOTot5D.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
  132. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DypKaZ6k.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
  133. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-B6yoOQeW.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
  134. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BHYS6viX.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
  135. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D2VD5Snj.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
  136. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-E98mWxN1.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
  137. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CT1zxhqP.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
  138. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-6GcngNcy.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
  139. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCL6NvKe.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
  140. package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
  141. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +469 -0
  142. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C0627e-9.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
  143. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
  144. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DPDPv9HL.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
  145. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D3OHdWiO.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
  146. package/dist/worker/console/static/assets/{linear-DI88zD7n.js → linear-BDZ9riWi.js} +1 -1
  147. package/dist/worker/console/static/assets/{mermaid.core-p2l8IrWY.js → mermaid.core-7pKqYtpZ.js} +5 -5
  148. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-B1bAX-oS.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
  149. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DwOnWxdD.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
  150. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-xRkTXnJC.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
  151. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-_9UC5Zio.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
  152. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CgEUirWJ.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
  153. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Czbpwiyz.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
  154. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-_j7_KiqY.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
  155. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BNJa6Hyi.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
  156. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DedpKtOc.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
  157. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BBvthr7c.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
  158. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
  159. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Ds7umgEM.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
  160. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
  161. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKa-p1nF.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
  162. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-LoUNR56o.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
  163. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bqbl6ym7.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
  164. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B8dUg4lf.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
  165. package/dist/worker/console/static/index.html +2 -2
  166. package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +24 -0
  167. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
  168. package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
  169. package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
  170. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
  171. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
  172. package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
  173. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +92 -38
  174. package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
  175. package/dist/worker/console/static-src/operator-chat/useChatThread.js +38 -55
  176. package/dist/worker/console/static-src/operator-chat/useComposer.js +182 -69
  177. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
  178. package/dist/worker/console/static-src/prd-file-import.js +3 -1
  179. package/dist/worker/console/workspace-context.js +22 -0
  180. package/dist/worker/console/workspace-initialization.js +209 -0
  181. package/dist/worker/delivery/git-transaction.js +2 -0
  182. package/dist/worker/feature/fullstack-validate.js +2 -0
  183. package/dist/worker/loop-agent/loop-agent-client.js +8 -2
  184. package/dist/worker/materialize/harness-task-materializer.js +4 -1
  185. package/dist/worker/metrics/projector.js +1 -1
  186. package/dist/worker/observability/event-store.js +4 -1
  187. package/dist/worker/observability/read-model.js +4 -1
  188. package/dist/worker/observe/node-transparency.js +23 -1
  189. package/dist/worker/observe/routes.js +4 -0
  190. package/dist/worker/observe/static/operator-chrome.js +3 -1
  191. package/dist/worker/observe/static/styles.css +7 -4
  192. package/dist/worker/observe/static/views/dag-inspector.js +5 -19
  193. package/dist/worker/outcomes/declared-artifacts.js +2 -0
  194. package/dist/worker/preflight.js +3 -0
  195. package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
  196. package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
  197. package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
  198. package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
  199. package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
  200. package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
  201. package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
  202. package/dist/worker/task-spec/validate.js +4 -1
  203. package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
  204. package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
  205. package/dist/workflows/dag/dag-retry-schema.js +11 -0
  206. package/dist/workflows/dag/decision-envelope.js +2 -0
  207. package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
  208. package/dist/workflows/dag/frontend-closeout.js +3 -1
  209. package/dist/workflows/dag/frontend-committed-facts.js +461 -0
  210. package/dist/workflows/dag/frontend-durable-tools.js +15 -3
  211. package/dist/workflows/dag/frontend-implementation-contract.js +369 -12
  212. package/dist/workflows/dag/frontend-plan-canary.js +53 -0
  213. package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
  214. package/dist/workflows/dag/frontend-plan-render.js +0 -2
  215. package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
  216. package/dist/workflows/dag/frontend-recovery-run.js +57 -0
  217. package/dist/workflows/dag/frontend-repair.js +55 -76
  218. package/dist/workflows/dag/frontend-review-context.js +42 -68
  219. package/dist/workflows/dag/frontend-review-scopes.js +2 -2
  220. package/dist/workflows/dag/frontend-risk.js +92 -10
  221. package/dist/workflows/dag/frontend-session-budget.js +117 -3
  222. package/dist/workflows/dag/frontend-shape.js +11 -55
  223. package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
  224. package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
  225. package/dist/workflows/dag/frontend-verification-trace.js +33 -54
  226. package/dist/workflows/dag/frontend-writer-admission.js +3 -47
  227. package/dist/workflows/dag/frontend-writer-status.js +0 -23
  228. package/dist/workflows/dag/init-hybrid.js +47 -19
  229. package/dist/workflows/dag/interrupt-request.js +2 -0
  230. package/dist/workflows/dag/lifecycle.js +11 -2
  231. package/dist/workflows/dag/node-execution.js +50 -13
  232. package/dist/workflows/dag/reconcile-run.js +2 -0
  233. package/dist/workflows/dag/repair-artifact.js +3 -1
  234. package/dist/workflows/dag/report.js +2 -0
  235. package/dist/workflows/dag/rerun-plan.js +10 -0
  236. package/dist/workflows/dag/rerun-task.js +79 -1
  237. package/dist/workflows/dag/retry-policy.js +18 -0
  238. package/dist/workflows/dag/scheduler.js +2 -4
  239. package/dist/workflows/dag/types.js +44 -6
  240. package/dist/workflows/dag/validate.js +4 -0
  241. package/docs/README.md +1 -0
  242. package/docs/architecture/runtime-boundaries.md +3 -3
  243. package/docs/governance/README.md +1 -0
  244. package/docs/init-surface.manifest.json +1 -0
  245. package/docs/operations/README.md +2 -0
  246. package/docs/templates/README.md +1 -1
  247. package/docs/templates/agent-dag.schema.json +2 -2
  248. package/docs/templates/backend-test-dag.json +14 -10
  249. package/docs/templates/frontend-implementation-contract.schema.json +0 -7
  250. package/package.json +9 -3
  251. package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
  252. package/skills/frontend-contract/references/contract-protocol.md +3 -1
  253. package/skills/frontend-plan/SKILL.md +8 -6
  254. package/skills/frontend-plan/references/decision-contract.md +3 -3
  255. package/skills/frontend-review/SKILL.md +18 -28
  256. package/skills/frontend-review/references/review-findings.md +8 -3
  257. package/dist/commands/new-task.js +0 -5
  258. package/dist/commands/status.js +0 -57
  259. package/dist/commands/task-contract.js +0 -269
  260. package/dist/shared/prompts.js +0 -26
  261. package/dist/task/config.js +0 -2
  262. package/dist/task/contract/validate-draft.js +0 -106
  263. package/dist/task/goal.js +0 -3
  264. package/dist/task/index.js +0 -12
  265. package/dist/task/lifecycle.js +0 -1
  266. package/dist/task/operator/capabilities.js +0 -6
  267. package/dist/task/operator/envelope.js +0 -2
  268. package/dist/task/operator/index.js +0 -5
  269. package/dist/task/operator/registry.js +0 -2
  270. package/dist/task/operator/types.js +0 -1
  271. package/dist/task/paths.js +0 -1
  272. package/dist/task/source-state.js +0 -1
  273. package/dist/task/subagent-guidance.js +0 -1
  274. package/dist/worker/console/chat/sift-bridge.js +0 -1
  275. package/dist/worker/console/index.js +0 -20
  276. package/dist/worker/console/static/assets/channel-C0A6UEIa.js +0 -1
  277. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +0 -1
  278. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +0 -1
  279. package/dist/worker/console/static/assets/index-24jp3aBh.js +0 -468
  280. package/dist/worker/console/static/assets/index-CY6y6I8M.css +0 -1
  281. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +0 -1
  282. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +0 -8
  283. package/dist/worker/scheduler/index.js +0 -25
  284. package/dist/workflows/dag/facts.js +0 -4
  285. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
  286. package/dist/workflows/dag/index.js +0 -6
  287. package/dist/workflows/dynamic/index.js +0 -7
@@ -0,0 +1,461 @@
1
+ import { readTypedEventStoreFromJsonl, } from "./frontend-typed-event-store.js";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function asString(value) {
6
+ return typeof value === "string" ? value.trim() : "";
7
+ }
8
+ function asStringArray(value) {
9
+ if (!Array.isArray(value))
10
+ return [];
11
+ return value
12
+ .map((item) => asString(item))
13
+ .filter((item) => item.length > 0);
14
+ }
15
+ /** Deterministic, sorted, de-duplicated string set. Lexicographic order only;
16
+ * never locale/Date-dependent so diff output is reproducible. */
17
+ function canonicalStringSet(values) {
18
+ return [
19
+ ...new Set(values
20
+ .map((item) => item.trim())
21
+ .filter((item) => item.length > 0)),
22
+ ].sort();
23
+ }
24
+ export const PLAN_LEDGER_FACT_KINDS = [
25
+ "target-surface",
26
+ "state-registry",
27
+ "component-choice",
28
+ "state-flow",
29
+ "data-flow",
30
+ "mock-api",
31
+ "design-deviation",
32
+ "dependency",
33
+ "plan-requirement",
34
+ "plan-verification-target",
35
+ "plan-evidence-gap",
36
+ "mock-endpoint",
37
+ ];
38
+ /**
39
+ * A+B (AC-004): reverse of `planLedgerFactsFromPatch` for the incremental
40
+ * `record_*` tool flow. Reconstructs the canonical editable patch from the raw
41
+ * fields each `record_*` tool committed. `finalize_plan` calls this and then
42
+ * re-publishes the assembled patch onto a `target-surface` fact so the
43
+ * writeSet-external compile authority (`frontend-implementation-contract.ts`)
44
+ * can still restore a plain `patch` object.
45
+ */
46
+ export function assemblePlanPatchFromCommittedFacts(records, contractRequirements) {
47
+ const facts = records
48
+ .filter((record) => record.phase === "committed")
49
+ .map((record) => record.fact)
50
+ .filter((fact) => isRecord(fact) &&
51
+ fact.origin === "plan" &&
52
+ typeof fact.kind === "string" &&
53
+ PLAN_LEDGER_FACT_KINDS.includes(fact.kind));
54
+ if (facts.length === 0)
55
+ return undefined;
56
+ // Singleton policy facts: the LAST committed fact wins. The model corrects a
57
+ // rejected plan by re-recording the policy (r18). Requirements / verification
58
+ // targets / evidence gaps stay additive; component choices accumulate; and
59
+ // state-flow is merged below by item name so UX-local batches do not erase one
60
+ // another.
61
+ const lastByKind = (kind) => {
62
+ let found;
63
+ for (const fact of facts) {
64
+ if (fact.kind === kind)
65
+ found = fact;
66
+ }
67
+ return found;
68
+ };
69
+ // Patch snapshots (published by earlier finalize calls) carry no routes;
70
+ // exclude them so a snapshot cannot override the model's route selection.
71
+ const routeSurface = (() => {
72
+ let found;
73
+ for (const fact of facts) {
74
+ if (fact.kind !== "target-surface" || isRecord(fact.patch))
75
+ continue;
76
+ found = fact;
77
+ }
78
+ return found;
79
+ })();
80
+ const patch = {};
81
+ const targetSurface = routeSurface;
82
+ if (targetSurface) {
83
+ const routes = canonicalStringSet(asStringArray(targetSurface.routes));
84
+ if (routes.length > 0)
85
+ patch.targets = { routes };
86
+ }
87
+ const componentChoices = facts.filter((fact) => fact.kind === "component-choice");
88
+ const collectedChoices = componentChoices.flatMap((fact) => Array.isArray(fact.uiComponentChoices)
89
+ ? fact.uiComponentChoices.filter(isRecord)
90
+ : []);
91
+ // Later corrections supersede earlier submissions per purpose: without
92
+ // this, a re-recorded choice (fixing a wrong specReference) would appear
93
+ // twice and the reviewer would still audit the stale entry (r19).
94
+ const choicesByPurpose = new Map();
95
+ const dedupedChoices = [];
96
+ for (const choice of collectedChoices) {
97
+ const purpose = asString(choice.purpose);
98
+ if (!purpose) {
99
+ dedupedChoices.push(choice);
100
+ continue;
101
+ }
102
+ if (choicesByPurpose.has(purpose)) {
103
+ const at = dedupedChoices.findIndex((existing) => asString(existing.purpose) === purpose);
104
+ dedupedChoices[at] = choice;
105
+ }
106
+ else {
107
+ choicesByPurpose.set(purpose, choice);
108
+ dedupedChoices.push(choice);
109
+ }
110
+ }
111
+ const stylingStrategy = componentChoices
112
+ .map((fact) => asString(fact.stylingStrategy))
113
+ .find((value) => value.length > 0);
114
+ if (dedupedChoices.length > 0)
115
+ patch.uiComponentChoices = dedupedChoices;
116
+ if (stylingStrategy)
117
+ patch.stylingStrategy = stylingStrategy;
118
+ // UX-local batches can emit several state-flow facts. Aggregate by the
119
+ // stable state/interaction name so a later correction replaces only that
120
+ // item; last-wins at the whole-fact level silently discarded earlier local
121
+ // batches when Segment 2 was split (r21).
122
+ const stateFlows = facts.filter((fact) => fact.kind === "state-flow");
123
+ const stateByName = new Map();
124
+ const unnamedStates = [];
125
+ const interactionByName = new Map();
126
+ const unnamedInteractions = [];
127
+ for (const flow of stateFlows) {
128
+ for (const name of asStringArray(flow.removeUiStateNames))
129
+ stateByName.delete(name);
130
+ for (const name of asStringArray(flow.removeInteractionNames))
131
+ interactionByName.delete(name);
132
+ for (const state of Array.isArray(flow.uiStates) ? flow.uiStates : []) {
133
+ if (!isRecord(state))
134
+ continue;
135
+ const name = asString(state.name);
136
+ if (name)
137
+ stateByName.set(name, state);
138
+ else
139
+ unnamedStates.push(state);
140
+ }
141
+ for (const interaction of Array.isArray(flow.interactions) ? flow.interactions : []) {
142
+ if (!isRecord(interaction))
143
+ continue;
144
+ const name = asString(interaction.name);
145
+ if (name)
146
+ interactionByName.set(name, interaction);
147
+ else
148
+ unnamedInteractions.push(interaction);
149
+ }
150
+ }
151
+ if (stateFlows.length > 0) {
152
+ patch.uiStates = [...stateByName.values(), ...unnamedStates];
153
+ patch.interactions = [...interactionByName.values(), ...unnamedInteractions];
154
+ }
155
+ const mockApi = lastByKind("mock-api");
156
+ if (!mockApi && facts.some(f => f.kind === "mock-endpoint" && f.removed !== true))
157
+ throw Error("MOCK_POLICY_MISSING: endpoint has no policy");
158
+ if (mockApi && isRecord(mockApi.mockApi)) {
159
+ const endpoints = new Map();
160
+ for (const endpoint of Array.isArray(mockApi.mockApi.endpoints) ? mockApi.mockApi.endpoints : []) {
161
+ if (isRecord(endpoint))
162
+ endpoints.set(`${endpoint.method}:${endpoint.path}`, endpoint);
163
+ }
164
+ for (const fact of facts)
165
+ if (fact.kind === "mock-endpoint" && isRecord(fact.endpoint)) {
166
+ const key = `${fact.endpoint.method}:${fact.endpoint.path}`;
167
+ if (fact.removed === true)
168
+ endpoints.delete(key);
169
+ else
170
+ endpoints.set(key, fact.endpoint);
171
+ }
172
+ if (mockApi.mockApi.strategy === "not-needed" && endpoints.size)
173
+ throw Error("MOCK_POLICY_CONFLICT: withdraw endpoints before declaring Mock/API not needed");
174
+ patch.mockApi = { ...mockApi.mockApi, endpoints: [...endpoints.values()] };
175
+ }
176
+ const deviation = lastByKind("design-deviation");
177
+ if (deviation) {
178
+ const conflicts = canonicalStringSet(asStringArray(deviation.conflicts));
179
+ if (conflicts.length > 0)
180
+ patch.designEvidence = { conflicts };
181
+ }
182
+ const dependency = lastByKind("dependency");
183
+ const dependencyPolicy = asString(dependency?.policy);
184
+ if (dependencyPolicy)
185
+ patch.dependencyPolicy = dependencyPolicy;
186
+ // Requirements / verification targets / evidence gaps / implementation
187
+ // steps arrive as individual facts (one per record_plan_* call) so a large
188
+ // plan never exceeds a single model output budget. Aggregate them in
189
+ // ledger order.
190
+ const targetRequirements = new Map();
191
+ for (const fact of facts.filter((item) => item.kind === "plan-verification-target")) {
192
+ const entry = isRecord(fact.entry) ? fact.entry : undefined;
193
+ const targetId = asString(entry?.id);
194
+ if (!targetId)
195
+ continue;
196
+ for (const requirementId of asStringArray(entry?.requirementIds)) {
197
+ const targets = targetRequirements.get(requirementId) ?? [];
198
+ if (!targets.includes(targetId))
199
+ targets.push(targetId);
200
+ targetRequirements.set(requirementId, targets);
201
+ }
202
+ }
203
+ const requirements = facts
204
+ .filter((fact) => fact.kind === "plan-requirement")
205
+ .map((fact) => fact.entry)
206
+ .filter(isRecord)
207
+ .map((entry) => {
208
+ const requirementId = asString(entry.id);
209
+ const derivedTargets = requirementId ? targetRequirements.get(requirementId) : undefined;
210
+ if (derivedTargets && derivedTargets.length > 0) {
211
+ return { ...entry, verificationTargetIds: [...derivedTargets].sort() };
212
+ }
213
+ return entry;
214
+ })
215
+ .map((entry) => {
216
+ // The canonical contract schema requires a non-empty
217
+ // expectedOutcome, but the plan should not be forced to re-write
218
+ // full outcome prose for every requirement (the contract node
219
+ // already captured requirement semantics). Derive a deterministic
220
+ // placeholder from the requirement id when omitted.
221
+ if (isRecord(entry) && !asString(entry.expectedOutcome)) {
222
+ const id = asString(entry.id) || "requirement";
223
+ return { ...entry, expectedOutcome: `implied by contract: ${id}` };
224
+ }
225
+ return entry;
226
+ })
227
+ // Source fidelity ledger (AC-005/AC-006): the contract node declared the
228
+ // authoritative sourceFragmentIds/sourceRefs for each requirement from
229
+ // the ledger's requirement→fragment mapping. The plan inherits them by
230
+ // requirement id so the compiled canonical contract carries the same
231
+ // provenance evidence without the plan re-deriving (or fabricating) it.
232
+ .map((entry) => {
233
+ if (!contractRequirements)
234
+ return entry;
235
+ const id = asString(entry.id);
236
+ if (!id)
237
+ return entry;
238
+ const inherited = contractRequirements.get(id);
239
+ if (!inherited)
240
+ return entry;
241
+ const merged = { ...entry, ...(inherited.text ? { expectedOutcome: inherited.text } : {}), ...(inherited.execution ? { execution: inherited.execution } : {}) };
242
+ // Inherit the contract-declared provenance only when the plan did
243
+ // not already declare it (model override wins; missing inherits).
244
+ if (!Array.isArray(entry.sourceFragmentIds)) {
245
+ if (inherited.sourceFragmentIds) {
246
+ merged.sourceFragmentIds = inherited.sourceFragmentIds;
247
+ }
248
+ }
249
+ if (!Array.isArray(entry.sourceRefs)) {
250
+ if (inherited.sourceRefs) {
251
+ merged.sourceRefs = inherited.sourceRefs;
252
+ }
253
+ }
254
+ return merged;
255
+ })
256
+ .map((entry) => {
257
+ if (isRecord(entry) && !isRecord(entry.evidenceGap)) {
258
+ const requirementId = asString(entry.id);
259
+ const standaloneGap = facts
260
+ .filter((fact) => fact.kind === "plan-evidence-gap")
261
+ .map((fact) => fact.entry)
262
+ .filter(isRecord)
263
+ .filter((gap) => asString(gap.requirementId) === requirementId)
264
+ .at(-1);
265
+ if (standaloneGap && asString(standaloneGap.description)) {
266
+ return { ...entry, evidenceGap: standaloneGap };
267
+ }
268
+ }
269
+ return entry;
270
+ })
271
+ // A requirement id is a canonical identity; drop duplicates so the
272
+ // compiled patch never carries the same requirement twice (the
273
+ // record_plan_requirement tool rejects re-records, this dedupes any
274
+ // duplicate edge records at the compile authority).
275
+ .filter((entry, index, all) => all.map((other) => asString(other.id)).lastIndexOf(asString(entry.id)) === index);
276
+ if (requirements.length > 0)
277
+ patch.requirements = requirements;
278
+ const verificationTargetOrder = [];
279
+ const verificationTargetById = new Map();
280
+ for (const fact of facts.filter((item) => item.kind === "plan-verification-target")) {
281
+ const entry = fact.entry;
282
+ if (!isRecord(entry))
283
+ continue;
284
+ const id = asString(entry.id);
285
+ if (!id)
286
+ continue;
287
+ if (!verificationTargetById.has(id))
288
+ verificationTargetOrder.push(id);
289
+ verificationTargetById.set(id, entry);
290
+ }
291
+ const verificationTargets = verificationTargetOrder
292
+ .map((id) => verificationTargetById.get(id))
293
+ .filter((entry) => Boolean(entry))
294
+ .map((entry) => {
295
+ // Coverage precedes UX. A later state-flow fact explicitly naming
296
+ // this VT establishes the reverse binding without another model call.
297
+ // No path/name heuristics: unbound or ambiguous decisions stay subject
298
+ // to canonical validation. Recompute from live states after removals.
299
+ const boundStates = [...stateByName.values()]
300
+ .filter((state) => state.applicable === true &&
301
+ asStringArray(state.verificationTargetIds).includes(asString(entry.id)))
302
+ .map((state) => asString(state.name));
303
+ if (boundStates.length === 0)
304
+ return entry;
305
+ return { ...entry, uiStates: [...new Set([
306
+ ...asStringArray(entry.uiStates), ...boundStates,
307
+ ])].sort() };
308
+ });
309
+ if (verificationTargets.length > 0) {
310
+ patch.verificationTargets = verificationTargets;
311
+ }
312
+ const evidenceGaps = facts
313
+ .filter((fact) => fact.kind === "plan-evidence-gap")
314
+ .map((fact) => fact.entry)
315
+ .filter(isRecord);
316
+ if (evidenceGaps.length > 0)
317
+ patch.evidenceGaps = evidenceGaps;
318
+ return Object.keys(patch).length > 0 ? patch : undefined;
319
+ }
320
+ /** A+B (AC-001/003): read only the committed-phase origin facts from a node's
321
+ * typed event store JSONL. Staged/quarantined records never leak downstream. */
322
+ export async function readCommittedOriginFacts(runDir, nodeId, fileName) {
323
+ const pathMod = await import("node:path");
324
+ const records = await readTypedEventStoreFromJsonl(pathMod.join(runDir, nodeId, fileName));
325
+ return records.filter((record) => record.phase === "committed");
326
+ }
327
+ function latestCommittedScoutTargetSurface(records) {
328
+ return records
329
+ .filter((record) => record.phase === "committed" &&
330
+ isRecord(record.fact) &&
331
+ record.fact.origin === "scout" &&
332
+ record.fact.kind === "target-surface")
333
+ .at(-1)?.fact;
334
+ }
335
+ /**
336
+ * Scout owns discovery. A complete fact must explicitly say so, name at least
337
+ * one target candidate and leave no unresolved paths. Existing surfaces must
338
+ * include fresh runtime evidence for a named target path. Greenfield
339
+ * surfaces are the one explicit exception: when runtime-injected source
340
+ * declarations prove that every named path was specified by the task source,
341
+ * all paths may be future (not yet on disk). This keeps create-new work
342
+ * possible without allowing Scout to invent an arbitrary path under the
343
+ * writer boundary. An empty test path list remains valid.
344
+ */
345
+ export function readCompleteScoutTargetSurface(records) {
346
+ const fact = latestCommittedScoutTargetSurface(records);
347
+ if (!fact)
348
+ return {
349
+ ok: false,
350
+ reason: "frontend scout facts missing: no committed target-surface fact",
351
+ };
352
+ if (fact.completeness !== "complete")
353
+ return {
354
+ ok: false,
355
+ reason: "frontend scout target surface is not complete; resolve discovery gaps before Plan",
356
+ };
357
+ const unresolvedPaths = asStringArray(fact.unresolvedPaths);
358
+ if (unresolvedPaths.length > 0)
359
+ return {
360
+ ok: false,
361
+ reason: `frontend scout target surface has unresolved paths: ${unresolvedPaths.join(", ")}`,
362
+ };
363
+ const surface = {
364
+ entrypoint: asString(fact.entrypoint),
365
+ implementationPaths: canonicalStringSet(asStringArray(fact.implementationPaths)),
366
+ proposedPaths: canonicalStringSet(asStringArray(fact.proposedPaths)),
367
+ testPaths: canonicalStringSet(asStringArray(fact.testPaths)),
368
+ };
369
+ const namedPaths = canonicalStringSet([
370
+ ...(surface.entrypoint ? [surface.entrypoint] : []),
371
+ ...surface.implementationPaths,
372
+ ...surface.proposedPaths,
373
+ ...surface.testPaths,
374
+ ]);
375
+ if (namedPaths.length === 0)
376
+ return {
377
+ ok: false,
378
+ reason: "frontend scout target surface is incomplete: name an entrypoint, implementation path, or test path",
379
+ };
380
+ const freshPaths = new Set(Array.isArray(fact.pathEvidence)
381
+ ? fact.pathEvidence
382
+ .filter(isRecord)
383
+ .filter((item) => item.fresh === true)
384
+ .map((item) => asString(item.path))
385
+ .filter(Boolean)
386
+ : []);
387
+ const pathEvidence = Array.isArray(fact.pathEvidence)
388
+ ? fact.pathEvidence.filter(isRecord)
389
+ : [];
390
+ const hasSourceDeclarationLedger = Array.isArray(fact.sourceDeclaredPaths);
391
+ const undeclaredFuturePaths = hasSourceDeclarationLedger
392
+ ? namedPaths.filter((candidate) => {
393
+ const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
394
+ return evidence?.fresh !== true && evidence?.sourceDeclared !== true && evidence?.proposed !== true;
395
+ })
396
+ : [];
397
+ if (undeclaredFuturePaths.length > 0)
398
+ return {
399
+ ok: false,
400
+ reason: `frontend scout target surface has future paths not declared by the task source: ${undeclaredFuturePaths.join(", ")}; test path is not declared by the task source, so do not guess test/ vs tests/ or other target directories`,
401
+ };
402
+ if (!namedPaths.some((candidate) => freshPaths.has(candidate))) {
403
+ const allPathsAreProposed = surface.proposedPaths.length > 0 &&
404
+ namedPaths.every((candidate) => surface.proposedPaths.includes(candidate) || freshPaths.has(candidate));
405
+ if (allPathsAreProposed)
406
+ return { ok: true, surface };
407
+ const allPathsAreSourceDeclaredFuturePaths = hasSourceDeclarationLedger &&
408
+ namedPaths.length > 0 &&
409
+ namedPaths.every((candidate) => {
410
+ const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
411
+ return evidence?.fresh !== true && evidence?.sourceDeclared === true;
412
+ });
413
+ if (allPathsAreSourceDeclaredFuturePaths)
414
+ return { ok: true, surface };
415
+ return {
416
+ ok: false,
417
+ reason: "frontend scout target surface lacks fresh runtime evidence for a named target path; greenfield exceptions require runtime-injected source declarations for every future path",
418
+ };
419
+ }
420
+ return { ok: true, surface };
421
+ }
422
+ /** Paths with Scout ownership that Plan may read for a specific decision. */
423
+ export function scoutConfirmedReadPaths(records) {
424
+ const checked = readCompleteScoutTargetSurface(records);
425
+ if (!checked.ok)
426
+ return [];
427
+ return canonicalStringSet([
428
+ ...(checked.surface.entrypoint ? [checked.surface.entrypoint] : []),
429
+ ...checked.surface.implementationPaths,
430
+ ...checked.surface.proposedPaths,
431
+ ...checked.surface.testPaths,
432
+ ]);
433
+ }
434
+ /** A+B (AC-001/003): fail-closed committed contract/scout fact prerequisites.
435
+ * `contract` requires a committed `contract-finalized` terminal fact; `scout`
436
+ * requires a complete target-surface fact. Missing or incomplete facts block
437
+ * downstream consumption — never fall back to text parsing. */
438
+ export function checkCommittedOriginFacts(input) {
439
+ const kinds = new Set(input.records
440
+ .filter((record) => record.phase === "committed")
441
+ .map((record) => record.fact.kind)
442
+ .filter((kind) => typeof kind === "string"));
443
+ if (input.origin === "contract") {
444
+ return kinds.has("contract-finalized")
445
+ ? { ok: true }
446
+ : {
447
+ ok: false,
448
+ reason: "frontend contract facts missing: no committed contract-finalized terminal fact",
449
+ };
450
+ }
451
+ if (input.origin === "scout") {
452
+ if (!kinds.has("target-surface"))
453
+ return {
454
+ ok: false,
455
+ reason: "frontend scout facts missing: no committed target-surface fact",
456
+ };
457
+ const checked = readCompleteScoutTargetSurface(input.records);
458
+ return checked.ok ? { ok: true } : { ok: false, reason: checked.reason };
459
+ }
460
+ return { ok: true };
461
+ }
@@ -28,6 +28,9 @@ async function exclusive(key, work) {
28
28
  }
29
29
  const hash = (value) => sha256OfCanonicalJson(JSON.parse(JSON.stringify(value, (_key, v) => v instanceof Map ? [...v.entries()].sort(([a], [b]) => String(a).localeCompare(String(b))) : v)));
30
30
  const ledgerError = (code, message) => Object.assign(new Error(message), { code });
31
+ /** Singleton finalize tools: their committed fact is a run terminal and their
32
+ * params are an acknowledgement, not domain data (see replay rule below). */
33
+ const FINALIZE_TERMINAL_TOOLS = new Set(["finalize_contract", "finalize_plan", "finalize_decision"]);
31
34
  const failure = (code, error) => ({ content: [{ type: "text", text: JSON.stringify({ ok: false, code, error }) }], details: { ok: false, code, error } });
32
35
  /** A failed rename must never fall back to copying over a live ledger. */
33
36
  async function persist(file, records) {
@@ -98,8 +101,8 @@ export async function createDurableFrontendTools(input) {
98
101
  try {
99
102
  await restore();
100
103
  const paramsSha256 = hash(params);
101
- const singleton = new Set(["record_route_selection", "record_state_registry", "record_mock_api", "record_data_flow", "record_dependency", "record_design_deviation", "record_required_deliverables", "record_target_surface", "finalize_contract", "finalize_plan"]);
102
- const naturalId = params?.id ?? params?.scopeId ?? params?.entry?.id ?? params?.requirementId ?? params?.choice?.purpose ?? (tool.name === "record_openspec_selection" ? params?.path : undefined) ?? (params?.endpoint ? `${params.endpoint.method}:${params.endpoint.path}` : undefined) ?? (singleton.has(tool.name) ? "singleton" : undefined);
104
+ const singleton = new Set(["record_route_selection", "record_state_registry", "record_mock_api", "record_data_flow", "record_dependency", "record_design_deviation", "record_required_deliverables", "record_target_surface", "finalize_contract", "finalize_plan", "finalize_decision"]);
105
+ const naturalId = params?.id ?? params?.scopeId ?? params?.entry?.id ?? params?.requirementId ?? params?.choice?.purpose ?? params?.entry?.purpose ?? params?.entry?.state ?? params?.entry?.interaction ?? params?.entry?.boundary ?? (tool.name === "record_openspec_selection" ? params?.path : undefined) ?? (params?.endpoint ? `${params.endpoint.method}:${params.endpoint.path}` : undefined) ?? (singleton.has(tool.name) ? "singleton" : undefined);
103
106
  const identity = `${tool.name}:${typeof naturalId === "string" ? naturalId : "incremental"}`;
104
107
  const submissions = input.store.records.flatMap(r => r.submission?.receipt ? [r.submission] : []);
105
108
  const previousCall = submissions.find(s => s.callId === callId);
@@ -108,9 +111,18 @@ export async function createDurableFrontendTools(input) {
108
111
  const previousFact = submissions.filter(s => s.identity === identity).at(-1);
109
112
  if (tool.name !== "record_plan_group_coverage" && previousFact?.paramsSha256 === paramsSha256)
110
113
  return replay(previousFact.receipt);
114
+ // A committed finalize terminal carries no domain payload in its params
115
+ // (r15: the model acknowledged with {replace:false}, the executor's
116
+ // auto-finalize sends {}) — both are acknowledgements of the same durable
117
+ // terminal, so any later finalize call replays the receipt instead of
118
+ // demanding a params match no caller can reproduce. Other singletons
119
+ // (state registry, data flows, ...) carry domain payloads and keep the
120
+ // replace:true correction path.
121
+ if (FINALIZE_TERMINAL_TOOLS.has(tool.name) && previousFact?.receipt)
122
+ return replay(previousFact.receipt);
111
123
  if (previousFact && naturalId !== undefined && params?.replace !== true && !["record_evidence_expectation", "record_required_deliverables", "record_data_flow"].includes(tool.name))
112
124
  return failure("FACT_IDENTITY_CONFLICT", `${identity}: changing a saved fact requires explicit replacement (replace:true)`);
113
- if (input.store.records.some(r => ["contract-finalized", "finalize_plan", "approve_review", "request_review_changes", "approve_design", "request_design_changes"].includes(String(r.fact.kind))))
125
+ if (input.store.records.some(r => ["contract-finalized", "finalize_plan", "finalize_decision", "approve_review", "request_review_changes", "approve_design", "request_design_changes"].includes(String(r.fact.kind))))
114
126
  return failure("TERMINAL_CONFLICT", "A successful terminal is already durable; only identical acknowledged submissions may be replayed");
115
127
  const draft = structuredClone(input.store);
116
128
  input.setWorkingStore(draft);