@tea-agent/loop-agent 0.43.0-next.9 → 0.43.0

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 +31 -2309
  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
@@ -1,13 +1,13 @@
1
1
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
2
2
  import { buildTurnProcessGroups, hydrateSessionThread, selectRecentTurnGroups, } from "../../chat/turn-process.js";
3
3
  import { isToolFailure } from "../../chat/tool-preview.js";
4
+ import { compareChatOrder } from "../../chat/turn-order.js";
4
5
  import { mergeOperationCardState, } from "../../chat/operation-card.js";
5
6
  import { createChatSseEventApplier, } from "./chat-sse-events.js";
6
7
  import { mergePendingSubmissionOnHydration } from "./pending-user-message.js";
7
8
  import { taskContextUnchanged } from "./format.js";
8
9
  import { upsertCompactionMessage } from "./compaction-message.js";
9
10
  import { sameTurnGroupInputs } from "./turn-group-equality.js";
10
- import { selectActiveTurnTail } from "./active-turn-tail.js";
11
11
  const EMPTY_USAGE = {
12
12
  inputTokens: 0,
13
13
  outputTokens: 0,
@@ -131,7 +131,7 @@ export function useChatThread(params) {
131
131
  const [historyHasMore, setHistoryHasMore] = useState(false);
132
132
  const [historyNextCursor, setHistoryNextCursor] = useState(null);
133
133
  const [historyLoading, setHistoryLoading] = useState(false);
134
- const historyLoadingRef = useRef(false);
134
+ const historyLoadingRef = useRef(null);
135
135
  const [unread, setUnread] = useState(0);
136
136
  const [humanGateCards, setHumanGateCards] = useState([]);
137
137
  const [interview, setInterview] = useState(null);
@@ -151,7 +151,6 @@ export function useChatThread(params) {
151
151
  const [streamingAssistantId, setStreamingAssistantId] = useState(null);
152
152
  const [showSystemInMessages] = useState(false);
153
153
  const completedGroupsRef = useRef([]);
154
- const previousTimelineRef = useRef(null);
155
154
  const refreshSessionTelemetry = useCallback(async () => {
156
155
  const targetSessionId = refs.sessionRef.current?.sessionId;
157
156
  if (!targetSessionId)
@@ -318,39 +317,17 @@ export function useChatThread(params) {
318
317
  };
319
318
  }, [toolCalls]);
320
319
  const turnGroups = useMemo(() => {
321
- const previousTimeline = previousTimelineRef.current;
322
- let built;
323
- const activeIndex = streamingAssistantId
324
- ? timelineMessages.findIndex((message) => message.id === streamingAssistantId)
325
- : -1;
326
- const canBuildActiveTail = Boolean(previousTimeline && streaming && streamingAssistantId && activeIndex >= 0) &&
327
- previousTimeline?.activeAssistantId === streamingAssistantId &&
328
- (previousTimeline?.messages.every((message, index) => message.id === streamingAssistantId || timelineMessages[index]?.id === message.id) ?? false);
329
- if (canBuildActiveTail && previousTimeline) {
330
- const tailMessages = selectActiveTurnTail(timelineMessages, activeIndex);
331
- const tailIds = new Set(tailMessages.map((message) => message.id));
332
- const tailTurnIds = new Set(tailMessages.map((message) => message.turnId).filter(Boolean));
333
- const tailTools = toolCalls.filter((tool) => tailIds.has(tool.assistantMessageId ?? "") ||
334
- (Boolean(tool.turnId) && tailTurnIds.has(tool.turnId)) ||
335
- (tool.createdAt >= (tailMessages[0]?.createdAt ?? Number.POSITIVE_INFINITY)));
336
- const activeTail = buildTurnProcessGroups({
337
- messages: tailMessages,
338
- toolCalls: tailTools,
339
- streaming,
340
- activeAssistantId: streamingAssistantId,
341
- });
342
- built = [...previousTimeline.groups.filter((group) => !group.live), ...activeTail];
343
- }
344
- else {
345
- built = buildTurnProcessGroups({
346
- messages: timelineMessages,
347
- toolCalls,
348
- streaming,
349
- activeAssistantId: streamingAssistantId,
350
- });
351
- }
352
- // Preserve completed group objects across streaming tokens. The active
353
- // tail is rebuilt, while stable historical groups keep their references so
320
+ // Server confirmation can change ordering without changing message ids.
321
+ // Rebuild membership from canonical messages; cache only finished row
322
+ // objects below so rich historical content still avoids re-rendering.
323
+ const built = buildTurnProcessGroups({
324
+ messages: timelineMessages,
325
+ toolCalls,
326
+ streaming,
327
+ activeAssistantId: streamingAssistantId,
328
+ });
329
+ // Preserve completed group objects across streaming tokens. Stable
330
+ // historical groups keep their references so
354
331
  // memoized rows do not re-render or re-parse the full conversation.
355
332
  const previous = new Map(completedGroupsRef.current.map((group) => [group.key, group]));
356
333
  const all = built.map((group) => {
@@ -360,12 +337,6 @@ export function useChatThread(params) {
360
337
  : group;
361
338
  });
362
339
  completedGroupsRef.current = all.filter((group) => !group.live);
363
- previousTimelineRef.current = {
364
- messages: timelineMessages,
365
- toolCalls,
366
- activeAssistantId: streamingAssistantId,
367
- groups: all,
368
- };
369
340
  return selectRecentTurnGroups(all, historyLimit);
370
341
  }, [
371
342
  timelineMessages,
@@ -374,12 +345,14 @@ export function useChatThread(params) {
374
345
  streamingAssistantId,
375
346
  historyLimit,
376
347
  ]);
377
- const loadEarlierMessages = useCallback(async () => {
348
+ const loadEarlierMessages = useCallback(async (beforePrepend) => {
378
349
  const targetSessionId = refs.sessionRef.current?.sessionId;
379
350
  const beforeId = historyNextCursor;
380
351
  if (!targetSessionId || !historyHasMore || !beforeId || historyLoadingRef.current)
381
352
  return false;
382
- historyLoadingRef.current = true;
353
+ const generation = refs.sessionGenerationRef.current;
354
+ const request = Symbol();
355
+ historyLoadingRef.current = request;
383
356
  setHistoryLoading(true);
384
357
  try {
385
358
  const query = new URLSearchParams({ limit: "50", beforeId });
@@ -387,7 +360,12 @@ export function useChatThread(params) {
387
360
  if (!res.ok || refs.sessionRef.current?.sessionId !== targetSessionId)
388
361
  return false;
389
362
  const body = (await res.json());
363
+ if (refs.sessionRef.current?.sessionId !== targetSessionId ||
364
+ refs.sessionGenerationRef.current !== generation ||
365
+ historyLoadingRef.current !== request)
366
+ return false;
390
367
  const page = hydrateSessionThread(body.messages ?? []);
368
+ beforePrepend?.();
391
369
  setMessages((current) => {
392
370
  const seen = new Set();
393
371
  const merged = [...page.messages, ...current].filter((message) => {
@@ -396,11 +374,7 @@ export function useChatThread(params) {
396
374
  seen.add(message.id);
397
375
  return true;
398
376
  });
399
- return merged.sort((a, b) => {
400
- if (a.eventSeq !== undefined && b.eventSeq !== undefined && a.eventSeq !== b.eventSeq)
401
- return a.eventSeq - b.eventSeq;
402
- return a.createdAt - b.createdAt;
403
- });
377
+ return merged.sort(compareChatOrder);
404
378
  });
405
379
  setToolCalls((current) => {
406
380
  const byId = new Map(current.map((tool) => [tool.id, tool]));
@@ -413,11 +387,19 @@ export function useChatThread(params) {
413
387
  setHistoryLimit((value) => value + 50);
414
388
  return true;
415
389
  }
390
+ catch (error) {
391
+ if (refs.sessionRef.current?.sessionId === targetSessionId && refs.sessionGenerationRef.current === generation) {
392
+ setError(`加载历史消息失败:${error instanceof Error ? error.message : String(error)}`);
393
+ }
394
+ return false;
395
+ }
416
396
  finally {
417
- historyLoadingRef.current = false;
418
- setHistoryLoading(false);
397
+ if (historyLoadingRef.current === request) {
398
+ historyLoadingRef.current = null;
399
+ setHistoryLoading(false);
400
+ }
419
401
  }
420
- }, [historyHasMore, historyNextCursor, origin, refs]);
402
+ }, [historyHasMore, historyNextCursor, origin, refs, setError]);
421
403
  const visibleMessages = useMemo(() => {
422
404
  const next = [];
423
405
  for (const group of turnGroups) {
@@ -451,13 +433,15 @@ export function useChatThread(params) {
451
433
  setHistoryLimit(50);
452
434
  setHistoryHasMore(false);
453
435
  setHistoryNextCursor(null);
454
- historyLoadingRef.current = false;
436
+ historyLoadingRef.current = null;
437
+ setHistoryLoading(false);
455
438
  completedGroupsRef.current = [];
456
- previousTimelineRef.current = null;
457
439
  refs.usageResponseKeysRef.current = new Set();
458
440
  }, [refs]);
459
441
  /** Adopt a freshly activated session's hydrated thread + streaming flags. */
460
442
  const applyActivatedSession = useCallback((next) => {
443
+ historyLoadingRef.current = null;
444
+ setHistoryLoading(false);
461
445
  const visuallyStopped = next.turnPhase === "stopping";
462
446
  // A submission still owned for THIS session keeps its optimistic bubble
463
447
  // across an activation snapshot (refresh / reopen); a lease owned by
@@ -509,7 +493,6 @@ export function useChatThread(params) {
509
493
  setHistoryHasMore(next.history?.hasMore === true);
510
494
  setHistoryNextCursor(next.history?.nextCursor ?? null);
511
495
  completedGroupsRef.current = [];
512
- previousTimelineRef.current = null;
513
496
  }, [refs]);
514
497
  /** Reconcile recovery (AC-001/AC-002/AC-003): reload the canonical session
515
498
  * facts (session-store messages + event-ring tail cursor) and REPLACE the
@@ -1,31 +1,70 @@
1
- import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
1
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, } from "react";
2
2
  import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
3
- import { confirmationToken } from "./format.js";
3
+ import { ComposerSessionLedger, composerSessionKey, } from "./composer-session-ledger.js";
4
+ import { confirmationToken, requestId } from "./format.js";
4
5
  import { clampShortcutIndex } from "./slash-palette-nav.js";
5
- import { createInputHistoryState, navigateInputHistory, recordInputHistoryEntry, resetInputHistoryBrowsing, } from "./input-history.js";
6
6
  import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
7
+ function sameComposerSession(left, right) {
8
+ if (!left || !right)
9
+ return left === right;
10
+ return composerSessionKey(left) === composerSessionKey(right);
11
+ }
12
+ function readPendingImage(file) {
13
+ return new Promise((resolve, reject) => {
14
+ const reader = new FileReader();
15
+ reader.onerror = () => reject(reader.error ?? new Error("图片读取失败"));
16
+ reader.onload = () => {
17
+ const value = String(reader.result ?? "");
18
+ resolve({
19
+ name: file.name,
20
+ type: "image",
21
+ mimeType: file.type,
22
+ data: value.slice(value.indexOf(",") + 1),
23
+ });
24
+ };
25
+ reader.readAsDataURL(file);
26
+ });
27
+ }
7
28
  /** Composer state: input text, @file palette, image attachments, /shortcuts,
8
29
  * IME composition guards, auto-grow and server-side draft sync. */
9
30
  export function useComposer(params) {
10
- const { origin, sessionId, sessionMode, refs, setError, onUnrecoverableSession, } = params;
11
- const [input, setInputState] = useState("");
31
+ const { origin, sessionIdentity, sessionMode, sessionReady = true, refs, setError, onUnrecoverableSession, } = params;
32
+ const sessionId = sessionIdentity?.sessionId;
33
+ const ledgerRef = useRef(null);
34
+ if (!ledgerRef.current)
35
+ ledgerRef.current = new ComposerSessionLedger();
36
+ const ledger = ledgerRef.current;
37
+ const activeIdentityRef = useRef(sessionIdentity);
38
+ activeIdentityRef.current = sessionIdentity;
39
+ const [, forceRender] = useReducer((version) => version + 1, 0);
40
+ const activeSnapshot = sessionIdentity
41
+ ? ledger.snapshot(sessionIdentity)
42
+ : { draft: "", images: [] };
43
+ const input = activeSnapshot.draft;
44
+ const pendingImages = activeSnapshot.images;
12
45
  const [fileOptions, setFileOptions] = useState([]);
13
- const [pendingImages, setPendingImages] = useState([]);
14
46
  const [shortcutOpen, setShortcutOpen] = useState(false);
15
47
  /** Any composer text that starts with `/` should reveal the palette,
16
48
  * including draft restore. Escape can still close it without clearing. */
17
49
  const setInput = useCallback((value) => {
18
- if (typeof value === "function") {
19
- setInputState((prev) => {
20
- const next = value(prev);
21
- setShortcutOpen(next.startsWith("/"));
22
- return next;
23
- });
50
+ const identity = activeIdentityRef.current;
51
+ if (!identity)
24
52
  return;
25
- }
26
- setInputState(value);
27
- setShortcutOpen(value.startsWith("/"));
28
- }, []);
53
+ const previous = ledger.snapshot(identity).draft;
54
+ const next = typeof value === "function" ? value(previous) : value;
55
+ if (ledger.setDraft(identity, next))
56
+ forceRender();
57
+ setShortcutOpen(next.startsWith("/"));
58
+ }, [ledger]);
59
+ const setPendingImages = useCallback((value) => {
60
+ const identity = activeIdentityRef.current;
61
+ if (!identity)
62
+ return;
63
+ const previous = ledger.snapshot(identity).images;
64
+ const next = typeof value === "function" ? value(previous) : value;
65
+ ledger.setImages(identity, next);
66
+ forceRender();
67
+ }, [ledger]);
29
68
  const [shortcutIndex, setShortcutIndex] = useState(0);
30
69
  const [dynamicCommands, setDynamicCommands] = useState([]);
31
70
  const [commandCapabilities, setCommandCapabilities] = useState({
@@ -36,30 +75,29 @@ export function useComposer(params) {
36
75
  const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
37
76
  const [commandsEpoch, setCommandsEpoch] = useState(0);
38
77
  const textareaRef = useRef(null);
39
- /** Arrow-key input history; unsent drafts survive history browsing. */
40
- const historyRef = useRef(createInputHistoryState());
41
78
  /** Active session generation for /commands; bumped immediately on switch (AC-2). */
42
79
  const commandsSessionRef = useRef(sessionId);
43
80
  const commandsGenerationRef = useRef(0);
44
81
  const handleInputChange = useCallback((value) => {
45
82
  setInput(value);
46
- setShortcutOpen(value.startsWith("/"));
47
83
  setShortcutIndex(0);
48
- // Manual typing edits a recalled entry in place → back to present draft.
49
- historyRef.current = resetInputHistoryBrowsing(historyRef.current);
50
- }, []);
84
+ }, [setInput]);
51
85
  /** Recall an earlier input via ↑/↓; returns true when the key was consumed. */
52
86
  const navigateHistory = useCallback((direction) => {
87
+ const identity = activeIdentityRef.current;
88
+ if (!identity)
89
+ return false;
90
+ const current = ledger.snapshot(identity).draft;
53
91
  const textarea = textareaRef.current;
54
- const decision = navigateInputHistory(historyRef.current, direction, {
55
- text: input,
56
- caretStart: textarea?.selectionStart ?? input.length,
57
- caretEnd: textarea?.selectionEnd ?? input.length,
92
+ const decision = ledger.navigateHistory(identity, direction, {
93
+ text: current,
94
+ caretStart: textarea?.selectionStart ?? current.length,
95
+ caretEnd: textarea?.selectionEnd ?? current.length,
58
96
  });
59
97
  if (!decision.handled)
60
98
  return false;
61
- historyRef.current = decision.state;
62
- setInput(decision.text);
99
+ forceRender();
100
+ setShortcutOpen(decision.text.startsWith("/"));
63
101
  const caret = decision.text.length;
64
102
  queueMicrotask(() => {
65
103
  const el = textareaRef.current;
@@ -68,22 +106,76 @@ export function useComposer(params) {
68
106
  }
69
107
  });
70
108
  return true;
71
- }, [input, setInput]);
72
- /** Record a submitted text into history (consecutive duplicates collapse). */
109
+ }, [ledger]);
110
+ /** Record a submitted text into the selected session's history. */
73
111
  const recordHistory = useCallback((text) => {
74
- historyRef.current = recordInputHistoryEntry(historyRef.current, text);
75
- }, []);
112
+ const identity = activeIdentityRef.current;
113
+ if (identity)
114
+ ledger.recordHistory(identity, text);
115
+ }, [ledger]);
116
+ const claimSubmission = useCallback(() => {
117
+ const identity = activeIdentityRef.current;
118
+ if (!identity)
119
+ return null;
120
+ const attempt = ledger.beginSubmission(identity, refs.sessionGenerationRef.current, requestId("chat-submit"));
121
+ if (!attempt)
122
+ return null;
123
+ forceRender();
124
+ setShortcutOpen(false);
125
+ setShortcutIndex(0);
126
+ return attempt;
127
+ }, [ledger, refs]);
128
+ const claimSessionSubmission = useCallback((identity, text, images = []) => {
129
+ ledger.setDraft(identity, text);
130
+ ledger.setImages(identity, images);
131
+ const attempt = ledger.beginSubmission(identity, refs.sessionGenerationRef.current, requestId("chat-submit"));
132
+ if (sameComposerSession(activeIdentityRef.current, identity))
133
+ forceRender();
134
+ return attempt;
135
+ }, [ledger, refs]);
136
+ const settleSubmission = useCallback((attempt, outcome) => {
137
+ const result = ledger.settle(attempt, outcome);
138
+ if (sameComposerSession(activeIdentityRef.current, attempt.identity))
139
+ forceRender();
140
+ return result;
141
+ }, [ledger]);
142
+ const settleSubmissionByRequest = useCallback((identity, clientRequestId, outcome) => {
143
+ const result = ledger.settleByRequest(identity, clientRequestId, outcome);
144
+ if (sameComposerSession(activeIdentityRef.current, identity))
145
+ forceRender();
146
+ return result;
147
+ }, [ledger]);
148
+ const seedSession = useCallback((identity, draft) => {
149
+ const seeded = ledger.seedDraft(identity, draft);
150
+ if (seeded && sameComposerSession(activeIdentityRef.current, identity))
151
+ forceRender();
152
+ return seeded;
153
+ }, [ledger]);
154
+ const replaceSessionDraft = useCallback((identity, draft, images) => {
155
+ ledger.setDraft(identity, draft);
156
+ ledger.setImages(identity, images);
157
+ if (sameComposerSession(activeIdentityRef.current, identity))
158
+ forceRender();
159
+ }, [ledger]);
160
+ const releaseSession = useCallback((identity) => {
161
+ ledger.release(identity);
162
+ if (sameComposerSession(activeIdentityRef.current, identity))
163
+ forceRender();
164
+ }, [ledger]);
76
165
  // Immediate invalidation on A→B even when B palette never opens (AC-2).
77
166
  useLayoutEffect(() => {
78
167
  commandsSessionRef.current = sessionId;
79
168
  commandsGenerationRef.current += 1;
169
+ refs.isComposingRef.current = false;
170
+ setFileOptions([]);
171
+ setShortcutOpen(input.startsWith("/"));
80
172
  setDynamicCommands([]);
81
173
  setCommandCapabilities({ sddBingo: false, sessionMode });
82
174
  setDynamicCommandsError(null);
83
175
  setDynamicCommandsLoading(false);
84
176
  }, [sessionId, sessionMode]);
85
177
  const loadDynamicCommands = useCallback(async () => {
86
- if (!sessionId) {
178
+ if (!sessionId || !sessionReady) {
87
179
  setDynamicCommands([]);
88
180
  setDynamicCommandsError(null);
89
181
  setDynamicCommandsLoading(false);
@@ -147,21 +239,21 @@ export function useComposer(params) {
147
239
  setDynamicCommandsLoading(false);
148
240
  }
149
241
  }
150
- }, [origin, sessionId, sessionMode]);
242
+ }, [origin, sessionId, sessionMode, sessionReady]);
151
243
  // Lazy-load dynamic commands when slash palette opens or session changes.
152
244
  useEffect(() => {
153
- if (!sessionId)
245
+ if (!sessionId || !sessionReady)
154
246
  return;
155
247
  if (!shortcutOpen && commandsEpoch === 0)
156
248
  return;
157
249
  void loadDynamicCommands();
158
- }, [sessionId, shortcutOpen, commandsEpoch, loadDynamicCommands]);
250
+ }, [sessionId, sessionReady, shortcutOpen, commandsEpoch, loadDynamicCommands]);
159
251
  const retryDynamicCommands = useCallback(() => {
160
252
  setCommandsEpoch((n) => n + 1);
161
253
  void loadDynamicCommands();
162
254
  }, [loadDynamicCommands]);
163
255
  useEffect(() => {
164
- if (!sessionId)
256
+ if (!sessionId || !sessionIdentity || !sessionReady)
165
257
  return;
166
258
  const match = input
167
259
  .slice(0, textareaRef.current?.selectionStart ?? input.length)
@@ -170,13 +262,29 @@ export function useComposer(params) {
170
262
  setFileOptions([]);
171
263
  return;
172
264
  }
265
+ const owner = { ...sessionIdentity };
266
+ const ownerKey = composerSessionKey(owner);
267
+ const generation = refs.sessionGenerationRef.current;
268
+ const controller = new AbortController();
173
269
  const timer = window.setTimeout(() => {
174
- void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/files?q=${encodeURIComponent(match[1] ?? "")}`)
270
+ void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/files?q=${encodeURIComponent(match[1] ?? "")}`, { signal: controller.signal })
175
271
  .then((r) => (r.ok ? r.json() : null))
176
- .then((body) => setFileOptions((body?.files ?? []).map((item) => item.path)));
272
+ .then((body) => {
273
+ const active = activeIdentityRef.current;
274
+ if (controller.signal.aborted ||
275
+ !active ||
276
+ composerSessionKey(active) !== ownerKey ||
277
+ refs.sessionGenerationRef.current !== generation)
278
+ return;
279
+ setFileOptions((body?.files ?? []).map((item) => item.path));
280
+ })
281
+ .catch(() => undefined);
177
282
  }, 150);
178
- return () => window.clearTimeout(timer);
179
- }, [origin, sessionId, input]);
283
+ return () => {
284
+ window.clearTimeout(timer);
285
+ controller.abort();
286
+ };
287
+ }, [origin, sessionId, sessionIdentity, sessionReady, input, refs]);
180
288
  const insertFileReference = useCallback((file) => {
181
289
  const caret = textareaRef.current?.selectionStart ?? input.length;
182
290
  const before = input.slice(0, caret);
@@ -185,33 +293,31 @@ export function useComposer(params) {
185
293
  return;
186
294
  setInput(`${input.slice(0, start)}@${file} ${input.slice(caret)}`);
187
295
  setFileOptions([]);
188
- }, [input]);
296
+ }, [input, setInput]);
189
297
  const addImagesFromFiles = useCallback((files) => {
190
- for (const file of Array.from(files).slice(0, 4)) {
191
- if (!file.type.startsWith("image/") || file.size > 5 * 1024 * 1024) {
192
- setError("图片仅支持 PNG/JPEG/GIF/WebP,单张不超过 5 MiB");
193
- continue;
194
- }
195
- const reader = new FileReader();
196
- reader.onload = () => {
197
- const value = String(reader.result ?? "");
198
- const data = value.slice(value.indexOf(",") + 1);
199
- setPendingImages((items) => [
200
- ...items,
201
- {
202
- name: file.name,
203
- type: "image",
204
- mimeType: file.type,
205
- data,
206
- },
207
- ].slice(0, 4));
208
- };
209
- reader.readAsDataURL(file);
210
- }
211
- }, [setError]);
298
+ const owner = activeIdentityRef.current;
299
+ if (!owner)
300
+ return;
301
+ const ownerIdentity = { ...owner };
302
+ const selected = Array.from(files).slice(0, 4);
303
+ const valid = selected.filter((file) => file.type.startsWith("image/") && file.size <= 5 * 1024 * 1024);
304
+ if (valid.length !== selected.length)
305
+ setError("图片仅支持 PNG/JPEG/GIF/WebP,单张不超过 5 MiB");
306
+ void Promise.all(valid.map(readPendingImage)).then((loaded) => {
307
+ if (!ledger.has(ownerIdentity) || loaded.length === 0)
308
+ return;
309
+ const current = ledger.snapshot(ownerIdentity).images;
310
+ ledger.setImages(ownerIdentity, [...current, ...loaded].slice(0, 4));
311
+ if (sameComposerSession(activeIdentityRef.current, ownerIdentity))
312
+ forceRender();
313
+ }, (error) => {
314
+ if (sameComposerSession(activeIdentityRef.current, ownerIdentity))
315
+ setError(error instanceof Error ? error.message : "图片读取失败");
316
+ });
317
+ }, [ledger, setError]);
212
318
  const removeImage = useCallback((image) => {
213
319
  setPendingImages((items) => items.filter((item) => item !== image));
214
- }, []);
320
+ }, [setPendingImages]);
215
321
  // Chromium handles growth in CSS without forcing a layout read on every key.
216
322
  // Keep a deferred fallback for browsers that do not support field-sizing.
217
323
  useEffect(() => {
@@ -231,7 +337,7 @@ export function useComposer(params) {
231
337
  }, [input]);
232
338
  // Debounced server-side composer draft sync.
233
339
  useEffect(() => {
234
- if (!sessionId)
340
+ if (!sessionId || !sessionReady)
235
341
  return;
236
342
  const timer = window.setTimeout(() => {
237
343
  void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/draft`, {
@@ -245,7 +351,7 @@ export function useComposer(params) {
245
351
  });
246
352
  }, 400);
247
353
  return () => window.clearTimeout(timer);
248
- }, [origin, sessionId, input]);
354
+ }, [origin, sessionId, sessionReady, input]);
249
355
  const mergedCommands = useMemo(() => mergeSlashCommands(dynamicCommands, commandCapabilities), [dynamicCommands, commandCapabilities]);
250
356
  const filteredShortcuts = useMemo(() => input.startsWith("/")
251
357
  ? filterSlashCommands(mergedCommands, input)
@@ -279,17 +385,24 @@ export function useComposer(params) {
279
385
  useEffect(() => {
280
386
  attachRuntimeSnapshotController({
281
387
  origin,
282
- sessionId,
388
+ sessionId: sessionReady ? sessionId : undefined,
283
389
  refs,
284
390
  onUnrecoverableSession,
285
391
  });
286
- }, [origin, sessionId, refs, onUnrecoverableSession]);
392
+ }, [origin, sessionId, sessionReady, refs, onUnrecoverableSession]);
287
393
  return {
288
394
  input,
289
395
  setInput,
290
396
  handleInputChange,
291
397
  navigateHistory,
292
398
  recordHistory,
399
+ claimSubmission,
400
+ claimSessionSubmission,
401
+ settleSubmission,
402
+ settleSubmissionByRequest,
403
+ seedSession,
404
+ replaceSessionDraft,
405
+ releaseSession,
293
406
  fileOptions,
294
407
  pendingImages,
295
408
  setPendingImages,
@@ -149,6 +149,8 @@ export function useRepoBrowser(params) {
149
149
  return;
150
150
  const initial = rootFiles.find((entry) => /^readme(?:\.[^.]+)?$/i.test(entry.name)) ??
151
151
  rootFiles[0];
152
+ if (!initial)
153
+ return;
152
154
  autoPreviewedWorkspaceRef.current.add(workspaceKey);
153
155
  void openPreview(initial.path);
154
156
  }, [
@@ -181,10 +181,12 @@ export function composePrdDocuments(docs, options) {
181
181
  if (docs.length === 0)
182
182
  return "";
183
183
  if (docs.length === 1) {
184
- return docs[0].text;
184
+ return docs[0]?.text ?? "";
185
185
  }
186
186
  const primaryIndex = Math.min(Math.max(0, options?.primaryIndex ?? 0), docs.length - 1);
187
187
  const primary = docs[primaryIndex];
188
+ if (!primary)
189
+ return docs[0]?.text ?? "";
188
190
  const title = extractComposeTitle(primary.text, primary.fileName);
189
191
  const names = docs.map((d) => d.fileName).join(", ");
190
192
  const parts = [