@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,192 @@
1
+ import { createInputHistoryState, navigateInputHistory, recordInputHistoryEntry, resetInputHistoryBrowsing, } from "./input-history.js";
2
+ export const DUPLICATE_SUBMISSION_GESTURE_MS = 350;
3
+ export function composerSessionKey(identity) {
4
+ return `${identity.workspaceId ?? ""}\u0000${identity.sessionId}`;
5
+ }
6
+ function cloneHistory(history) {
7
+ return { ...history, entries: [...history.entries] };
8
+ }
9
+ function cloneImages(images) {
10
+ return [...images];
11
+ }
12
+ function createRecord() {
13
+ return {
14
+ draft: "",
15
+ images: [],
16
+ history: createInputHistoryState(),
17
+ revision: 0,
18
+ sequence: 0,
19
+ initialized: false,
20
+ attempts: new Map(),
21
+ failedSubmissionCount: 0,
22
+ };
23
+ }
24
+ function sameImages(left, right) {
25
+ return (left.length === right.length &&
26
+ left.every((image, index) => {
27
+ const other = right[index];
28
+ return (other !== undefined &&
29
+ image.name === other.name &&
30
+ image.type === other.type &&
31
+ image.mimeType === other.mimeType &&
32
+ image.data === other.data);
33
+ }));
34
+ }
35
+ /**
36
+ * Browser-local composer state addressed by durable session identity.
37
+ *
38
+ * Network callbacks settle immutable attempts against their owning record;
39
+ * they never need to mutate whichever composer happens to be visible.
40
+ */
41
+ export class ComposerSessionLedger {
42
+ now;
43
+ records = new Map();
44
+ constructor(now = Date.now) {
45
+ this.now = now;
46
+ }
47
+ has(identity) {
48
+ return this.records.has(composerSessionKey(identity));
49
+ }
50
+ snapshot(identity) {
51
+ const record = this.record(identity);
52
+ return {
53
+ draft: record.draft,
54
+ images: cloneImages(record.images),
55
+ history: cloneHistory(record.history),
56
+ revision: record.revision,
57
+ failedSubmissionCount: record.failedSubmissionCount,
58
+ };
59
+ }
60
+ seedDraft(identity, draft) {
61
+ const record = this.record(identity);
62
+ if (record.initialized || record.revision !== 0 || record.attempts.size > 0)
63
+ return false;
64
+ record.draft = draft;
65
+ record.initialized = true;
66
+ return true;
67
+ }
68
+ setDraft(identity, draft) {
69
+ const record = this.record(identity);
70
+ if (record.draft === draft)
71
+ return false;
72
+ record.draft = draft;
73
+ record.history = resetInputHistoryBrowsing(record.history);
74
+ record.revision += 1;
75
+ record.initialized = true;
76
+ return true;
77
+ }
78
+ setImages(identity, images) {
79
+ const record = this.record(identity);
80
+ record.images = cloneImages(images);
81
+ record.revision += 1;
82
+ record.initialized = true;
83
+ return true;
84
+ }
85
+ recordHistory(identity, text) {
86
+ const record = this.record(identity);
87
+ record.history = recordInputHistoryEntry(record.history, text);
88
+ }
89
+ navigateHistory(identity, direction, context) {
90
+ const record = this.record(identity);
91
+ const decision = navigateInputHistory(record.history, direction, context);
92
+ if (!decision.handled)
93
+ return decision;
94
+ record.history = decision.state;
95
+ if (record.draft !== decision.text) {
96
+ record.draft = decision.text;
97
+ record.revision += 1;
98
+ record.initialized = true;
99
+ }
100
+ return decision;
101
+ }
102
+ beginSubmission(identity, sessionGeneration, clientRequestId) {
103
+ const record = this.record(identity);
104
+ if (!record.draft.trim() && record.images.length === 0)
105
+ return null;
106
+ const now = this.now();
107
+ const sinceLastClaim = record.lastClaim
108
+ ? now - record.lastClaim.at
109
+ : Number.POSITIVE_INFINITY;
110
+ if (record.lastClaim &&
111
+ sinceLastClaim >= 0 &&
112
+ sinceLastClaim <= DUPLICATE_SUBMISSION_GESTURE_MS &&
113
+ record.lastClaim.draft === record.draft &&
114
+ sameImages(record.lastClaim.images, record.images))
115
+ return null;
116
+ record.sequence += 1;
117
+ const sequence = record.sequence;
118
+ const controller = new AbortController();
119
+ const draftSnapshot = record.draft;
120
+ const imageSnapshot = cloneImages(record.images);
121
+ record.lastClaim = {
122
+ draft: draftSnapshot,
123
+ images: cloneImages(imageSnapshot),
124
+ at: now,
125
+ };
126
+ record.history = recordInputHistoryEntry(record.history, draftSnapshot);
127
+ record.draft = "";
128
+ record.images = [];
129
+ record.revision += 1;
130
+ record.initialized = true;
131
+ const attempt = {
132
+ attemptId: `${composerSessionKey(identity)}:${sequence}:${clientRequestId}`,
133
+ identity: { ...identity },
134
+ sessionGeneration,
135
+ sequence,
136
+ clientRequestId,
137
+ draftSnapshot,
138
+ imageSnapshot,
139
+ committedRevision: record.revision,
140
+ signal: controller.signal,
141
+ };
142
+ record.attempts.set(attempt.attemptId, { attempt, controller });
143
+ return attempt;
144
+ }
145
+ settle(attempt, outcome) {
146
+ const key = composerSessionKey(attempt.identity);
147
+ const record = this.records.get(key);
148
+ const owned = record?.attempts.get(attempt.attemptId);
149
+ if (!record || !owned || owned.attempt !== attempt)
150
+ return "stale";
151
+ record.attempts.delete(attempt.attemptId);
152
+ owned.controller.abort();
153
+ if (outcome === "accepted")
154
+ return "accepted";
155
+ if (record.revision === attempt.committedRevision &&
156
+ record.draft === "" &&
157
+ record.images.length === 0) {
158
+ record.draft = attempt.draftSnapshot;
159
+ record.images = cloneImages(attempt.imageSnapshot);
160
+ record.revision += 1;
161
+ return "restored";
162
+ }
163
+ record.failedSubmissionCount = Math.min(Number.MAX_SAFE_INTEGER, record.failedSubmissionCount + 1);
164
+ return "retained";
165
+ }
166
+ settleByRequest(identity, clientRequestId, outcome) {
167
+ const record = this.records.get(composerSessionKey(identity));
168
+ const owned = record
169
+ ? [...record.attempts.values()].find((entry) => entry.attempt.clientRequestId === clientRequestId)
170
+ : undefined;
171
+ return owned ? this.settle(owned.attempt, outcome) : "stale";
172
+ }
173
+ release(identity) {
174
+ const key = composerSessionKey(identity);
175
+ const record = this.records.get(key);
176
+ if (!record)
177
+ return;
178
+ for (const owned of record.attempts.values())
179
+ owned.controller.abort();
180
+ record.attempts.clear();
181
+ this.records.delete(key);
182
+ }
183
+ record(identity) {
184
+ const key = composerSessionKey(identity);
185
+ let record = this.records.get(key);
186
+ if (!record) {
187
+ record = createRecord();
188
+ this.records.set(key, record);
189
+ }
190
+ return record;
191
+ }
192
+ }
@@ -27,7 +27,15 @@ export function replacePendingUserMessage(messages, clientRequestId, durable) {
27
27
  continue;
28
28
  replaced = true;
29
29
  const { pending: _pending, ...rest } = message;
30
- next.push({ ...rest, ...durable, clientRequestId });
30
+ next.push({
31
+ ...rest,
32
+ ...durable,
33
+ // Durable reconciliation replaces values in the established optimistic
34
+ // seat. A later server timestamp must not move the user below the empty
35
+ // active assistant cursor when turn groups sort by display time.
36
+ createdAt: Math.min(message.createdAt, durable.createdAt),
37
+ clientRequestId,
38
+ });
31
39
  }
32
40
  return replaced ? next : [...next, durable];
33
41
  }
@@ -27,8 +27,11 @@ export function buildPaletteModel(entries) {
27
27
  for (const source of SLASH_GROUP_DISPLAY_ORDER) {
28
28
  const items = [];
29
29
  for (let i = 0; i < entries.length; i += 1) {
30
- if (entries[i].source === source) {
31
- items.push({ entry: entries[i], globalIndex: i });
30
+ const entry = entries[i];
31
+ if (!entry)
32
+ continue;
33
+ if (entry.source === source) {
34
+ items.push({ entry, globalIndex: i });
32
35
  }
33
36
  }
34
37
  if (items.length === 0)
@@ -69,6 +72,8 @@ export function movePaletteIndex(currentIndex, model, columns, direction) {
69
72
  if (groupPos < 0)
70
73
  return currentIndex;
71
74
  const group = model[groupPos];
75
+ if (!group)
76
+ return currentIndex;
72
77
  const offset = group.items.findIndex((card) => card.globalIndex === currentIndex);
73
78
  if (offset < 0)
74
79
  return currentIndex;
@@ -84,25 +89,30 @@ export function movePaletteIndex(currentIndex, model, columns, direction) {
84
89
  }
85
90
  if (Math.floor(targetOffset / columns) !== row)
86
91
  return currentIndex;
87
- return group.items[targetOffset].globalIndex;
92
+ const targetCard = group.items[targetOffset];
93
+ return targetCard ? targetCard.globalIndex : currentIndex;
88
94
  }
89
95
  const delta = direction === "up" ? -1 : 1;
90
96
  const targetOffset = offset + delta * columns;
91
97
  if (targetOffset >= 0 && targetOffset < group.items.length) {
92
- return group.items[targetOffset].globalIndex;
98
+ const targetCard = group.items[targetOffset];
99
+ return targetCard ? targetCard.globalIndex : currentIndex;
93
100
  }
94
101
  // Cross-group: walk to the previous/next non-empty group and target the
95
102
  // same column on its last (up) / first (down) row; stick when that column
96
103
  // does not exist in the partial edge row.
97
104
  for (let cursor = groupPos + delta; cursor >= 0 && cursor < model.length; cursor += delta) {
98
105
  const candidate = model[cursor];
106
+ if (!candidate)
107
+ continue;
99
108
  if (candidate.items.length === 0)
100
109
  continue;
101
110
  const lastRowStart = Math.floor((candidate.items.length - 1) / columns) * columns;
102
111
  const target = direction === "up" ? lastRowStart + col : col;
103
112
  if (target >= candidate.items.length)
104
113
  return currentIndex;
105
- return candidate.items[target].globalIndex;
114
+ const targetCard = candidate.items[target];
115
+ return targetCard ? targetCard.globalIndex : currentIndex;
106
116
  }
107
117
  return currentIndex;
108
118
  }
@@ -0,0 +1,29 @@
1
+ function anchorAt(group) {
2
+ return group.user?.createdAt ?? group.assistants[0]?.createdAt ?? 0;
3
+ }
4
+ function pendingAt(item) {
5
+ const at = Date.parse(item.updatedAt);
6
+ return Number.isFinite(at) ? at : Number.MAX_SAFE_INTEGER;
7
+ }
8
+ /**
9
+ * Merge pending (dispatching/queued) user steering into the message timeline
10
+ * by timestamp instead of always rendering at the very end. A pending message
11
+ * whose `updatedAt` precedes an already-rendered turn keeps its real slot; a
12
+ * stable sort preserves current group order for equal anchors (turns first).
13
+ */
14
+ export function mergePendingIntoTimeline(input) {
15
+ const entries = [];
16
+ for (const group of input.turnGroups) {
17
+ entries.push({ kind: "group", group });
18
+ }
19
+ for (const item of input.pendingSteering) {
20
+ if (item.state !== "dispatching")
21
+ continue;
22
+ entries.push({ kind: "pending", item });
23
+ }
24
+ return entries.sort((a, b) => {
25
+ const atA = a.kind === "group" ? anchorAt(a.group) : pendingAt(a.item);
26
+ const atB = b.kind === "group" ? anchorAt(b.group) : pendingAt(b.item);
27
+ return atA - atB;
28
+ });
29
+ }
@@ -180,7 +180,7 @@ function mergeCompactionMessages(messages, compactions) {
180
180
  export function useChatSessions(params) {
181
181
  const { origin, refs, setSession, thread, composer, browser, setError, setNotice, onCompactionChange, setProcessOpen, setAutoScroll, } = params;
182
182
  const { applyActivatedSession, resetForNewSession, setMessages } = thread;
183
- const { setInput, setPendingImages } = composer;
183
+ const { seedSession, releaseSession } = composer;
184
184
  const { setPreviews, setActivePreviewPath } = browser;
185
185
  const { workspaceId, blocked } = params;
186
186
  const workspaceQuery = workspaceId
@@ -198,7 +198,11 @@ export function useChatSessions(params) {
198
198
  return owner ? `?workspaceId=${encodeURIComponent(owner)}` : "";
199
199
  }, [refs]);
200
200
  const [sessionList, setSessionList] = useState([]);
201
+ const [selectedSession, setSelectedSession] = useState(null);
202
+ const selectedSessionRef = useRef(null);
203
+ selectedSessionRef.current = selectedSession;
201
204
  const sessionListRef = useRef([]);
205
+ const sessionListRequestVersionRef = useRef(0);
202
206
  const pendingDeletedIdsRef = useRef(new Set());
203
207
  const pendingCreatedRowsRef = useRef(new Map());
204
208
  const sessionMutationVersionRef = useRef(0);
@@ -240,8 +244,6 @@ export function useChatSessions(params) {
240
244
  refs.settledTurnIdsRef.current = new Set();
241
245
  refs.activeTurnIdRef.current = null;
242
246
  resetForNewSession();
243
- setInput("");
244
- setPendingImages([]);
245
247
  setPreviews([]);
246
248
  setActivePreviewPath(null);
247
249
  setProcessOpen(false);
@@ -249,8 +251,6 @@ export function useChatSessions(params) {
249
251
  }, [
250
252
  refs,
251
253
  resetForNewSession,
252
- setInput,
253
- setPendingImages,
254
254
  setPreviews,
255
255
  setActivePreviewPath,
256
256
  setProcessOpen,
@@ -259,6 +259,7 @@ export function useChatSessions(params) {
259
259
  const refreshSessionList = useCallback(async () => {
260
260
  if (blocked)
261
261
  return; // AC-R5-002: fail-closed, zero session requests
262
+ const version = ++sessionListRequestVersionRef.current;
262
263
  try {
263
264
  // The sidebar is a multi-workspace index. Keep one unscoped summary
264
265
  // collection so expanding or switching a workspace never projects the
@@ -268,6 +269,8 @@ export function useChatSessions(params) {
268
269
  credentials: "include",
269
270
  });
270
271
  const body = (await res.json().catch(() => ({})));
272
+ if (version !== sessionListRequestVersionRef.current)
273
+ return;
271
274
  if (!res.ok) {
272
275
  setError(body.error?.message ?? `会话列表加载失败(HTTP ${res.status})`);
273
276
  return;
@@ -285,6 +288,8 @@ export function useChatSessions(params) {
285
288
  }
286
289
  }
287
290
  catch (error) {
291
+ if (version !== sessionListRequestVersionRef.current)
292
+ return;
288
293
  setError(`会话列表加载失败:${error instanceof Error ? error.message : String(error)}`);
289
294
  }
290
295
  }, [origin, blocked, setError]);
@@ -296,12 +301,22 @@ export function useChatSessions(params) {
296
301
  const { next: removedList, removed, index: removedIndex } = removeSessionById(currentList, sessionId);
297
302
  if (!removed || removedIndex < 0)
298
303
  return false;
304
+ // No pre-mutation snapshot may overwrite this deletion or its rollback.
305
+ sessionListRequestVersionRef.current += 1;
299
306
  pendingDeletedIdsRef.current.add(sessionId);
300
307
  deleteRequestVersionsRef.current.set(sessionId, requestId);
301
308
  const activeSession = refs.sessionRef.current?.sessionId === sessionId
302
309
  ? refs.sessionRef.current
303
310
  : null;
304
- const activeWorkspaceId = activeSession?.workspaceId ?? workspaceId;
311
+ const selectedForDelete = selectedSessionRef.current?.sessionId === sessionId
312
+ ? selectedSessionRef.current
313
+ : null;
314
+ const deletingIdentity = selectedForDelete ??
315
+ activeSession ?? {
316
+ sessionId,
317
+ ...(removed.workspaceId ? { workspaceId: removed.workspaceId } : {}),
318
+ };
319
+ const activeWorkspaceId = deletingIdentity.workspaceId ?? workspaceId;
305
320
  const deletingRow = {
306
321
  ...removed,
307
322
  optimisticState: "deleting",
@@ -320,16 +335,17 @@ export function useChatSessions(params) {
320
335
  const restore = () => {
321
336
  if (deleteRequestVersionsRef.current.get(sessionId) !== requestId)
322
337
  return;
338
+ sessionListRequestVersionRef.current += 1;
323
339
  pendingDeletedIdsRef.current.delete(sessionId);
324
340
  deleteRequestVersionsRef.current.delete(sessionId);
325
- const restored = restoreSessionAt(sessionListRef.current, removed, removedIndex);
341
+ const restored = restoreSessionAt(sessionListRef.current.filter((item) => item.sessionId !== sessionId), removed, removedIndex);
326
342
  if (restored === sessionListRef.current)
327
343
  return;
328
344
  sessionListRef.current = restored;
329
345
  setSessionList(restored);
330
346
  };
331
347
  let deletionSelectionVersion = null;
332
- if (activeSession) {
348
+ if (activeSession || selectedForDelete) {
333
349
  deletionSelectionVersion = ++sessionSelectionVersionRef.current;
334
350
  refs.sessionRef.current = null;
335
351
  resetActiveProjection();
@@ -337,17 +353,16 @@ export function useChatSessions(params) {
337
353
  forgetActiveChatSession(window.localStorage, activeWorkspaceId, sessionId);
338
354
  }
339
355
  const restoreActiveSession = async () => {
340
- if (activeSession &&
356
+ if ((activeSession || selectedForDelete) &&
341
357
  deletionSelectionVersion === sessionSelectionVersionRef.current &&
342
358
  !refs.sessionRef.current) {
343
- await activateSessionRef.current?.(activeSession.sessionId, activeSession.workspaceId);
359
+ await activateSessionRef.current?.(deletingIdentity.sessionId, deletingIdentity.workspaceId);
344
360
  }
345
361
  };
346
362
  try {
347
363
  // AC-002: delete follows the session's durable owner, not the
348
364
  // global active workspace.
349
- const ownerQuery = sessionOwnerQuery(sessionListRef.current.find((item) => item.sessionId === sessionId)
350
- ?.workspaceId ?? null);
365
+ const ownerQuery = sessionOwnerQuery(removed.workspaceId ?? null);
351
366
  const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}${ownerQuery}`, {
352
367
  method: "DELETE",
353
368
  credentials: "include",
@@ -362,6 +377,13 @@ export function useChatSessions(params) {
362
377
  return false;
363
378
  }
364
379
  titleWatcherRef.current?.cancel(sessionId);
380
+ const next = sessionListRef.current.filter((item) => item.sessionId !== sessionId);
381
+ sessionListRef.current = next;
382
+ setSessionList(next);
383
+ if (deletionSelectionVersion === sessionSelectionVersionRef.current &&
384
+ selectedSessionRef.current?.sessionId === sessionId)
385
+ setSelectedSession(null);
386
+ releaseSession(deletingIdentity);
365
387
  void refreshSessionList().finally(() => {
366
388
  pendingDeletedIdsRef.current.delete(sessionId);
367
389
  deleteRequestVersionsRef.current.delete(sessionId);
@@ -379,6 +401,7 @@ export function useChatSessions(params) {
379
401
  refs,
380
402
  setSession,
381
403
  resetActiveProjection,
404
+ releaseSession,
382
405
  refreshSessionList,
383
406
  setError,
384
407
  sessionOwnerQuery,
@@ -388,25 +411,45 @@ export function useChatSessions(params) {
388
411
  const active = refs.sessionRef.current;
389
412
  const listed = sessionListRef.current.find((item) => item.sessionId === sessionId);
390
413
  const scope = ownerWorkspaceId ?? listed?.workspaceId ?? active?.workspaceId ?? workspaceId;
414
+ const identity = {
415
+ sessionId,
416
+ ...(scope ? { workspaceId: scope } : {}),
417
+ };
391
418
  forgetActiveChatSession(window.localStorage, scope, sessionId);
419
+ releaseSession(identity);
392
420
  pendingDeletedIdsRef.current.add(sessionId);
393
421
  const { next } = removeSessionById(sessionListRef.current, sessionId);
394
422
  sessionListRef.current = next;
395
423
  setSessionList(next);
396
- if (active?.sessionId === sessionId) {
424
+ if (active?.sessionId === sessionId ||
425
+ selectedSessionRef.current?.sessionId === sessionId) {
397
426
  sessionSelectionVersionRef.current += 1;
398
427
  refs.sessionRef.current = null;
428
+ setSelectedSession(null);
399
429
  resetActiveProjection();
400
430
  setSession(null);
401
431
  }
402
432
  void refreshSessionList();
403
- }, [refs, resetActiveProjection, setSession, workspaceId, refreshSessionList]);
433
+ }, [refs, releaseSession, resetActiveProjection, setSession, workspaceId, refreshSessionList]);
404
434
  const activateSession = useCallback(async (sessionId, ownerWorkspaceId) => {
405
435
  const selectionVersion = ++sessionSelectionVersionRef.current;
436
+ const listedOwner = sessionListRef.current.find((item) => item.sessionId === sessionId)?.workspaceId;
437
+ const requestedOwner = ownerWorkspaceId ?? listedOwner;
438
+ const requested = {
439
+ sessionId,
440
+ ...(requestedOwner ? { workspaceId: requestedOwner } : {}),
441
+ };
442
+ // DSH-style staging: selection identity changes synchronously; detail
443
+ // hydration follows. The previous session loses send authority now.
444
+ setSelectedSession(requested);
445
+ resetActiveProjection({ keepStreams: true });
446
+ refs.sessionRef.current = null;
447
+ setSession(null);
448
+ setError(null);
406
449
  // AC-002: activation targets the session's durable owning workspace —
407
450
  // never the global active workspace (no cross-workspace leak when
408
451
  // Operate/Inspect switch their scope while a session stays open).
409
- const ownerQuery = sessionOwnerQuery(ownerWorkspaceId);
452
+ const ownerQuery = sessionOwnerQuery(requested.workspaceId);
410
453
  try {
411
454
  const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}${ownerQuery}`, { credentials: "include" });
412
455
  const body = (await res.json());
@@ -427,7 +470,7 @@ export function useChatSessions(params) {
427
470
  // AC-002: session switch (same or cross workspace) never aborts other
428
471
  // sessions' background request controllers; the single /events owner is
429
472
  // re-bound by useChatStream's session effect.
430
- resetActiveProjection({ keepStreams: true });
473
+ setSelectedSession(next);
431
474
  refs.sessionRef.current = next;
432
475
  refs.activeTurnIdRef.current = body.session.activeTurnId ?? null;
433
476
  const visuallyStopped = body.session.turnPhase === "stopping";
@@ -447,9 +490,8 @@ export function useChatSessions(params) {
447
490
  refs.assistantIdRef.current = null;
448
491
  refs.abortRef.current =
449
492
  refs.sessionAbortControllersRef.current.get(sessionId) ?? null;
450
- setPendingImages([]);
451
493
  setPreviews([]);
452
- setInput(body.session.composerDraft ?? "");
494
+ seedSession(next, body.session.composerDraft ?? "");
453
495
  const hydrated = hydrateSessionThread(body.session.messages ?? []);
454
496
  hydrated.messages = mergeCompactionMessages(hydrated.messages, body.compactions);
455
497
  applyActivatedSession({
@@ -482,9 +524,8 @@ export function useChatSessions(params) {
482
524
  origin,
483
525
  refs,
484
526
  setSession,
485
- setPendingImages,
486
527
  setPreviews,
487
- setInput,
528
+ seedSession,
488
529
  applyActivatedSession,
489
530
  resetActiveProjection,
490
531
  setActivePreviewPath,
@@ -518,9 +559,7 @@ export function useChatSessions(params) {
518
559
  }
519
560
  const watchSessionTitle = useCallback((sessionId) => titleWatcherRef.current?.startIfEligible(sessionId), []);
520
561
  useEffect(() => {
521
- titleWatcherRef.current?.reconcile(sessionList
522
- .filter((item) => item.state !== "archived" && !item.title)
523
- .map((item) => item.sessionId));
562
+ titleWatcherRef.current?.reconcile(sessionList.flatMap((item) => item.state !== "archived" && !item.title ? [item.sessionId] : []));
524
563
  }, [sessionList]);
525
564
  useEffect(() => () => titleWatcherRef.current?.dispose(), []);
526
565
  // Browser refresh/workspace-switch recovery: selection is client state keyed
@@ -537,15 +576,22 @@ export function useChatSessions(params) {
537
576
  setSession(null);
538
577
  const savedSessionId = readActiveChatSessionId(window.localStorage, workspaceId);
539
578
  if (!savedSessionId) {
579
+ setSelectedSession(null);
540
580
  // Keep the per-workspace map intact, but clear the legacy global pointer
541
581
  // so Operate/Models cannot link a session from another workspace.
542
582
  forgetActiveChatSession(window.localStorage, workspaceId);
543
583
  return;
544
584
  }
585
+ setSelectedSession({
586
+ sessionId: savedSessionId,
587
+ ...(workspaceId ? { workspaceId } : {}),
588
+ });
545
589
  void (async () => {
546
590
  try {
547
591
  const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(savedSessionId)}${workspaceQuery}`, { credentials: "include" });
548
592
  if (!res.ok) {
593
+ if (selectionVersion === sessionSelectionVersionRef.current)
594
+ setSelectedSession(null);
549
595
  forgetActiveChatSession(window.localStorage, workspaceId, savedSessionId);
550
596
  return;
551
597
  }
@@ -557,6 +603,7 @@ export function useChatSessions(params) {
557
603
  return;
558
604
  const restored = projectActiveChatSession(body.session, workspaceId);
559
605
  refs.sessionGenerationRef.current += 1;
606
+ setSelectedSession(restored);
560
607
  refs.sessionRef.current = restored;
561
608
  // Do not seed Last-Event-ID here: session messages are restored
562
609
  // from the durable store, but tool/gate/operation projections live
@@ -577,8 +624,8 @@ export function useChatSessions(params) {
577
624
  refs.settledTurnIdsRef.current = new Set();
578
625
  refs.lastEventIdRef.current = null;
579
626
  setSession(restored);
627
+ seedSession(restored, body.session.composerDraft ?? "");
580
628
  rememberActiveChatSession(window.localStorage, restored.workspaceId ?? workspaceId, restored.sessionId);
581
- setInput(body.session.composerDraft ?? "");
582
629
  const hydrated = hydrateSessionThread(body.session.messages ?? []);
583
630
  hydrated.messages = mergeCompactionMessages(hydrated.messages, body.compactions);
584
631
  applyActivatedSession({
@@ -610,7 +657,7 @@ export function useChatSessions(params) {
610
657
  workspaceQuery,
611
658
  refs,
612
659
  setSession,
613
- setInput,
660
+ seedSession,
614
661
  applyActivatedSession,
615
662
  blocked,
616
663
  resetActiveProjection,
@@ -622,7 +669,7 @@ export function useChatSessions(params) {
622
669
  // either explicit (sidebar create-in-workspace) or the current valid
623
670
  // selection. No scope → fail closed client-side (the server would reject
624
671
  // with WORKSPACE_REQUIRED anyway); never a default-workspace fallback.
625
- const createSessionAndPrimeTitleWatcherForHook = useCallback((options) => {
672
+ const createSessionAndPrimeTitleWatcherForHook = useCallback(async (options) => {
626
673
  const scope = options?.workspaceId?.trim() ?? workspaceId?.trim();
627
674
  if (!scope) {
628
675
  setError("创建会话需要选择一个有效的工作区(不会回退默认工作区)");
@@ -644,25 +691,27 @@ export function useChatSessions(params) {
644
691
  const optimisticList = insertOptimisticSession(sessionListRef.current, optimisticRow);
645
692
  sessionListRef.current = optimisticList;
646
693
  setSessionList(optimisticList);
647
- return createSessionAndPrimeTitleWatcher({
648
- origin,
649
- sessionListRef,
650
- setSessionList,
651
- titleWatcher: titleWatcherRef.current,
652
- refreshSessionList,
653
- options: { ...options, workspaceId: scope, optimisticSessionId },
654
- setCreatingSession,
655
- creatingRef: creatingSessionRef,
656
- }).then((created) => {
694
+ try {
695
+ const created = await createSessionAndPrimeTitleWatcher({
696
+ origin,
697
+ sessionListRef,
698
+ setSessionList,
699
+ titleWatcher: titleWatcherRef.current,
700
+ refreshSessionList,
701
+ options: { ...options, workspaceId: scope, optimisticSessionId },
702
+ setCreatingSession,
703
+ creatingRef: creatingSessionRef,
704
+ });
657
705
  pendingCreatedRowsRef.current.delete(optimisticSessionId);
658
706
  return created;
659
- }).catch((error) => {
707
+ }
708
+ catch (error) {
660
709
  pendingCreatedRowsRef.current.delete(optimisticSessionId);
661
710
  const cleaned = sessionListRef.current.filter((item) => item.sessionId !== optimisticSessionId);
662
711
  sessionListRef.current = cleaned;
663
712
  setSessionList(cleaned);
664
713
  throw error;
665
- });
714
+ }
666
715
  }, [origin, refreshSessionList, workspaceId, setError]);
667
716
  const createSession = useCallback(async (options) => {
668
717
  // Suppress concurrent create from any entry (sidebar / Landing / chip).
@@ -708,6 +757,7 @@ export function useChatSessions(params) {
708
757
  // createSession().then(sendPrompt) reads the new session even
709
758
  // though sendPrompt's closure captured the stale (null) value.
710
759
  refs.sessionRef.current = created;
760
+ setSelectedSession(created);
711
761
  rememberActiveChatSession(window.localStorage, created.workspaceId ?? options?.workspaceId ?? workspaceId, created.sessionId);
712
762
  setSession(created);
713
763
  return created;
@@ -834,6 +884,7 @@ export function useChatSessions(params) {
834
884
  const activeSessionId = refs.sessionRef.current.sessionId;
835
885
  sessionSelectionVersionRef.current += 1;
836
886
  refs.sessionRef.current = null;
887
+ setSelectedSession(null);
837
888
  resetActiveProjection();
838
889
  setSession(null);
839
890
  forgetActiveChatSession(window.localStorage, activeWorkspaceId, activeSessionId);
@@ -841,6 +892,9 @@ export function useChatSessions(params) {
841
892
  };
842
893
  return {
843
894
  sessionList,
895
+ selectedSession,
896
+ sessionSwitching: Boolean(selectedSession &&
897
+ refs.sessionRef.current?.sessionId !== selectedSession.sessionId),
844
898
  creatingSession,
845
899
  activateSession,
846
900
  refreshSessionList,