@tea-agent/loop-agent 0.43.0-next.8 → 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 (291) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +41 -2312
  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 +59 -24
  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/terminal-sessions.js +65 -1
  97. package/dist/worker/console/chat/terminal-tools.js +17 -5
  98. package/dist/worker/console/chat/tool-preview.js +115 -0
  99. package/dist/worker/console/chat/tools.js +10 -0
  100. package/dist/worker/console/chat/turn-order.js +13 -0
  101. package/dist/worker/console/chat/turn-process.js +32 -30
  102. package/dist/worker/console/console-update-and-init.js +1 -19
  103. package/dist/worker/console/console-update-runtime.js +5 -20
  104. package/dist/worker/console/operator-actions.js +63 -1
  105. package/dist/worker/console/prd-intake-bridge.js +54 -1
  106. package/dist/worker/console/routes.js +8 -0
  107. package/dist/worker/console/scheduled-goal-host.js +98 -0
  108. package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
  109. package/dist/worker/console/server.js +24 -1
  110. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CwAxHSAM.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
  111. package/dist/worker/console/static/assets/{arc-Bozf-oeF.js → arc-eoQiMvuk.js} +1 -1
  112. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEypbEQv.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
  113. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-B1BQWCbE.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
  114. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B3u5-yZz.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
  115. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
  116. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DSUc2NTh.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
  117. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DZouMIvT.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
  118. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-B3bhB5hh.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
  119. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CZFP_BOz.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
  120. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DnE_wb-X.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
  121. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C2OfoiWm.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
  122. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BKUTL8mm.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
  123. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BT5oz0tg.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
  124. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-D8vk-GJi.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
  125. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CkUIm3zu.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
  126. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
  127. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
  128. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CO9QNHmR.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
  129. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-s52EPl2Z.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
  130. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BHaseJfW.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
  131. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CTVhRG_R.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
  132. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B6pYeOMR.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
  133. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CXXRzw5j.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
  134. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BOeJHiLA.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
  135. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DbEg6h_l.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
  136. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CjlEcbX6.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
  137. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BcqMPbD_.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
  138. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DWfRyHe5.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
  139. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Bz7TeIc8.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
  140. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BD-JxYjU.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
  141. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cv16vMHK.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
  142. package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
  143. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +469 -0
  144. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BlWO0HQQ.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
  145. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C4mbsn97.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
  146. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CBPvVKjw.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
  147. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DPHLp54K.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
  148. package/dist/worker/console/static/assets/{linear-CXx_9o6k.js → linear-BDZ9riWi.js} +1 -1
  149. package/dist/worker/console/static/assets/{mermaid.core-DU3IxMq9.js → mermaid.core-7pKqYtpZ.js} +5 -5
  150. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-COm4a2ud.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
  151. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cbt7OA5d.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
  152. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BiYV_4Wl.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
  153. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-lg2zhvbp.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
  154. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-cBLvtf0p.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
  155. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BoekU3Bg.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
  156. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-D_DMZYga.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
  157. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Lzmzn8lz.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
  158. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Cos3HpIe.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
  159. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-D-hFBGsx.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
  160. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
  161. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BY95sCVI.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
  162. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
  163. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-D1iCgWHS.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
  164. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D9IYy4WK.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
  165. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C9yJwO-R.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
  166. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CC2llyou.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
  167. package/dist/worker/console/static/index.html +2 -2
  168. package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +10 -0
  169. package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +24 -0
  170. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
  171. package/dist/worker/console/static-src/operator-chat/compaction-message.js +10 -17
  172. package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
  173. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
  174. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
  175. package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
  176. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +10 -0
  177. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +105 -44
  178. package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
  179. package/dist/worker/console/static-src/operator-chat/useChatThread.js +38 -57
  180. package/dist/worker/console/static-src/operator-chat/useComposer.js +195 -73
  181. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
  182. package/dist/worker/console/static-src/prd-file-import.js +3 -1
  183. package/dist/worker/console/workspace-context.js +22 -0
  184. package/dist/worker/console/workspace-initialization.js +209 -0
  185. package/dist/worker/delivery/git-transaction.js +2 -0
  186. package/dist/worker/feature/fullstack-validate.js +2 -0
  187. package/dist/worker/loop-agent/loop-agent-client.js +8 -2
  188. package/dist/worker/materialize/harness-task-materializer.js +4 -1
  189. package/dist/worker/metrics/projector.js +1 -1
  190. package/dist/worker/observability/event-store.js +4 -1
  191. package/dist/worker/observability/read-model.js +4 -1
  192. package/dist/worker/observe/node-transparency.js +23 -1
  193. package/dist/worker/observe/routes.js +4 -0
  194. package/dist/worker/observe/static/operator-chrome.js +3 -1
  195. package/dist/worker/observe/static/styles.css +7 -4
  196. package/dist/worker/observe/static/views/dag-inspector.js +5 -19
  197. package/dist/worker/outcomes/declared-artifacts.js +2 -0
  198. package/dist/worker/preflight.js +3 -0
  199. package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
  200. package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
  201. package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
  202. package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
  203. package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
  204. package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
  205. package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
  206. package/dist/worker/task-spec/validate.js +4 -1
  207. package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
  208. package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
  209. package/dist/workflows/dag/dag-retry-schema.js +11 -0
  210. package/dist/workflows/dag/decision-envelope.js +2 -0
  211. package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
  212. package/dist/workflows/dag/frontend-closeout.js +3 -1
  213. package/dist/workflows/dag/frontend-committed-facts.js +461 -0
  214. package/dist/workflows/dag/frontend-durable-tools.js +15 -3
  215. package/dist/workflows/dag/frontend-implementation-contract.js +369 -12
  216. package/dist/workflows/dag/frontend-plan-canary.js +53 -0
  217. package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
  218. package/dist/workflows/dag/frontend-plan-render.js +0 -2
  219. package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
  220. package/dist/workflows/dag/frontend-recovery-run.js +57 -0
  221. package/dist/workflows/dag/frontend-repair.js +55 -76
  222. package/dist/workflows/dag/frontend-review-context.js +42 -68
  223. package/dist/workflows/dag/frontend-review-scopes.js +2 -2
  224. package/dist/workflows/dag/frontend-risk.js +92 -10
  225. package/dist/workflows/dag/frontend-session-budget.js +117 -3
  226. package/dist/workflows/dag/frontend-shape.js +11 -55
  227. package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
  228. package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
  229. package/dist/workflows/dag/frontend-verification-trace.js +33 -54
  230. package/dist/workflows/dag/frontend-writer-admission.js +3 -47
  231. package/dist/workflows/dag/frontend-writer-status.js +0 -23
  232. package/dist/workflows/dag/init-hybrid.js +47 -19
  233. package/dist/workflows/dag/interrupt-request.js +2 -0
  234. package/dist/workflows/dag/lifecycle.js +11 -2
  235. package/dist/workflows/dag/node-execution.js +50 -13
  236. package/dist/workflows/dag/reconcile-run.js +2 -0
  237. package/dist/workflows/dag/repair-artifact.js +3 -1
  238. package/dist/workflows/dag/report.js +2 -0
  239. package/dist/workflows/dag/rerun-plan.js +10 -0
  240. package/dist/workflows/dag/rerun-task.js +79 -1
  241. package/dist/workflows/dag/retry-policy.js +18 -0
  242. package/dist/workflows/dag/scheduler.js +2 -4
  243. package/dist/workflows/dag/types.js +44 -6
  244. package/dist/workflows/dag/validate.js +4 -0
  245. package/docs/README.md +1 -0
  246. package/docs/architecture/runtime-boundaries.md +3 -3
  247. package/docs/governance/README.md +1 -0
  248. package/docs/init-surface.manifest.json +1 -0
  249. package/docs/operations/README.md +2 -0
  250. package/docs/templates/README.md +1 -1
  251. package/docs/templates/agent-dag.schema.json +2 -2
  252. package/docs/templates/backend-test-dag.json +14 -10
  253. package/docs/templates/frontend-implementation-contract.schema.json +0 -7
  254. package/package.json +9 -3
  255. package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
  256. package/skills/frontend-contract/references/contract-protocol.md +3 -1
  257. package/skills/frontend-plan/SKILL.md +8 -6
  258. package/skills/frontend-plan/references/decision-contract.md +3 -3
  259. package/skills/frontend-review/SKILL.md +18 -28
  260. package/skills/frontend-review/references/review-findings.md +8 -3
  261. package/dist/commands/new-task.js +0 -5
  262. package/dist/commands/status.js +0 -57
  263. package/dist/commands/task-contract.js +0 -269
  264. package/dist/shared/prompts.js +0 -26
  265. package/dist/task/config.js +0 -2
  266. package/dist/task/contract/validate-draft.js +0 -106
  267. package/dist/task/goal.js +0 -3
  268. package/dist/task/index.js +0 -12
  269. package/dist/task/lifecycle.js +0 -1
  270. package/dist/task/operator/capabilities.js +0 -6
  271. package/dist/task/operator/envelope.js +0 -2
  272. package/dist/task/operator/index.js +0 -5
  273. package/dist/task/operator/registry.js +0 -2
  274. package/dist/task/operator/types.js +0 -1
  275. package/dist/task/paths.js +0 -1
  276. package/dist/task/source-state.js +0 -1
  277. package/dist/task/subagent-guidance.js +0 -1
  278. package/dist/worker/console/chat/sift-bridge.js +0 -1
  279. package/dist/worker/console/index.js +0 -20
  280. package/dist/worker/console/static/assets/channel-JM-I_b0s.js +0 -1
  281. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DuQQAYEg.js +0 -1
  282. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DuQQAYEg.js +0 -1
  283. package/dist/worker/console/static/assets/index-B5H-giS2.js +0 -468
  284. package/dist/worker/console/static/assets/index-Bf7dxfm-.css +0 -1
  285. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-img0hVJ4.js +0 -1
  286. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B4acZJvk.js +0 -8
  287. package/dist/worker/scheduler/index.js +0 -25
  288. package/dist/workflows/dag/facts.js +0 -4
  289. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
  290. package/dist/workflows/dag/index.js +0 -6
  291. package/dist/workflows/dynamic/index.js +0 -7
@@ -1,6 +1,7 @@
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";
@@ -130,7 +131,7 @@ export function useChatThread(params) {
130
131
  const [historyHasMore, setHistoryHasMore] = useState(false);
131
132
  const [historyNextCursor, setHistoryNextCursor] = useState(null);
132
133
  const [historyLoading, setHistoryLoading] = useState(false);
133
- const historyLoadingRef = useRef(false);
134
+ const historyLoadingRef = useRef(null);
134
135
  const [unread, setUnread] = useState(0);
135
136
  const [humanGateCards, setHumanGateCards] = useState([]);
136
137
  const [interview, setInterview] = useState(null);
@@ -150,7 +151,6 @@ export function useChatThread(params) {
150
151
  const [streamingAssistantId, setStreamingAssistantId] = useState(null);
151
152
  const [showSystemInMessages] = useState(false);
152
153
  const completedGroupsRef = useRef([]);
153
- const previousTimelineRef = useRef(null);
154
154
  const refreshSessionTelemetry = useCallback(async () => {
155
155
  const targetSessionId = refs.sessionRef.current?.sessionId;
156
156
  if (!targetSessionId)
@@ -317,42 +317,17 @@ export function useChatThread(params) {
317
317
  };
318
318
  }, [toolCalls]);
319
319
  const turnGroups = useMemo(() => {
320
- const previousTimeline = previousTimelineRef.current;
321
- let built;
322
- const activeIndex = streamingAssistantId
323
- ? timelineMessages.findIndex((message) => message.id === streamingAssistantId)
324
- : -1;
325
- const canBuildActiveTail = Boolean(previousTimeline && streaming && streamingAssistantId && activeIndex >= 0) &&
326
- previousTimeline?.activeAssistantId === streamingAssistantId &&
327
- (previousTimeline?.messages.every((message, index) => message.id === streamingAssistantId || timelineMessages[index]?.id === message.id) ?? false);
328
- if (canBuildActiveTail && previousTimeline) {
329
- let tailStart = activeIndex;
330
- while (tailStart > 0 && timelineMessages[tailStart - 1]?.role !== "user")
331
- tailStart -= 1;
332
- const tailMessages = timelineMessages.slice(tailStart);
333
- const tailIds = new Set(tailMessages.map((message) => message.id));
334
- const tailTurnIds = new Set(tailMessages.map((message) => message.turnId).filter(Boolean));
335
- const tailTools = toolCalls.filter((tool) => tailIds.has(tool.assistantMessageId ?? "") ||
336
- (Boolean(tool.turnId) && tailTurnIds.has(tool.turnId)) ||
337
- (tool.createdAt >= (tailMessages[0]?.createdAt ?? Number.POSITIVE_INFINITY)));
338
- const activeTail = buildTurnProcessGroups({
339
- messages: tailMessages,
340
- toolCalls: tailTools,
341
- streaming,
342
- activeAssistantId: streamingAssistantId,
343
- });
344
- built = [...previousTimeline.groups.filter((group) => !group.live), ...activeTail];
345
- }
346
- else {
347
- built = buildTurnProcessGroups({
348
- messages: timelineMessages,
349
- toolCalls,
350
- streaming,
351
- activeAssistantId: streamingAssistantId,
352
- });
353
- }
354
- // Preserve completed group objects across streaming tokens. The active
355
- // 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
356
331
  // memoized rows do not re-render or re-parse the full conversation.
357
332
  const previous = new Map(completedGroupsRef.current.map((group) => [group.key, group]));
358
333
  const all = built.map((group) => {
@@ -362,12 +337,6 @@ export function useChatThread(params) {
362
337
  : group;
363
338
  });
364
339
  completedGroupsRef.current = all.filter((group) => !group.live);
365
- previousTimelineRef.current = {
366
- messages: timelineMessages,
367
- toolCalls,
368
- activeAssistantId: streamingAssistantId,
369
- groups: all,
370
- };
371
340
  return selectRecentTurnGroups(all, historyLimit);
372
341
  }, [
373
342
  timelineMessages,
@@ -376,12 +345,14 @@ export function useChatThread(params) {
376
345
  streamingAssistantId,
377
346
  historyLimit,
378
347
  ]);
379
- const loadEarlierMessages = useCallback(async () => {
348
+ const loadEarlierMessages = useCallback(async (beforePrepend) => {
380
349
  const targetSessionId = refs.sessionRef.current?.sessionId;
381
350
  const beforeId = historyNextCursor;
382
351
  if (!targetSessionId || !historyHasMore || !beforeId || historyLoadingRef.current)
383
352
  return false;
384
- historyLoadingRef.current = true;
353
+ const generation = refs.sessionGenerationRef.current;
354
+ const request = Symbol();
355
+ historyLoadingRef.current = request;
385
356
  setHistoryLoading(true);
386
357
  try {
387
358
  const query = new URLSearchParams({ limit: "50", beforeId });
@@ -389,7 +360,12 @@ export function useChatThread(params) {
389
360
  if (!res.ok || refs.sessionRef.current?.sessionId !== targetSessionId)
390
361
  return false;
391
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;
392
367
  const page = hydrateSessionThread(body.messages ?? []);
368
+ beforePrepend?.();
393
369
  setMessages((current) => {
394
370
  const seen = new Set();
395
371
  const merged = [...page.messages, ...current].filter((message) => {
@@ -398,11 +374,7 @@ export function useChatThread(params) {
398
374
  seen.add(message.id);
399
375
  return true;
400
376
  });
401
- return merged.sort((a, b) => {
402
- if (a.eventSeq !== undefined && b.eventSeq !== undefined && a.eventSeq !== b.eventSeq)
403
- return a.eventSeq - b.eventSeq;
404
- return a.createdAt - b.createdAt;
405
- });
377
+ return merged.sort(compareChatOrder);
406
378
  });
407
379
  setToolCalls((current) => {
408
380
  const byId = new Map(current.map((tool) => [tool.id, tool]));
@@ -415,11 +387,19 @@ export function useChatThread(params) {
415
387
  setHistoryLimit((value) => value + 50);
416
388
  return true;
417
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
+ }
418
396
  finally {
419
- historyLoadingRef.current = false;
420
- setHistoryLoading(false);
397
+ if (historyLoadingRef.current === request) {
398
+ historyLoadingRef.current = null;
399
+ setHistoryLoading(false);
400
+ }
421
401
  }
422
- }, [historyHasMore, historyNextCursor, origin, refs]);
402
+ }, [historyHasMore, historyNextCursor, origin, refs, setError]);
423
403
  const visibleMessages = useMemo(() => {
424
404
  const next = [];
425
405
  for (const group of turnGroups) {
@@ -453,13 +433,15 @@ export function useChatThread(params) {
453
433
  setHistoryLimit(50);
454
434
  setHistoryHasMore(false);
455
435
  setHistoryNextCursor(null);
456
- historyLoadingRef.current = false;
436
+ historyLoadingRef.current = null;
437
+ setHistoryLoading(false);
457
438
  completedGroupsRef.current = [];
458
- previousTimelineRef.current = null;
459
439
  refs.usageResponseKeysRef.current = new Set();
460
440
  }, [refs]);
461
441
  /** Adopt a freshly activated session's hydrated thread + streaming flags. */
462
442
  const applyActivatedSession = useCallback((next) => {
443
+ historyLoadingRef.current = null;
444
+ setHistoryLoading(false);
463
445
  const visuallyStopped = next.turnPhase === "stopping";
464
446
  // A submission still owned for THIS session keeps its optimistic bubble
465
447
  // across an activation snapshot (refresh / reopen); a lease owned by
@@ -511,7 +493,6 @@ export function useChatThread(params) {
511
493
  setHistoryHasMore(next.history?.hasMore === true);
512
494
  setHistoryNextCursor(next.history?.nextCursor ?? null);
513
495
  completedGroupsRef.current = [];
514
- previousTimelineRef.current = null;
515
496
  }, [refs]);
516
497
  /** Reconcile recovery (AC-001/AC-002/AC-003): reload the canonical session
517
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,44 +293,51 @@ 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
- }, []);
215
- // Auto-grow textarea height.
216
- useLayoutEffect(() => {
320
+ }, [setPendingImages]);
321
+ // Chromium handles growth in CSS without forcing a layout read on every key.
322
+ // Keep a deferred fallback for browsers that do not support field-sizing.
323
+ useEffect(() => {
217
324
  const ta = textareaRef.current;
218
325
  if (!ta)
219
326
  return;
220
- ta.style.height = "auto";
221
- ta.style.height = `${Math.min(ta.scrollHeight, 280)}px`;
327
+ if (typeof CSS !== "undefined" &&
328
+ CSS.supports("field-sizing", "content")) {
329
+ ta.style.removeProperty("height");
330
+ return;
331
+ }
332
+ const frame = window.requestAnimationFrame(() => {
333
+ ta.style.height = "auto";
334
+ ta.style.height = `${Math.min(ta.scrollHeight, 280)}px`;
335
+ });
336
+ return () => window.cancelAnimationFrame(frame);
222
337
  }, [input]);
223
338
  // Debounced server-side composer draft sync.
224
339
  useEffect(() => {
225
- if (!sessionId)
340
+ if (!sessionId || !sessionReady)
226
341
  return;
227
342
  const timer = window.setTimeout(() => {
228
343
  void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/draft`, {
@@ -236,7 +351,7 @@ export function useComposer(params) {
236
351
  });
237
352
  }, 400);
238
353
  return () => window.clearTimeout(timer);
239
- }, [origin, sessionId, input]);
354
+ }, [origin, sessionId, sessionReady, input]);
240
355
  const mergedCommands = useMemo(() => mergeSlashCommands(dynamicCommands, commandCapabilities), [dynamicCommands, commandCapabilities]);
241
356
  const filteredShortcuts = useMemo(() => input.startsWith("/")
242
357
  ? filterSlashCommands(mergedCommands, input)
@@ -270,17 +385,24 @@ export function useComposer(params) {
270
385
  useEffect(() => {
271
386
  attachRuntimeSnapshotController({
272
387
  origin,
273
- sessionId,
388
+ sessionId: sessionReady ? sessionId : undefined,
274
389
  refs,
275
390
  onUnrecoverableSession,
276
391
  });
277
- }, [origin, sessionId, refs, onUnrecoverableSession]);
392
+ }, [origin, sessionId, sessionReady, refs, onUnrecoverableSession]);
278
393
  return {
279
394
  input,
280
395
  setInput,
281
396
  handleInputChange,
282
397
  navigateHistory,
283
398
  recordHistory,
399
+ claimSubmission,
400
+ claimSessionSubmission,
401
+ settleSubmission,
402
+ settleSubmissionByRequest,
403
+ seedSession,
404
+ replaceSessionDraft,
405
+ releaseSession,
284
406
  fileOptions,
285
407
  pendingImages,
286
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 = [