@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
@@ -64,7 +64,7 @@ function clearEmptyAssistant(setMessages, assistantId) {
64
64
  export function useChatStream(params) {
65
65
  const { origin, refs, session, thread, composer, setError, setAutoScroll, setNotice, recoveryVersion = 0, watchSessionTitle, } = params;
66
66
  const { applySseBlock, setMessages, setStreaming, setStreamingAssistantId, streaming, setTurnPhase, setDeferredTurn, setDeferredTurns, } = thread;
67
- const { setInput, setPendingImages, recordHistory } = composer;
67
+ const { settleSubmission, settleSubmissionByRequest, replaceSessionDraft, } = composer;
68
68
  const showNotice = useCallback((message) => {
69
69
  if (!setNotice)
70
70
  return;
@@ -223,15 +223,22 @@ export function useChatStream(params) {
223
223
  }, [origin]);
224
224
  const postCreateTurn = useCallback(async (input) => {
225
225
  try {
226
+ // Resolve against the configured Console origin and fail closed if a
227
+ // future URL helper ever escapes that trusted same-origin boundary.
228
+ const consoleOrigin = new URL(origin, window.location.href);
229
+ const turnsUrl = new URL(operatorChatTurnsUrl(origin, input.sessionId), consoleOrigin);
230
+ if (turnsUrl.origin !== consoleOrigin.origin)
231
+ throw new Error("create-turn URL escaped the Console origin");
226
232
  // Real create-turn path remains `${origin}/api/operator/v1/chat/sessions/:id/turns`
227
233
  // (via operatorChatTurnsUrl); source contract anchors on /turns`.
228
- const res = await fetch(operatorChatTurnsUrl(origin, input.sessionId), {
234
+ const res = await fetch(turnsUrl, {
229
235
  method: "POST",
230
236
  headers: {
231
237
  "Content-Type": "application/json",
232
238
  "x-loop-console-confirmation": confirmationToken(),
233
239
  },
234
240
  credentials: "include",
241
+ signal: input.signal,
235
242
  body: JSON.stringify({
236
243
  clientRequestId: input.clientRequestId,
237
244
  text: input.text.trim() || "请查看附件图片。",
@@ -350,9 +357,6 @@ export function useChatStream(params) {
350
357
  bag.sessionId = refs.sessionRef.current?.sessionId ?? null;
351
358
  bag.sessionGeneration = refs.sessionGenerationRef.current;
352
359
  bag.submitGeneration = refs.submitGenerationRef.current;
353
- const setPendingImagesUi = (images) => {
354
- setPendingImages(images);
355
- };
356
360
  settleCreateTurnOutcome(bag, outcome, {
357
361
  assistantId: ctx.assistantId,
358
362
  text: ctx.text,
@@ -363,13 +367,13 @@ export function useChatStream(params) {
363
367
  acceptedUi: {
364
368
  setStreaming,
365
369
  setStreamingAssistantId,
366
- setPendingImages: setPendingImagesUi,
370
+ setPendingImages: () => undefined,
367
371
  watchSessionTitle,
368
372
  },
369
373
  adoptUi: {
370
374
  clearEmptyAssistant: (assistantId) => clearEmptyAssistant(setMessages, assistantId),
371
- setInput,
372
- setPendingImages: setPendingImagesUi,
375
+ setInput: () => undefined,
376
+ setPendingImages: () => undefined,
373
377
  setStreaming,
374
378
  setStreamingAssistantId,
375
379
  removePendingMessage: (requestId) => setMessages((messages) => removePendingSubmission(messages, requestId)),
@@ -379,8 +383,8 @@ export function useChatStream(params) {
379
383
  setStreaming,
380
384
  setStreamingAssistantId,
381
385
  clearEmptyAssistant: (assistantId) => clearEmptyAssistant(setMessages, assistantId),
382
- setInput,
383
- setPendingImages: setPendingImagesUi,
386
+ setInput: () => undefined,
387
+ setPendingImages: () => undefined,
384
388
  formatError: formatOperatorUserError,
385
389
  removePendingMessage: (requestId) => setMessages((messages) => removePendingSubmission(messages, requestId)),
386
390
  },
@@ -389,6 +393,14 @@ export function useChatStream(params) {
389
393
  void postAbortForTurn(ctx.sessionId, ctx.generation, turnId);
390
394
  },
391
395
  });
396
+ if (outcome.kind === "accepted") {
397
+ settleSubmission(ctx.attempt, "accepted");
398
+ }
399
+ else if (outcome.kind === "adopt-active" ||
400
+ outcome.kind === "rejected" ||
401
+ outcome.kind === "stale") {
402
+ settleSubmission(ctx.attempt, "rejected");
403
+ }
392
404
  applyOwnershipBagToRefs(refs, bag);
393
405
  const ownershipPhase = turnPhaseFromOwnership(bag);
394
406
  setTurnPhase(visualTurnPhaseFromOwnership(bag));
@@ -403,68 +415,84 @@ export function useChatStream(params) {
403
415
  setStreaming,
404
416
  setTurnPhase,
405
417
  setStreamingAssistantId,
406
- setPendingImages,
418
+ settleSubmission,
407
419
  watchSessionTitle,
408
420
  setMessages,
409
- setInput,
410
421
  setError,
411
422
  postAbortForTurn,
412
423
  ]);
413
424
  const queueDeferredTurn = useCallback(async (input) => {
425
+ const { attempt } = input;
414
426
  const activeSession = refs.sessionRef.current;
415
- if (!activeSession || (!input.text.trim() && input.images.length === 0))
427
+ const isCurrent = () => refs.sessionRef.current?.sessionId === attempt.identity.sessionId &&
428
+ refs.sessionGenerationRef.current === attempt.sessionGeneration;
429
+ if (!activeSession ||
430
+ activeSession.sessionId !== attempt.identity.sessionId ||
431
+ !isCurrent()) {
432
+ settleSubmission(attempt, "rejected");
416
433
  return { action: "handled" };
434
+ }
435
+ const showError = (message) => {
436
+ if (isCurrent())
437
+ setError(message);
438
+ };
417
439
  const acceptDeferred = (deferredTurn, deferredTurns) => {
440
+ settleSubmission(attempt, "accepted");
441
+ if (!isCurrent())
442
+ return;
418
443
  setDeferredTurn(deferredTurns?.[0] ?? deferredTurn);
419
444
  setDeferredTurns(deferredTurns ?? [deferredTurn]);
420
- setInput("");
421
- setPendingImages([]);
422
445
  setError(null);
423
446
  };
424
447
  const acceptDispatched = () => {
448
+ settleSubmission(attempt, "accepted");
449
+ if (!isCurrent())
450
+ return;
425
451
  setDeferredTurn(null);
426
452
  setDeferredTurns([]);
427
- setInput("");
428
- setPendingImages([]);
429
453
  setError(null);
430
454
  };
431
455
  const putOnce = async () => {
432
- const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn`, {
456
+ const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(attempt.identity.sessionId)}/deferred-turn`, {
433
457
  method: "PUT",
434
458
  headers: {
435
459
  "Content-Type": "application/json",
436
460
  "x-loop-console-confirmation": confirmationToken(),
437
461
  },
438
462
  credentials: "include",
463
+ signal: attempt.signal,
439
464
  body: JSON.stringify({
440
- clientRequestId: input.clientRequestId,
465
+ clientRequestId: attempt.clientRequestId,
441
466
  blockedByTurnId: input.blockedByTurnId,
442
- text: input.text,
443
- images: input.images,
467
+ text: attempt.draftSnapshot,
468
+ images: attempt.imageSnapshot,
444
469
  }),
445
470
  });
446
471
  const body = (await response.json().catch(() => ({})));
447
472
  return { response, body };
448
473
  };
449
474
  try {
450
- for (let attempt = 0; attempt < 2; attempt += 1) {
475
+ for (let retry = 0; retry < 2; retry += 1) {
451
476
  const { response, body } = await putOnce();
452
477
  if (response.ok && body.accepted && body.deferredTurn) {
453
478
  acceptDeferred(body.deferredTurn, body.deferredTurns);
454
479
  return { action: "handled" };
455
480
  }
456
- if (body.deferredTurns)
457
- setDeferredTurns(body.deferredTurns);
458
- if (body.deferredTurn)
459
- setDeferredTurn(body.deferredTurn);
460
481
  if (body.error?.code !== "TURN_NOT_ACTIVE") {
461
- setError(body.error?.message ??
482
+ if (isCurrent()) {
483
+ if (body.deferredTurns)
484
+ setDeferredTurns(body.deferredTurns);
485
+ if (body.deferredTurn)
486
+ setDeferredTurn(body.deferredTurn);
487
+ }
488
+ settleSubmission(attempt, "rejected");
489
+ showError(body.error?.message ??
462
490
  `待发送消息未被服务端接受(HTTP ${response.status})`);
463
491
  return { action: "handled" };
464
492
  }
465
- const canonical = await fetchSessionState(activeSession.sessionId);
493
+ const canonical = await fetchSessionState(attempt.identity.sessionId);
466
494
  const decision = reconcileDeferredSubmissionFromState({
467
- clientRequestId: input.clientRequestId,
495
+ clientRequestId: attempt.clientRequestId,
468
496
  blockedByTurnId: input.blockedByTurnId,
469
497
  state: canonical ?? undefined,
470
498
  });
@@ -476,20 +504,29 @@ export function useChatStream(params) {
476
504
  acceptDispatched();
477
505
  return { action: "handled" };
478
506
  }
479
- if (decision.action === "retry-deferred" && attempt === 0)
507
+ if (decision.action === "retry-deferred" && retry === 0)
480
508
  continue;
481
- if (decision.action === "send-now") {
509
+ if (decision.action === "send-now")
482
510
  return { action: "send-now" };
511
+ if (isCurrent()) {
512
+ if (body.deferredTurns)
513
+ setDeferredTurns(body.deferredTurns);
514
+ if (body.deferredTurn)
515
+ setDeferredTurn(body.deferredTurn);
483
516
  }
484
- setError(body.error?.message ?? "停止状态已变化,当前消息仍保留在输入框");
517
+ settleSubmission(attempt, "rejected");
518
+ showError(body.error?.message ?? "停止状态已变化,当前消息仍保留在输入框");
485
519
  return { action: "handled" };
486
520
  }
521
+ settleSubmission(attempt, "rejected");
487
522
  return { action: "handled" };
488
523
  }
489
524
  catch (error) {
490
- const canonical = await fetchSessionState(activeSession.sessionId);
525
+ if (attempt.signal.aborted)
526
+ return { action: "handled" };
527
+ const canonical = await fetchSessionState(attempt.identity.sessionId);
491
528
  const decision = reconcileDeferredSubmissionFromState({
492
- clientRequestId: input.clientRequestId,
529
+ clientRequestId: attempt.clientRequestId,
493
530
  blockedByTurnId: input.blockedByTurnId,
494
531
  state: canonical ?? undefined,
495
532
  });
@@ -501,10 +538,10 @@ export function useChatStream(params) {
501
538
  acceptDispatched();
502
539
  return { action: "handled" };
503
540
  }
504
- if (decision.action === "send-now") {
541
+ if (decision.action === "send-now")
505
542
  return { action: "send-now" };
506
- }
507
- setError(error instanceof Error
543
+ settleSubmission(attempt, "rejected");
544
+ showError(error instanceof Error
508
545
  ? `无法确认消息是否已排队:${error.message}`
509
546
  : "无法确认消息是否已排队,请刷新后查看服务端状态");
510
547
  return { action: "handled" };
@@ -515,8 +552,7 @@ export function useChatStream(params) {
515
552
  thread.deferredTurn,
516
553
  setDeferredTurn,
517
554
  setDeferredTurns,
518
- setInput,
519
- setPendingImages,
555
+ settleSubmission,
520
556
  setError,
521
557
  fetchSessionState,
522
558
  ]);
@@ -529,6 +565,10 @@ export function useChatStream(params) {
529
565
  : thread.deferredTurns[0] ?? thread.deferredTurn;
530
566
  if (!activeSession || !deferred)
531
567
  return;
568
+ const identity = { ...activeSession };
569
+ const generation = refs.sessionGenerationRef.current;
570
+ const isCurrent = () => refs.sessionRef.current?.sessionId === identity.sessionId &&
571
+ refs.sessionGenerationRef.current === generation;
532
572
  try {
533
573
  const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn`, {
534
574
  method: "DELETE",
@@ -544,20 +584,25 @@ export function useChatStream(params) {
544
584
  });
545
585
  const body = (await response.json().catch(() => ({})));
546
586
  if (!response.ok || !body.cancelled) {
547
- setError(body.error?.message ?? "待发送消息撤回失败");
587
+ if (isCurrent())
588
+ setError(body.error?.message ?? "待发送消息撤回失败");
548
589
  return;
549
590
  }
591
+ if (mode === "restore") {
592
+ replaceSessionDraft(identity, deferred.text ?? body.deferredTurn?.text ?? "", deferred.images ??
593
+ body.deferredTurn?.images ??
594
+ []);
595
+ }
596
+ if (!isCurrent())
597
+ return;
550
598
  const remaining = thread.deferredTurns.filter((item) => item.clientRequestId !== deferred.clientRequestId);
551
599
  setDeferredTurns(remaining);
552
600
  setDeferredTurn(remaining[0] ?? null);
553
- if (mode === "restore") {
554
- setInput(deferred.text ?? body.deferredTurn?.text ?? "");
555
- setPendingImages(deferred.images ?? body.deferredTurn?.images ?? []);
556
- }
557
601
  setError(null);
558
602
  }
559
603
  catch (error) {
560
- setError(error instanceof Error ? error.message : String(error));
604
+ if (isCurrent())
605
+ setError(error instanceof Error ? error.message : String(error));
561
606
  }
562
607
  }, [
563
608
  origin,
@@ -566,8 +611,7 @@ export function useChatStream(params) {
566
611
  thread.deferredTurns,
567
612
  setDeferredTurn,
568
613
  setDeferredTurns,
569
- setInput,
570
- setPendingImages,
614
+ replaceSessionDraft,
571
615
  setError,
572
616
  ]);
573
617
  const cancelDeferredTurn = useCallback(() => requestDeferredTurnDelete("restore"), [requestDeferredTurnDelete]);
@@ -576,6 +620,9 @@ export function useChatStream(params) {
576
620
  const activeSession = refs.sessionRef.current;
577
621
  if (!activeSession)
578
622
  return;
623
+ const generation = refs.sessionGenerationRef.current;
624
+ const isCurrent = () => refs.sessionRef.current?.sessionId === activeSession.sessionId &&
625
+ refs.sessionGenerationRef.current === generation;
579
626
  const item = thread.deferredTurns.find((entry) => entry.clientRequestId === target.clientRequestId);
580
627
  if (!item)
581
628
  return;
@@ -587,6 +634,8 @@ export function useChatStream(params) {
587
634
  body: JSON.stringify({ clientRequestId: target.clientRequestId, blockedByTurnId: item.blockedByTurnId, text, images: item.images ?? [], expectedRevision: target.revision }),
588
635
  });
589
636
  const body = (await response.json().catch(() => ({})));
637
+ if (!isCurrent())
638
+ return;
590
639
  if (!response.ok)
591
640
  throw new Error(body.error?.message ?? "编辑排队消息失败");
592
641
  if (body.deferredTurns)
@@ -596,13 +645,17 @@ export function useChatStream(params) {
596
645
  setError(null);
597
646
  }
598
647
  catch (error) {
599
- setError(error instanceof Error ? error.message : "编辑排队消息失败");
648
+ if (isCurrent())
649
+ setError(error instanceof Error ? error.message : "编辑排队消息失败");
600
650
  }
601
651
  }, [origin, refs, thread.deferredTurns, setDeferredTurn, setDeferredTurns, setError]);
602
652
  const steerDeferredTurn = useCallback(async (target) => {
603
653
  const activeSession = refs.sessionRef.current;
604
654
  if (!activeSession)
605
655
  return;
656
+ const generation = refs.sessionGenerationRef.current;
657
+ const isCurrent = () => refs.sessionRef.current?.sessionId === activeSession.sessionId &&
658
+ refs.sessionGenerationRef.current === generation;
606
659
  try {
607
660
  const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn/${encodeURIComponent(target.clientRequestId)}/steer`, {
608
661
  method: "POST",
@@ -611,6 +664,8 @@ export function useChatStream(params) {
611
664
  body: JSON.stringify({ expectedRevision: target.revision }),
612
665
  });
613
666
  const body = (await response.json().catch(() => ({})));
667
+ if (!isCurrent())
668
+ return;
614
669
  if (!response.ok)
615
670
  throw new Error(body.error?.message ?? "插话发送失败");
616
671
  const remaining = body.deferredTurns ?? thread.deferredTurns.filter((item) => item.clientRequestId !== target.clientRequestId);
@@ -619,33 +674,39 @@ export function useChatStream(params) {
619
674
  setError(null);
620
675
  }
621
676
  catch (error) {
622
- setError(error instanceof Error ? error.message : "插话发送失败");
677
+ if (isCurrent())
678
+ setError(error instanceof Error ? error.message : "插话发送失败");
623
679
  }
624
680
  }, [origin, refs, thread.deferredTurns, setDeferredTurn, setDeferredTurns, setError]);
625
- const sendPrompt = useCallback(async (text, images) => {
681
+ const sendPrompt = useCallback(async (attempt) => {
682
+ const text = attempt.draftSnapshot;
683
+ const images = attempt.imageSnapshot;
626
684
  const initialSession = refs.sessionRef.current;
627
- if (!initialSession || (!text.trim() && images.length === 0))
685
+ if (!initialSession ||
686
+ initialSession.sessionId !== attempt.identity.sessionId ||
687
+ refs.sessionGenerationRef.current !== attempt.sessionGeneration ||
688
+ (!text.trim() && images.length === 0)) {
689
+ settleSubmission(attempt, "rejected");
628
690
  return;
691
+ }
629
692
  const ownership = ownershipBagFromRefs(refs);
630
693
  const route = decideSubmissionRoute({
631
694
  turnPhase: turnPhaseFromOwnership(ownership),
632
695
  blockedByTurnId: ownership.stoppingTurnId ?? ownership.activeTurnId,
633
696
  });
634
- const clientRequestId = requestId("chat-submit");
697
+ const clientRequestId = attempt.clientRequestId;
635
698
  if (route.action === "hold") {
636
699
  const phase = turnPhaseFromOwnership(ownership);
637
700
  if (phase === "stopping")
638
701
  showNotice("正在确认上一轮停止状态,当前消息已保留");
639
702
  else if (phase === "running")
640
703
  showNotice("正在确认当前回合状态,当前消息已保留");
704
+ settleSubmission(attempt, "rejected");
641
705
  return;
642
706
  }
643
- recordHistory?.(text);
644
707
  if (route.action === "queue-deferred") {
645
708
  const deferred = await queueDeferredTurn({
646
- text,
647
- images,
648
- clientRequestId,
709
+ attempt,
649
710
  blockedByTurnId: route.blockedByTurnId,
650
711
  });
651
712
  if (deferred.action !== "send-now")
@@ -655,19 +716,22 @@ export function useChatStream(params) {
655
716
  const generation = refs.sessionGenerationRef.current;
656
717
  if (!activeSession ||
657
718
  activeSession.sessionId !== initialSession.sessionId ||
719
+ generation !== attempt.sessionGeneration ||
658
720
  (!text.trim() && images.length === 0) ||
659
721
  refs.streamingRef.current ||
660
722
  refs.submitInFlightRef.current ||
661
723
  // AC-R5-002: ownership lease blocks send until terminal release.
662
- refs.pendingSubmissionRef.current)
724
+ refs.pendingSubmissionRef.current) {
725
+ settleSubmission(attempt, "rejected");
663
726
  return;
727
+ }
664
728
  // AC-001: acquire synchronous submit ownership before the first await.
665
729
  refs.submitInFlightRef.current = true;
666
730
  const submitGeneration = ++refs.submitGenerationRef.current;
667
731
  const userMessageId = requestId("u");
668
732
  const assistantId = requestId("a");
669
733
  const draftText = text.trim();
670
- const draftImages = images.slice();
734
+ const draftImages = [...images];
671
735
  const pending = createPendingSubmission({
672
736
  clientRequestId,
673
737
  submitGeneration,
@@ -699,7 +763,6 @@ export function useChatStream(params) {
699
763
  user: userMsg,
700
764
  assistant: assistantMsg,
701
765
  }));
702
- setInput("");
703
766
  setAutoScroll(true);
704
767
  setError(null);
705
768
  try {
@@ -713,8 +776,7 @@ export function useChatStream(params) {
713
776
  releaseSubmitOwnership(clientRequestId);
714
777
  setMessages((messages) => removePendingSubmission(messages, clientRequestId));
715
778
  clearEmptyAssistant(setMessages, assistantId);
716
- setInput(draftText);
717
- setPendingImages(draftImages);
779
+ settleSubmission(attempt, "rejected");
718
780
  return;
719
781
  }
720
782
  if (refs.sessionRef.current?.sessionId !== activeSession.sessionId ||
@@ -722,13 +784,11 @@ export function useChatStream(params) {
722
784
  releaseSubmitOwnership(clientRequestId);
723
785
  if (refs.sessionRef.current?.sessionId === activeSession.sessionId) {
724
786
  // Same session, newer generation: the activation snapshot replaced
725
- // the thread, so the optimistic row is gone. Restore the draft
726
- // instead of losing the user's text silently (2026-09-08).
787
+ // the thread, so remove its obsolete optimistic projection.
727
788
  setMessages((messages) => removePendingSubmission(messages, clientRequestId));
728
789
  clearEmptyAssistant(setMessages, assistantId);
729
- setInput(draftText);
730
- setPendingImages(draftImages);
731
790
  }
791
+ settleSubmission(attempt, "rejected");
732
792
  return;
733
793
  }
734
794
  // Attachments stay until accepted (AC/UIS-accepted).
@@ -744,6 +804,7 @@ export function useChatStream(params) {
744
804
  assistantId,
745
805
  text: draftText,
746
806
  images: draftImages,
807
+ attempt,
747
808
  pending,
748
809
  };
749
810
  const first = await postCreateTurn({
@@ -751,6 +812,7 @@ export function useChatStream(params) {
751
812
  clientRequestId,
752
813
  text: draftText,
753
814
  images: draftImages,
815
+ signal: attempt.signal,
754
816
  });
755
817
  if (!isSubmissionCurrent({
756
818
  pending,
@@ -760,6 +822,7 @@ export function useChatStream(params) {
760
822
  })) {
761
823
  pending.resolveOutcome({ kind: "stale" });
762
824
  releaseSubmitOwnership(clientRequestId);
825
+ settleSubmission(attempt, first.httpOk && first.body.accepted ? "accepted" : "rejected");
763
826
  return;
764
827
  }
765
828
  let outcome = first.networkError
@@ -786,6 +849,7 @@ export function useChatStream(params) {
786
849
  clientRequestId,
787
850
  text: draftText,
788
851
  images: draftImages,
852
+ signal: attempt.signal,
789
853
  });
790
854
  if (!isSubmissionCurrent({
791
855
  pending,
@@ -795,6 +859,7 @@ export function useChatStream(params) {
795
859
  })) {
796
860
  pending.resolveOutcome({ kind: "stale" });
797
861
  releaseSubmitOwnership(clientRequestId);
862
+ settleSubmission(attempt, retry.httpOk && retry.body.accepted ? "accepted" : "rejected");
798
863
  return;
799
864
  }
800
865
  outcome = retry.networkError
@@ -825,14 +890,13 @@ export function useChatStream(params) {
825
890
  releaseSubmitOwnership,
826
891
  showNotice,
827
892
  setMessages,
828
- setInput,
893
+ settleSubmission,
829
894
  setAutoScroll,
830
895
  setError,
831
896
  setStreaming,
832
897
  setTurnPhase,
833
898
  setStreamingAssistantId,
834
899
  queueDeferredTurn,
835
- recordHistory,
836
900
  ]);
837
901
  const handleStop = useCallback(async () => {
838
902
  // AC-R5-001: durable stopRequested is set synchronously before any await so
@@ -888,8 +952,9 @@ export function useChatStream(params) {
888
952
  // Intent remains; /state bind-active will fire exactly one abort.
889
953
  return;
890
954
  }
891
- else {
892
- // rejected / stale — clear durable intent (settle already released).
955
+ if (outcome.kind === "rejected" ||
956
+ outcome.kind === "stale") {
957
+ // Rejected/stale settlement released this submission's stop intent.
893
958
  refs.stopRequestedRef.current = false;
894
959
  return;
895
960
  }
@@ -933,6 +998,7 @@ export function useChatStream(params) {
933
998
  if (!session || (!streaming && !hasOwnershipWork))
934
999
  return;
935
1000
  const sessionId = session.sessionId;
1001
+ const sessionIdentity = { ...session };
936
1002
  const generation = refs.sessionGenerationRef.current;
937
1003
  let cancelled = false;
938
1004
  let syncInFlight = null;
@@ -975,11 +1041,15 @@ export function useChatStream(params) {
975
1041
  if (state && "deferredTurn" in state)
976
1042
  setDeferredTurn(state.deferredTurn ?? null);
977
1043
  if (result === "bound" && ownershipPhase !== "stopping") {
1044
+ settleSubmissionByRequest(sessionIdentity, pending.clientRequestId, "accepted");
978
1045
  // US-BUSY-ACTIVE-BOUND: keep streaming/busy with same id.
979
1046
  setStreaming(true);
980
1047
  return;
981
1048
  }
982
1049
  if (result === "released") {
1050
+ settleSubmissionByRequest(sessionIdentity, pending.clientRequestId, decision.action === "release-not-accepted"
1051
+ ? "rejected"
1052
+ : "accepted");
983
1053
  return;
984
1054
  }
985
1055
  // hold without acceptedTurnId: keep pending + busy.
@@ -1002,6 +1072,7 @@ export function useChatStream(params) {
1002
1072
  stillPending.clientRequestId &&
1003
1073
  isTerminalTurnState(state?.activeTurn?.state);
1004
1074
  if (latestMatches || activeMatches) {
1075
+ settleSubmissionByRequest(sessionIdentity, stillPending.clientRequestId, "accepted");
1005
1076
  const termTurnId = activeMatches
1006
1077
  ? (state?.activeTurn?.turnId ?? null)
1007
1078
  : (state?.latestTurn?.turnId ??
@@ -1073,7 +1144,10 @@ export function useChatStream(params) {
1073
1144
  stopRetryIndex >= STOP_RECONCILE_DELAYS_MS.length ||
1074
1145
  (!refs.stopRequestedRef.current && !refs.stoppingTurnIdRef.current))
1075
1146
  return;
1076
- const delay = STOP_RECONCILE_DELAYS_MS[stopRetryIndex++];
1147
+ const delay = STOP_RECONCILE_DELAYS_MS[stopRetryIndex];
1148
+ stopRetryIndex += 1;
1149
+ if (delay === undefined)
1150
+ return;
1077
1151
  stopRetryTimer = window.setTimeout(() => {
1078
1152
  void sync().finally(scheduleStopRetry);
1079
1153
  }, delay);
@@ -1095,6 +1169,7 @@ export function useChatStream(params) {
1095
1169
  refs,
1096
1170
  session,
1097
1171
  streaming,
1172
+ settleSubmissionByRequest,
1098
1173
  setTurnPhase,
1099
1174
  markIdleIfCurrent,
1100
1175
  releaseSubmitOwnership,