@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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +31 -2309
- package/README.md +7 -0
- package/dist/application/dag/args.js +8 -0
- package/dist/application/task-lifecycle/advance.js +38 -7
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +2 -0
- package/dist/cli/program.js +3 -1
- package/dist/cli/update/init-surface-notifier.js +2 -0
- package/dist/cli/update/policy.js +2 -0
- package/dist/commands/client-recovery.js +20 -55
- package/dist/commands/cursor-prompt.js +2 -0
- package/dist/commands/dag-approve.js +2 -0
- package/dist/commands/dag-final-verification.js +2 -0
- package/dist/commands/dag-init-hybrid.js +2 -0
- package/dist/commands/dag-reconcile-tasks.js +2 -0
- package/dist/commands/dag-reject.js +2 -0
- package/dist/commands/dag-report.js +2 -0
- package/dist/commands/dag-rerun.js +2 -0
- package/dist/commands/dag-resume.js +2 -0
- package/dist/commands/dag-workflow-compile.js +2 -0
- package/dist/commands/dag-workflow-plan.js +2 -0
- package/dist/commands/dag-workflow-validate.js +2 -0
- package/dist/commands/delegate.js +2 -0
- package/dist/commands/examples.js +2 -0
- package/dist/commands/import-prd.js +2 -0
- package/dist/commands/init-model-catalog.js +2 -0
- package/dist/commands/init.js +19 -13
- package/dist/commands/instructions.js +2 -0
- package/dist/commands/knowledge.js +2 -0
- package/dist/commands/loop-benchmark.js +2 -0
- package/dist/commands/pi-prompt.js +2 -0
- package/dist/commands/pi-reuse-benchmark.js +2 -0
- package/dist/commands/promote-run.js +2 -0
- package/dist/commands/stats.js +1 -1
- package/dist/commands/study-init.js +2 -0
- package/dist/commands/task-advance.js +34 -0
- package/dist/commands/task-source-prepare.js +2 -0
- package/dist/commands/worktree-create.js +2 -0
- package/dist/commands/worktree-remove.js +2 -0
- package/dist/executors/dag-pi-executor.js +1650 -198
- package/dist/executors/pi-executor.js +85 -3
- package/dist/executors/pi-sdk-executor.js +18 -0
- package/dist/executors/shell-executor.js +93 -7
- package/dist/executors/shell-verification.js +3 -0
- package/dist/executors/shell-write-guard.js +27 -32
- package/dist/governance/exec-plans.js +6 -3
- package/dist/infrastructure/console/app-data.js +6 -0
- package/dist/shared/artifacts-core.js +2 -0
- package/dist/shared/backend-dogfood-preflight.js +47 -0
- package/dist/shared/dag-failure-category.js +3 -0
- package/dist/shared/git-progress.js +2 -0
- package/dist/shared/one-shot-prompt-args.js +2 -0
- package/dist/shared/operator/capabilities.js +180 -0
- package/dist/shared/package-metadata.js +6 -4
- package/dist/shared/pi-context-pressure/extension.js +26 -16
- package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
- package/dist/shared/pi-provider-recovery.js +182 -0
- package/dist/shared/pi-retry-settings.js +40 -0
- package/dist/shared/reference-context.js +2 -0
- package/dist/task/config-types.js +29 -0
- package/dist/task/contract/project.js +9 -0
- package/dist/task/contract/recover.js +6 -0
- package/dist/task/contract/schema.js +17 -0
- package/dist/task/source-prepare/artifact-meta.js +9 -2
- package/dist/task/source-prepare/build-draft.js +60 -0
- package/dist/task/source-prepare/fragment-inventory.js +4 -1
- package/dist/task/source-prepare/parse-intent.js +36 -11
- package/dist/task/task-demand-routing.js +5 -2
- package/dist/worker/console/chat/browser-automation.js +523 -0
- package/dist/worker/console/chat/browser-policy.js +6 -36
- package/dist/worker/console/chat/browser-routes.js +188 -56
- package/dist/worker/console/chat/explore-tools.js +5 -2
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +184 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +94 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +208 -1138
- package/dist/worker/console/chat/routes.js +27 -7
- package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
- package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
- package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
- package/dist/worker/console/chat/session-mode-view.js +5 -4
- package/dist/worker/console/chat/session-mode.js +18 -12
- package/dist/worker/console/chat/session-store.js +42 -9
- package/dist/worker/console/chat/tool-preview.js +115 -0
- package/dist/worker/console/chat/tools.js +10 -0
- package/dist/worker/console/chat/turn-order.js +13 -0
- package/dist/worker/console/chat/turn-process.js +32 -30
- package/dist/worker/console/console-update-and-init.js +1 -19
- package/dist/worker/console/console-update-runtime.js +5 -20
- package/dist/worker/console/operator-actions.js +63 -1
- package/dist/worker/console/prd-intake-bridge.js +54 -1
- package/dist/worker/console/routes.js +8 -0
- package/dist/worker/console/scheduled-goal-host.js +98 -0
- package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
- package/dist/worker/console/server.js +24 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D_fNbWc_.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
- package/dist/worker/console/static/assets/{arc-_HuqLW6m.js → arc-eoQiMvuk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Djy2qY1m.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-bB-1TSyY.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5Hmghrz.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Duyl39oy.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-x6mdnSyf.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-WlBuFj7W.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CTF429HX.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dwd6r-SF.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C-Br82D1.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CuGp0DeE.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CJhFX6Lh.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DpGOa_jT.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DXJaTynd.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CRBZWjE4.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BdXcxtlb.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-07UKsOts.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-D38p9__S.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-nSiBNNjH.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BFOTot5D.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DypKaZ6k.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-B6yoOQeW.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BHYS6viX.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D2VD5Snj.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-E98mWxN1.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CT1zxhqP.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-6GcngNcy.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCL6NvKe.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +469 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C0627e-9.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DPDPv9HL.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D3OHdWiO.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI88zD7n.js → linear-BDZ9riWi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-p2l8IrWY.js → mermaid.core-7pKqYtpZ.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-B1bAX-oS.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DwOnWxdD.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-xRkTXnJC.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-_9UC5Zio.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CgEUirWJ.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Czbpwiyz.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-_j7_KiqY.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BNJa6Hyi.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DedpKtOc.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BBvthr7c.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Ds7umgEM.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKa-p1nF.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-LoUNR56o.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bqbl6ym7.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B8dUg4lf.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +24 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
- package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +92 -38
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +38 -55
- package/dist/worker/console/static-src/operator-chat/useComposer.js +182 -69
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
- package/dist/worker/console/static-src/prd-file-import.js +3 -1
- package/dist/worker/console/workspace-context.js +22 -0
- package/dist/worker/console/workspace-initialization.js +209 -0
- package/dist/worker/delivery/git-transaction.js +2 -0
- package/dist/worker/feature/fullstack-validate.js +2 -0
- package/dist/worker/loop-agent/loop-agent-client.js +8 -2
- package/dist/worker/materialize/harness-task-materializer.js +4 -1
- package/dist/worker/metrics/projector.js +1 -1
- package/dist/worker/observability/event-store.js +4 -1
- package/dist/worker/observability/read-model.js +4 -1
- package/dist/worker/observe/node-transparency.js +23 -1
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/operator-chrome.js +3 -1
- package/dist/worker/observe/static/styles.css +7 -4
- package/dist/worker/observe/static/views/dag-inspector.js +5 -19
- package/dist/worker/outcomes/declared-artifacts.js +2 -0
- package/dist/worker/preflight.js +3 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
- package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
- package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
- package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
- package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
- package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
- package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
- package/dist/worker/task-spec/validate.js +4 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
- package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
- package/dist/workflows/dag/dag-retry-schema.js +11 -0
- package/dist/workflows/dag/decision-envelope.js +2 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-committed-facts.js +461 -0
- package/dist/workflows/dag/frontend-durable-tools.js +15 -3
- package/dist/workflows/dag/frontend-implementation-contract.js +369 -12
- package/dist/workflows/dag/frontend-plan-canary.js +53 -0
- package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
- package/dist/workflows/dag/frontend-plan-render.js +0 -2
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
- package/dist/workflows/dag/frontend-recovery-run.js +57 -0
- package/dist/workflows/dag/frontend-repair.js +55 -76
- package/dist/workflows/dag/frontend-review-context.js +42 -68
- package/dist/workflows/dag/frontend-review-scopes.js +2 -2
- package/dist/workflows/dag/frontend-risk.js +92 -10
- package/dist/workflows/dag/frontend-session-budget.js +117 -3
- package/dist/workflows/dag/frontend-shape.js +11 -55
- package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
- package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
- package/dist/workflows/dag/frontend-verification-trace.js +33 -54
- package/dist/workflows/dag/frontend-writer-admission.js +3 -47
- package/dist/workflows/dag/frontend-writer-status.js +0 -23
- package/dist/workflows/dag/init-hybrid.js +47 -19
- package/dist/workflows/dag/interrupt-request.js +2 -0
- package/dist/workflows/dag/lifecycle.js +11 -2
- package/dist/workflows/dag/node-execution.js +50 -13
- package/dist/workflows/dag/reconcile-run.js +2 -0
- package/dist/workflows/dag/repair-artifact.js +3 -1
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/rerun-plan.js +10 -0
- package/dist/workflows/dag/rerun-task.js +79 -1
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/scheduler.js +2 -4
- package/dist/workflows/dag/types.js +44 -6
- package/dist/workflows/dag/validate.js +4 -0
- package/docs/README.md +1 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/governance/README.md +1 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +14 -10
- package/docs/templates/frontend-implementation-contract.schema.json +0 -7
- package/package.json +9 -3
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +3 -1
- package/skills/frontend-plan/SKILL.md +8 -6
- package/skills/frontend-plan/references/decision-contract.md +3 -3
- package/skills/frontend-review/SKILL.md +18 -28
- package/skills/frontend-review/references/review-findings.md +8 -3
- package/dist/commands/new-task.js +0 -5
- package/dist/commands/status.js +0 -57
- package/dist/commands/task-contract.js +0 -269
- package/dist/shared/prompts.js +0 -26
- package/dist/task/config.js +0 -2
- package/dist/task/contract/validate-draft.js +0 -106
- package/dist/task/goal.js +0 -3
- package/dist/task/index.js +0 -12
- package/dist/task/lifecycle.js +0 -1
- package/dist/task/operator/capabilities.js +0 -6
- package/dist/task/operator/envelope.js +0 -2
- package/dist/task/operator/index.js +0 -5
- package/dist/task/operator/registry.js +0 -2
- package/dist/task/operator/types.js +0 -1
- package/dist/task/paths.js +0 -1
- package/dist/task/source-state.js +0 -1
- package/dist/task/subagent-guidance.js +0 -1
- package/dist/worker/console/chat/sift-bridge.js +0 -1
- package/dist/worker/console/index.js +0 -20
- package/dist/worker/console/static/assets/channel-C0A6UEIa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/index-24jp3aBh.js +0 -468
- package/dist/worker/console/static/assets/index-CY6y6I8M.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +0 -8
- package/dist/worker/scheduler/index.js +0 -25
- package/dist/workflows/dag/facts.js +0 -4
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
- package/dist/workflows/dag/index.js +0 -6
- 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(
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
418
|
-
|
|
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 =
|
|
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 {
|
|
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,
|
|
11
|
-
const
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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 =
|
|
55
|
-
text:
|
|
56
|
-
caretStart: textarea?.selectionStart ??
|
|
57
|
-
caretEnd: textarea?.selectionEnd ??
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
}, [
|
|
72
|
-
/** Record a submitted text into
|
|
109
|
+
}, [ledger]);
|
|
110
|
+
/** Record a submitted text into the selected session's history. */
|
|
73
111
|
const recordHistory = useCallback((text) => {
|
|
74
|
-
|
|
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) =>
|
|
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 () =>
|
|
179
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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]
|
|
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 = [
|