@tea-agent/loop-agent 0.39.0-next.9 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +269 -69
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +16 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +124 -18
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +555 -34
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +138 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/operator/capabilities.js +181 -27
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +28 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +3 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +20 -1
- package/dist/task/contract/schema.js +8 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +480 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +10 -10
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +320 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/routes.js +46 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +111 -7
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +6 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +67 -1
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +85 -0
- package/dist/worker/observe/static/views/dag-inspector.js +912 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +343 -33
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +110 -61
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-implementation-contract.js +465 -6
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +1019 -209
- package/dist/workflows/dag/node-execution.js +228 -15
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +224 -27
- package/dist/workflows/dag/rerun-run.js +61 -6
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +109 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +185 -12
- package/dist/workflows/dag/validate.js +64 -20
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +6 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +2 -2
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +6 -4
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
- package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect } from "react";
|
|
2
2
|
import { formatOperatorUserError } from "../../operator-user-error.js";
|
|
3
3
|
import { confirmationToken, requestId, sleep } from "./format.js";
|
|
4
|
-
import { classifyCreateTurnNetworkFailure, classifyCreateTurnResponse, createPendingSubmission, isRuntimeBusyUnion, isSubmissionCurrent, isTerminalTurnState, keepAmbiguousAfterRetry, reconcilePendingSubmissionFromState, resolveAmbiguousFromState, } from "./turn-submission.js";
|
|
5
|
-
import { applyAbortDecision, applyPendingReconcileDecision, classifyAbortResponse, markStopRequested, operatorChatAbortUrl, operatorChatEventsUrl, operatorChatStateUrl, operatorChatTurnsUrl, releaseOwnershipOnTerminalEvidence, releaseSubmitOwnership as releaseOwnershipBag, requestStopOnOwnership, settleCreateTurnOutcome, } from "./turn-stream-controller.js";
|
|
4
|
+
import { classifyCreateTurnNetworkFailure, classifyCreateTurnResponse, createPendingSubmission, decideSubmissionRoute, isRuntimeBusyUnion, isSubmissionCurrent, isTerminalTurnState, keepAmbiguousAfterRetry, reconcileDeferredSubmissionFromState, reconcilePendingSubmissionFromState, resolveAmbiguousFromState, } from "./turn-submission.js";
|
|
5
|
+
import { applyAbortDecision, applyPendingReconcileDecision, classifyAbortResponse, markStopRequested, operatorChatAbortUrl, operatorChatEventsUrl, operatorChatStateUrl, operatorChatTurnsUrl, releaseOwnershipOnTerminalEvidence, releaseSubmitOwnership as releaseOwnershipBag, requestStopOnOwnership, settleCreateTurnOutcome, turnPhaseFromOwnership, } from "./turn-stream-controller.js";
|
|
6
6
|
/** Project ChatRefs into the executable ownership bag used by the controller. */
|
|
7
7
|
function ownershipBagFromRefs(refs) {
|
|
8
8
|
return {
|
|
@@ -57,8 +57,8 @@ function clearEmptyAssistant(setMessages, assistantId) {
|
|
|
57
57
|
* detached Turn resource only; it never opens a second inline SSE consumer. */
|
|
58
58
|
export function useChatStream(params) {
|
|
59
59
|
const { origin, refs, session, thread, composer, setError, setAutoScroll, setNotice, recoveryVersion = 0, watchSessionTitle, } = params;
|
|
60
|
-
const { applySseBlock, setMessages, setStreaming, setStreamingAssistantId, streaming, } = thread;
|
|
61
|
-
const { setInput, setPendingImages } = composer;
|
|
60
|
+
const { applySseBlock, setMessages, setStreaming, setStreamingAssistantId, streaming, setTurnPhase, setDeferredTurn, } = thread;
|
|
61
|
+
const { setInput, setPendingImages, recordHistory } = composer;
|
|
62
62
|
const showNotice = useCallback((message) => {
|
|
63
63
|
if (!setNotice)
|
|
64
64
|
return;
|
|
@@ -78,9 +78,10 @@ export function useChatStream(params) {
|
|
|
78
78
|
refs.assistantIdRef.current = null;
|
|
79
79
|
refs.stoppingTurnIdRef.current = null;
|
|
80
80
|
setStreaming(false);
|
|
81
|
+
setTurnPhase("idle");
|
|
81
82
|
setStreamingAssistantId(null);
|
|
82
83
|
clearEmptyAssistant(setMessages, assistantId);
|
|
83
|
-
}, [refs, setStreaming, setStreamingAssistantId, setMessages]);
|
|
84
|
+
}, [refs, setStreaming, setTurnPhase, setStreamingAssistantId, setMessages]);
|
|
84
85
|
/** Follow one session's event stream until it is explicitly aborted. The
|
|
85
86
|
* server's `stream_ready` marker separates replay from live events; this
|
|
86
87
|
* means replay cannot render duplicate messages while live text remains
|
|
@@ -242,9 +243,13 @@ export function useChatStream(params) {
|
|
|
242
243
|
/** Issue real POST /abort for a known turnId (Stop never disposes Session). */
|
|
243
244
|
const postAbortForTurn = useCallback(async (sessionId, generation, turnId) => {
|
|
244
245
|
refs.stoppingTurnIdRef.current = turnId;
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
// Optimistic visual stop: remove the running spinner immediately, but
|
|
247
|
+
// retain ownership as `stopping` until the server proves a terminal state.
|
|
248
|
+
// This keeps the composer responsive without falsely advertising idle.
|
|
249
|
+
refs.streamingRef.current = false;
|
|
250
|
+
setStreaming(false);
|
|
251
|
+
setTurnPhase("stopping");
|
|
252
|
+
setStreamingAssistantId(null);
|
|
248
253
|
try {
|
|
249
254
|
const res = await fetch(operatorChatAbortUrl(origin, sessionId, turnId), {
|
|
250
255
|
method: "POST",
|
|
@@ -278,6 +283,7 @@ export function useChatStream(params) {
|
|
|
278
283
|
markIdle: () => markIdleIfCurrent(sessionId, generation, refs.assistantIdRef.current),
|
|
279
284
|
});
|
|
280
285
|
applyOwnershipBagToRefs(refs, bag);
|
|
286
|
+
setTurnPhase(turnPhaseFromOwnership(bag));
|
|
281
287
|
return;
|
|
282
288
|
}
|
|
283
289
|
applyAbortDecision(bag, decision, noticeSink(), {
|
|
@@ -285,6 +291,11 @@ export function useChatStream(params) {
|
|
|
285
291
|
markIdle: () => markIdleIfCurrent(sessionId, generation, refs.assistantIdRef.current),
|
|
286
292
|
});
|
|
287
293
|
applyOwnershipBagToRefs(refs, bag);
|
|
294
|
+
setTurnPhase(turnPhaseFromOwnership(bag));
|
|
295
|
+
// Sync the post-decision busy state back to React: optimistic stop
|
|
296
|
+
// flipped it idle up front; pending/keep-busy outcomes restore it.
|
|
297
|
+
if (bag.streaming)
|
|
298
|
+
setStreaming(true);
|
|
288
299
|
}
|
|
289
300
|
catch {
|
|
290
301
|
// Abort network failure: keep busy (UIS-abort-network-fail).
|
|
@@ -297,11 +308,17 @@ export function useChatStream(params) {
|
|
|
297
308
|
networkError: true,
|
|
298
309
|
}), noticeSink());
|
|
299
310
|
applyOwnershipBagToRefs(refs, bag);
|
|
311
|
+
// Network failure: the optimistic idle was premature — restore busy
|
|
312
|
+
// so the turn is not silently dropped; /state reconcile converges.
|
|
313
|
+
refs.streamingRef.current = true;
|
|
314
|
+
setStreaming(true);
|
|
315
|
+
setTurnPhase("stopping");
|
|
300
316
|
}
|
|
301
317
|
}, [
|
|
302
318
|
origin,
|
|
303
319
|
refs,
|
|
304
320
|
setStreaming,
|
|
321
|
+
setTurnPhase,
|
|
305
322
|
markIdleIfCurrent,
|
|
306
323
|
fetchSessionState,
|
|
307
324
|
noticeSink,
|
|
@@ -351,10 +368,12 @@ export function useChatStream(params) {
|
|
|
351
368
|
},
|
|
352
369
|
});
|
|
353
370
|
applyOwnershipBagToRefs(refs, bag);
|
|
371
|
+
setTurnPhase(turnPhaseFromOwnership(bag));
|
|
354
372
|
}, [
|
|
355
373
|
refs,
|
|
356
374
|
noticeSink,
|
|
357
375
|
setStreaming,
|
|
376
|
+
setTurnPhase,
|
|
358
377
|
setStreamingAssistantId,
|
|
359
378
|
setPendingImages,
|
|
360
379
|
watchSessionTitle,
|
|
@@ -363,20 +382,212 @@ export function useChatStream(params) {
|
|
|
363
382
|
setError,
|
|
364
383
|
postAbortForTurn,
|
|
365
384
|
]);
|
|
385
|
+
const queueDeferredTurn = useCallback(async (input) => {
|
|
386
|
+
const activeSession = refs.sessionRef.current;
|
|
387
|
+
if (!activeSession || (!input.text.trim() && input.images.length === 0))
|
|
388
|
+
return { action: "handled" };
|
|
389
|
+
const existing = thread.deferredTurn;
|
|
390
|
+
showNotice("正在排队…");
|
|
391
|
+
const acceptDeferred = (deferredTurn) => {
|
|
392
|
+
setDeferredTurn(deferredTurn);
|
|
393
|
+
setInput("");
|
|
394
|
+
setPendingImages([]);
|
|
395
|
+
setError(null);
|
|
396
|
+
showNotice(existing ? "已更新排队消息" : "已排队,将在当前回合结束后自动发送");
|
|
397
|
+
};
|
|
398
|
+
const acceptDispatched = () => {
|
|
399
|
+
setDeferredTurn(null);
|
|
400
|
+
setInput("");
|
|
401
|
+
setPendingImages([]);
|
|
402
|
+
setError(null);
|
|
403
|
+
showNotice("消息已进入发送");
|
|
404
|
+
};
|
|
405
|
+
const putOnce = async () => {
|
|
406
|
+
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn`, {
|
|
407
|
+
method: "PUT",
|
|
408
|
+
headers: {
|
|
409
|
+
"Content-Type": "application/json",
|
|
410
|
+
"x-loop-console-confirmation": confirmationToken(),
|
|
411
|
+
},
|
|
412
|
+
credentials: "include",
|
|
413
|
+
body: JSON.stringify({
|
|
414
|
+
clientRequestId: input.clientRequestId,
|
|
415
|
+
blockedByTurnId: input.blockedByTurnId,
|
|
416
|
+
text: input.text,
|
|
417
|
+
images: input.images,
|
|
418
|
+
...(existing ? { expectedRevision: existing.revision } : {}),
|
|
419
|
+
}),
|
|
420
|
+
});
|
|
421
|
+
const body = (await response.json().catch(() => ({})));
|
|
422
|
+
return { response, body };
|
|
423
|
+
};
|
|
424
|
+
try {
|
|
425
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
426
|
+
const { response, body } = await putOnce();
|
|
427
|
+
if (response.ok && body.accepted && body.deferredTurn) {
|
|
428
|
+
acceptDeferred(body.deferredTurn);
|
|
429
|
+
return { action: "handled" };
|
|
430
|
+
}
|
|
431
|
+
if (body.deferredTurn)
|
|
432
|
+
setDeferredTurn(body.deferredTurn);
|
|
433
|
+
if (body.error?.code !== "TURN_NOT_ACTIVE") {
|
|
434
|
+
setError(body.error?.message ??
|
|
435
|
+
`待发送消息未被服务端接受(HTTP ${response.status})`);
|
|
436
|
+
return { action: "handled" };
|
|
437
|
+
}
|
|
438
|
+
const canonical = await fetchSessionState(activeSession.sessionId);
|
|
439
|
+
const decision = reconcileDeferredSubmissionFromState({
|
|
440
|
+
clientRequestId: input.clientRequestId,
|
|
441
|
+
blockedByTurnId: input.blockedByTurnId,
|
|
442
|
+
state: canonical ?? undefined,
|
|
443
|
+
});
|
|
444
|
+
if (decision.action === "deferred") {
|
|
445
|
+
acceptDeferred(decision.deferredTurn);
|
|
446
|
+
return { action: "handled" };
|
|
447
|
+
}
|
|
448
|
+
if (decision.action === "accepted") {
|
|
449
|
+
acceptDispatched();
|
|
450
|
+
return { action: "handled" };
|
|
451
|
+
}
|
|
452
|
+
if (decision.action === "retry-deferred" && attempt === 0)
|
|
453
|
+
continue;
|
|
454
|
+
if (decision.action === "send-now" && !existing) {
|
|
455
|
+
showNotice("上一轮已停止,正在发送当前消息…");
|
|
456
|
+
return { action: "send-now" };
|
|
457
|
+
}
|
|
458
|
+
setError(body.error?.message ?? "停止状态已变化,当前消息仍保留在输入框");
|
|
459
|
+
return { action: "handled" };
|
|
460
|
+
}
|
|
461
|
+
return { action: "handled" };
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
const canonical = await fetchSessionState(activeSession.sessionId);
|
|
465
|
+
const decision = reconcileDeferredSubmissionFromState({
|
|
466
|
+
clientRequestId: input.clientRequestId,
|
|
467
|
+
blockedByTurnId: input.blockedByTurnId,
|
|
468
|
+
state: canonical ?? undefined,
|
|
469
|
+
});
|
|
470
|
+
if (decision.action === "deferred") {
|
|
471
|
+
acceptDeferred(decision.deferredTurn);
|
|
472
|
+
return { action: "handled" };
|
|
473
|
+
}
|
|
474
|
+
if (decision.action === "accepted") {
|
|
475
|
+
acceptDispatched();
|
|
476
|
+
return { action: "handled" };
|
|
477
|
+
}
|
|
478
|
+
if (decision.action === "send-now" && !existing) {
|
|
479
|
+
showNotice("上一轮已停止,正在发送当前消息…");
|
|
480
|
+
return { action: "send-now" };
|
|
481
|
+
}
|
|
482
|
+
setError(error instanceof Error
|
|
483
|
+
? `无法确认消息是否已排队:${error.message}`
|
|
484
|
+
: "无法确认消息是否已排队,请刷新后查看服务端状态");
|
|
485
|
+
return { action: "handled" };
|
|
486
|
+
}
|
|
487
|
+
}, [
|
|
488
|
+
origin,
|
|
489
|
+
refs,
|
|
490
|
+
thread.deferredTurn,
|
|
491
|
+
setDeferredTurn,
|
|
492
|
+
setInput,
|
|
493
|
+
setPendingImages,
|
|
494
|
+
setError,
|
|
495
|
+
showNotice,
|
|
496
|
+
fetchSessionState,
|
|
497
|
+
]);
|
|
498
|
+
/** DELETE the queued turn; "restore" moves its text back into the composer,
|
|
499
|
+
* "discard" drops it entirely (2026-08-23 queue chip actions). */
|
|
500
|
+
const requestDeferredTurnDelete = useCallback(async (mode) => {
|
|
501
|
+
const activeSession = refs.sessionRef.current;
|
|
502
|
+
const deferred = thread.deferredTurn;
|
|
503
|
+
if (!activeSession || !deferred)
|
|
504
|
+
return;
|
|
505
|
+
try {
|
|
506
|
+
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn`, {
|
|
507
|
+
method: "DELETE",
|
|
508
|
+
headers: {
|
|
509
|
+
"Content-Type": "application/json",
|
|
510
|
+
"x-loop-console-confirmation": confirmationToken(),
|
|
511
|
+
},
|
|
512
|
+
credentials: "include",
|
|
513
|
+
body: JSON.stringify({
|
|
514
|
+
clientRequestId: deferred.clientRequestId,
|
|
515
|
+
expectedRevision: deferred.revision,
|
|
516
|
+
}),
|
|
517
|
+
});
|
|
518
|
+
const body = (await response.json().catch(() => ({})));
|
|
519
|
+
if (!response.ok || !body.cancelled) {
|
|
520
|
+
setError(body.error?.message ?? "待发送消息撤回失败");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
setDeferredTurn(null);
|
|
524
|
+
if (mode === "restore") {
|
|
525
|
+
setInput(body.deferredTurn?.text ?? "");
|
|
526
|
+
setPendingImages(body.deferredTurn?.images ?? []);
|
|
527
|
+
showNotice("待发送消息已移回输入框");
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
showNotice("已删除排队消息");
|
|
531
|
+
}
|
|
532
|
+
setError(null);
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
536
|
+
}
|
|
537
|
+
}, [
|
|
538
|
+
origin,
|
|
539
|
+
refs,
|
|
540
|
+
thread.deferredTurn,
|
|
541
|
+
setDeferredTurn,
|
|
542
|
+
setInput,
|
|
543
|
+
setPendingImages,
|
|
544
|
+
setError,
|
|
545
|
+
showNotice,
|
|
546
|
+
]);
|
|
547
|
+
const cancelDeferredTurn = useCallback(() => requestDeferredTurnDelete("restore"), [requestDeferredTurnDelete]);
|
|
548
|
+
const discardDeferredTurn = useCallback(() => requestDeferredTurnDelete("discard"), [requestDeferredTurnDelete]);
|
|
366
549
|
const sendPrompt = useCallback(async (text, images) => {
|
|
550
|
+
const initialSession = refs.sessionRef.current;
|
|
551
|
+
if (!initialSession || (!text.trim() && images.length === 0))
|
|
552
|
+
return;
|
|
553
|
+
const ownership = ownershipBagFromRefs(refs);
|
|
554
|
+
const route = decideSubmissionRoute({
|
|
555
|
+
turnPhase: turnPhaseFromOwnership(ownership),
|
|
556
|
+
blockedByTurnId: ownership.stoppingTurnId ?? ownership.activeTurnId,
|
|
557
|
+
});
|
|
558
|
+
const clientRequestId = requestId("chat-submit");
|
|
559
|
+
if (route.action === "hold") {
|
|
560
|
+
const phase = turnPhaseFromOwnership(ownership);
|
|
561
|
+
if (phase === "stopping")
|
|
562
|
+
showNotice("正在确认上一轮停止状态,当前消息已保留");
|
|
563
|
+
else if (phase === "running")
|
|
564
|
+
showNotice("正在确认当前回合状态,当前消息已保留");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
recordHistory?.(text);
|
|
568
|
+
if (route.action === "queue-deferred") {
|
|
569
|
+
const deferred = await queueDeferredTurn({
|
|
570
|
+
text,
|
|
571
|
+
images,
|
|
572
|
+
clientRequestId,
|
|
573
|
+
blockedByTurnId: route.blockedByTurnId,
|
|
574
|
+
});
|
|
575
|
+
if (deferred.action !== "send-now")
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
367
578
|
const activeSession = refs.sessionRef.current;
|
|
368
579
|
const generation = refs.sessionGenerationRef.current;
|
|
369
580
|
if (!activeSession ||
|
|
581
|
+
activeSession.sessionId !== initialSession.sessionId ||
|
|
370
582
|
(!text.trim() && images.length === 0) ||
|
|
371
583
|
refs.streamingRef.current ||
|
|
372
584
|
refs.submitInFlightRef.current ||
|
|
373
585
|
// AC-R5-002: ownership lease blocks send until terminal release.
|
|
374
|
-
|
|
586
|
+
refs.pendingSubmissionRef.current)
|
|
375
587
|
return;
|
|
376
588
|
// AC-001: acquire synchronous submit ownership before the first await.
|
|
377
589
|
refs.submitInFlightRef.current = true;
|
|
378
590
|
const submitGeneration = ++refs.submitGenerationRef.current;
|
|
379
|
-
const clientRequestId = requestId("chat-submit");
|
|
380
591
|
const userMessageId = requestId("u");
|
|
381
592
|
const assistantId = requestId("a");
|
|
382
593
|
const draftText = text.trim();
|
|
@@ -424,6 +635,7 @@ export function useChatStream(params) {
|
|
|
424
635
|
setAutoScroll(true);
|
|
425
636
|
setError(null);
|
|
426
637
|
refs.streamingRef.current = true;
|
|
638
|
+
setTurnPhase("running");
|
|
427
639
|
refs.assistantIdRef.current = assistantId;
|
|
428
640
|
setStreaming(true);
|
|
429
641
|
setStreamingAssistantId(assistantId);
|
|
@@ -519,7 +731,10 @@ export function useChatStream(params) {
|
|
|
519
731
|
setAutoScroll,
|
|
520
732
|
setError,
|
|
521
733
|
setStreaming,
|
|
734
|
+
setTurnPhase,
|
|
522
735
|
setStreamingAssistantId,
|
|
736
|
+
queueDeferredTurn,
|
|
737
|
+
recordHistory,
|
|
523
738
|
]);
|
|
524
739
|
const handleStop = useCallback(async () => {
|
|
525
740
|
// AC-R5-001: durable stopRequested is set synchronously before any await so
|
|
@@ -535,6 +750,7 @@ export function useChatStream(params) {
|
|
|
535
750
|
notice: noticeSink(),
|
|
536
751
|
});
|
|
537
752
|
applyOwnershipBagToRefs(refs, bag);
|
|
753
|
+
setTurnPhase("stopping");
|
|
538
754
|
// Immediate abort already scheduled for a known turnId — coalesce via
|
|
539
755
|
// abortPostedTurnIds; stopInFlight only gates concurrent in-flight POSTs.
|
|
540
756
|
if (stopResult.abortedTurnId) {
|
|
@@ -643,6 +859,9 @@ export function useChatStream(params) {
|
|
|
643
859
|
},
|
|
644
860
|
});
|
|
645
861
|
applyOwnershipBagToRefs(refs, bag);
|
|
862
|
+
setTurnPhase(turnPhaseFromOwnership(bag));
|
|
863
|
+
if (state && "deferredTurn" in state)
|
|
864
|
+
setDeferredTurn(state.deferredTurn ?? null);
|
|
646
865
|
if (result === "bound") {
|
|
647
866
|
// US-BUSY-ACTIVE-BOUND: keep streaming/busy with same id.
|
|
648
867
|
setStreaming(true);
|
|
@@ -742,6 +961,7 @@ export function useChatStream(params) {
|
|
|
742
961
|
refs,
|
|
743
962
|
session,
|
|
744
963
|
streaming,
|
|
964
|
+
setTurnPhase,
|
|
745
965
|
markIdleIfCurrent,
|
|
746
966
|
releaseSubmitOwnership,
|
|
747
967
|
postAbortForTurn,
|
|
@@ -763,5 +983,11 @@ export function useChatStream(params) {
|
|
|
763
983
|
}
|
|
764
984
|
};
|
|
765
985
|
}, [session, recoveryVersion, refs, startEventsStream]);
|
|
766
|
-
return {
|
|
986
|
+
return {
|
|
987
|
+
sendPrompt,
|
|
988
|
+
handleStop,
|
|
989
|
+
cancelDeferredTurn,
|
|
990
|
+
discardDeferredTurn,
|
|
991
|
+
openEventsStream,
|
|
992
|
+
};
|
|
767
993
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { buildTurnProcessGroups } from "../../chat/turn-process.js";
|
|
2
|
+
import { buildTurnProcessGroups, hydrateSessionThread, selectRecentTurnGroups, } from "../../chat/turn-process.js";
|
|
4
3
|
import { createChatSseEventApplier, } from "./chat-sse-events.js";
|
|
5
4
|
import { taskContextUnchanged } from "./format.js";
|
|
6
5
|
const EMPTY_USAGE = {
|
|
@@ -32,6 +31,8 @@ export function useChatThread(params) {
|
|
|
32
31
|
const [contextLoading, setContextLoading] = useState(false);
|
|
33
32
|
const [usage, setUsage] = useState(EMPTY_USAGE);
|
|
34
33
|
const [streaming, setStreaming] = useState(false);
|
|
34
|
+
const [turnPhase, setTurnPhase] = useState("idle");
|
|
35
|
+
const [deferredTurn, setDeferredTurn] = useState(null);
|
|
35
36
|
/** Assistant bubble id for the in-flight turn; only this row shows the streaming cursor. */
|
|
36
37
|
const [streamingAssistantId, setStreamingAssistantId] = useState(null);
|
|
37
38
|
const [showSystemInMessages] = useState(false);
|
|
@@ -45,6 +46,8 @@ export function useChatThread(params) {
|
|
|
45
46
|
setHumanGateCards,
|
|
46
47
|
setArtifactCards,
|
|
47
48
|
setStreaming,
|
|
49
|
+
setTurnPhase,
|
|
50
|
+
setDeferredTurn,
|
|
48
51
|
setStreamingAssistantId,
|
|
49
52
|
setUnread,
|
|
50
53
|
setOperationCards,
|
|
@@ -121,9 +124,7 @@ export function useChatThread(params) {
|
|
|
121
124
|
}, 350);
|
|
122
125
|
return () => window.clearTimeout(timer);
|
|
123
126
|
}, [sessionId, operationCards, humanGateCards, interview, loadTaskContext]);
|
|
124
|
-
const
|
|
125
|
-
.filter((m) => m.role !== "system" || showSystemInMessages)
|
|
126
|
-
.slice(-historyLimit), [messages, showSystemInMessages, historyLimit]);
|
|
127
|
+
const timelineMessages = useMemo(() => messages.filter((m) => m.role !== "system" || showSystemInMessages), [messages, showSystemInMessages]);
|
|
127
128
|
useEffect(() => {
|
|
128
129
|
const onVisibility = () => {
|
|
129
130
|
if (!document.hidden)
|
|
@@ -158,12 +159,30 @@ export function useChatThread(params) {
|
|
|
158
159
|
statusKind,
|
|
159
160
|
};
|
|
160
161
|
}, [toolCalls]);
|
|
161
|
-
const turnGroups = useMemo(() =>
|
|
162
|
-
|
|
162
|
+
const turnGroups = useMemo(() => {
|
|
163
|
+
const all = buildTurnProcessGroups({
|
|
164
|
+
messages: timelineMessages,
|
|
165
|
+
toolCalls,
|
|
166
|
+
streaming,
|
|
167
|
+
activeAssistantId: streamingAssistantId,
|
|
168
|
+
});
|
|
169
|
+
return selectRecentTurnGroups(all, historyLimit);
|
|
170
|
+
}, [
|
|
171
|
+
timelineMessages,
|
|
163
172
|
toolCalls,
|
|
164
173
|
streaming,
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
streamingAssistantId,
|
|
175
|
+
historyLimit,
|
|
176
|
+
]);
|
|
177
|
+
const visibleMessages = useMemo(() => {
|
|
178
|
+
const next = [];
|
|
179
|
+
for (const group of turnGroups) {
|
|
180
|
+
if (group.user)
|
|
181
|
+
next.push(group.user);
|
|
182
|
+
next.push(...group.assistants);
|
|
183
|
+
}
|
|
184
|
+
return next;
|
|
185
|
+
}, [turnGroups]);
|
|
167
186
|
/** Reset all session-scoped projections before adopting a fresh session. */
|
|
168
187
|
const resetForNewSession = useCallback(() => {
|
|
169
188
|
setMessages([]);
|
|
@@ -175,6 +194,8 @@ export function useChatThread(params) {
|
|
|
175
194
|
setTaskContext(null);
|
|
176
195
|
setUsage(EMPTY_USAGE);
|
|
177
196
|
setStreaming(false);
|
|
197
|
+
setTurnPhase("idle");
|
|
198
|
+
setDeferredTurn(null);
|
|
178
199
|
setStreamingAssistantId(null);
|
|
179
200
|
setUnread(0);
|
|
180
201
|
setHistoryLimit(50);
|
|
@@ -192,6 +213,8 @@ export function useChatThread(params) {
|
|
|
192
213
|
setUsage(EMPTY_USAGE);
|
|
193
214
|
refs.usageResponseKeysRef.current = new Set();
|
|
194
215
|
setStreaming(next.streaming);
|
|
216
|
+
setTurnPhase(next.turnPhase ?? (next.streaming ? "running" : "idle"));
|
|
217
|
+
setDeferredTurn(next.deferredTurn ?? null);
|
|
195
218
|
setStreamingAssistantId(null);
|
|
196
219
|
setUnread(0);
|
|
197
220
|
setHistoryLimit(50);
|
|
@@ -213,6 +236,8 @@ export function useChatThread(params) {
|
|
|
213
236
|
const activeTurnId = body.session.activeTurnId ?? null;
|
|
214
237
|
refs.activeTurnIdRef.current = activeTurnId;
|
|
215
238
|
refs.streamingRef.current = Boolean(activeTurnId);
|
|
239
|
+
setTurnPhase(body.session.turnPhase ?? (activeTurnId ? "running" : "idle"));
|
|
240
|
+
setDeferredTurn(body.session.deferredTurn ?? null);
|
|
216
241
|
// Resume the events stream from the snapshot tail so the next
|
|
217
242
|
// reconnect never immediately re-expires (AC-003). The refs below
|
|
218
243
|
// are reset so any follow-up tail replay re-applies cleanly.
|
|
@@ -220,18 +245,9 @@ export function useChatThread(params) {
|
|
|
220
245
|
refs.seenEventIdsRef.current = new Set();
|
|
221
246
|
refs.turnAssistantIdsRef.current = new Map();
|
|
222
247
|
refs.sealedAssistantIdsRef.current = new Set();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const content = parseAssistantContent(message.content);
|
|
227
|
-
return {
|
|
228
|
-
id: message.id,
|
|
229
|
-
role: message.role,
|
|
230
|
-
text: message.text,
|
|
231
|
-
createdAt: Date.parse(message.at) || Date.now(),
|
|
232
|
-
...(content && content.length > 0 ? { content } : {}),
|
|
233
|
-
};
|
|
234
|
-
}));
|
|
248
|
+
const hydrated = hydrateSessionThread(body.session.messages ?? []);
|
|
249
|
+
setMessages(hydrated.messages);
|
|
250
|
+
setToolCalls(hydrated.toolCalls);
|
|
235
251
|
setStreamingAssistantId(null);
|
|
236
252
|
return true;
|
|
237
253
|
}
|
|
@@ -240,7 +256,15 @@ export function useChatThread(params) {
|
|
|
240
256
|
// trigger another reconcile and retry (best-effort recovery).
|
|
241
257
|
return false;
|
|
242
258
|
}
|
|
243
|
-
}, [
|
|
259
|
+
}, [
|
|
260
|
+
origin,
|
|
261
|
+
refs,
|
|
262
|
+
setMessages,
|
|
263
|
+
setToolCalls,
|
|
264
|
+
setStreamingAssistantId,
|
|
265
|
+
setTurnPhase,
|
|
266
|
+
setDeferredTurn,
|
|
267
|
+
]);
|
|
244
268
|
return {
|
|
245
269
|
messages,
|
|
246
270
|
setMessages,
|
|
@@ -259,6 +283,10 @@ export function useChatThread(params) {
|
|
|
259
283
|
usage,
|
|
260
284
|
streaming,
|
|
261
285
|
setStreaming,
|
|
286
|
+
turnPhase,
|
|
287
|
+
setTurnPhase,
|
|
288
|
+
deferredTurn,
|
|
289
|
+
setDeferredTurn,
|
|
262
290
|
streamingAssistantId,
|
|
263
291
|
setStreamingAssistantId,
|
|
264
292
|
visibleMessages,
|
|
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } f
|
|
|
2
2
|
import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
|
|
3
3
|
import { confirmationToken } from "./format.js";
|
|
4
4
|
import { clampShortcutIndex } from "./slash-palette-nav.js";
|
|
5
|
+
import { createInputHistoryState, navigateInputHistory, recordInputHistoryEntry, resetInputHistoryBrowsing, } from "./input-history.js";
|
|
5
6
|
import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
|
|
6
7
|
/** Composer state: input text, @file palette, image attachments, /shortcuts,
|
|
7
8
|
* IME composition guards, auto-grow and server-side draft sync. */
|
|
@@ -31,6 +32,8 @@ export function useComposer(params) {
|
|
|
31
32
|
const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
|
|
32
33
|
const [commandsEpoch, setCommandsEpoch] = useState(0);
|
|
33
34
|
const textareaRef = useRef(null);
|
|
35
|
+
/** Arrow-key input history; unsent drafts survive history browsing. */
|
|
36
|
+
const historyRef = useRef(createInputHistoryState());
|
|
34
37
|
/** Active session generation for /commands; bumped immediately on switch (AC-2). */
|
|
35
38
|
const commandsSessionRef = useRef(sessionId);
|
|
36
39
|
const commandsGenerationRef = useRef(0);
|
|
@@ -38,6 +41,33 @@ export function useComposer(params) {
|
|
|
38
41
|
setInput(value);
|
|
39
42
|
setShortcutOpen(value.startsWith("/"));
|
|
40
43
|
setShortcutIndex(0);
|
|
44
|
+
// Manual typing edits a recalled entry in place → back to present draft.
|
|
45
|
+
historyRef.current = resetInputHistoryBrowsing(historyRef.current);
|
|
46
|
+
}, []);
|
|
47
|
+
/** Recall an earlier input via ↑/↓; returns true when the key was consumed. */
|
|
48
|
+
const navigateHistory = useCallback((direction) => {
|
|
49
|
+
const textarea = textareaRef.current;
|
|
50
|
+
const decision = navigateInputHistory(historyRef.current, direction, {
|
|
51
|
+
text: input,
|
|
52
|
+
caretStart: textarea?.selectionStart ?? input.length,
|
|
53
|
+
caretEnd: textarea?.selectionEnd ?? input.length,
|
|
54
|
+
});
|
|
55
|
+
if (!decision.handled)
|
|
56
|
+
return false;
|
|
57
|
+
historyRef.current = decision.state;
|
|
58
|
+
setInput(decision.text);
|
|
59
|
+
const caret = decision.text.length;
|
|
60
|
+
queueMicrotask(() => {
|
|
61
|
+
const el = textareaRef.current;
|
|
62
|
+
if (el && el.value === decision.text) {
|
|
63
|
+
el.setSelectionRange(caret, caret);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return true;
|
|
67
|
+
}, [input, setInput]);
|
|
68
|
+
/** Record a submitted text into history (consecutive duplicates collapse). */
|
|
69
|
+
const recordHistory = useCallback((text) => {
|
|
70
|
+
historyRef.current = recordInputHistoryEntry(historyRef.current, text);
|
|
41
71
|
}, []);
|
|
42
72
|
// Immediate invalidation on A→B even when B palette never opens (AC-2).
|
|
43
73
|
useLayoutEffect(() => {
|
|
@@ -234,6 +264,8 @@ export function useComposer(params) {
|
|
|
234
264
|
input,
|
|
235
265
|
setInput,
|
|
236
266
|
handleInputChange,
|
|
267
|
+
navigateHistory,
|
|
268
|
+
recordHistory,
|
|
237
269
|
fileOptions,
|
|
238
270
|
pendingImages,
|
|
239
271
|
setPendingImages,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { ACTIVE_CHAT_SESSION_STORAGE_KEY } from "../../chat/workspace-landing.js";
|
|
3
3
|
/** Left-rail repo tree + file preview tabs state. */
|
|
4
4
|
export function useRepoBrowser(params) {
|
|
@@ -9,6 +9,8 @@ export function useRepoBrowser(params) {
|
|
|
9
9
|
const [previews, setPreviews] = useState([]);
|
|
10
10
|
const [activePreviewPath, setActivePreviewPath] = useState(null);
|
|
11
11
|
const [previewLoading, setPreviewLoading] = useState(false);
|
|
12
|
+
const expandedDirsRef = useRef(new Set());
|
|
13
|
+
expandedDirsRef.current = expandedDirs;
|
|
12
14
|
const loadTree = useCallback(async (directory = "") => {
|
|
13
15
|
try {
|
|
14
16
|
setTreeError(null);
|
|
@@ -28,6 +30,28 @@ export function useRepoBrowser(params) {
|
|
|
28
30
|
useEffect(() => {
|
|
29
31
|
void loadTree();
|
|
30
32
|
}, [loadTree]);
|
|
33
|
+
/** pi-web parity: re-fetch the root + every expanded directory. Bump
|
|
34
|
+
* `refreshTick` to trigger; cached entries are NOT reused, so newly
|
|
35
|
+
* created/deleted files appear without a manual refresh. */
|
|
36
|
+
const [refreshTick, setRefreshTick] = useState(0);
|
|
37
|
+
const refreshTree = useCallback(() => {
|
|
38
|
+
setRefreshTick((tick) => tick + 1);
|
|
39
|
+
}, []);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const dirs = ["", ...expandedDirsRef.current];
|
|
42
|
+
for (const dir of dirs)
|
|
43
|
+
void loadTree(dir);
|
|
44
|
+
}, [loadTree, refreshTick]);
|
|
45
|
+
// Also refresh when the tab regains focus: external tools (editor, git,
|
|
46
|
+
// CLI) may have changed the tree while the console was hidden.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const onVisibility = () => {
|
|
49
|
+
if (!document.hidden)
|
|
50
|
+
refreshTree();
|
|
51
|
+
};
|
|
52
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
53
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
54
|
+
}, [refreshTree]);
|
|
31
55
|
const openPreview = useCallback(async (file) => {
|
|
32
56
|
setPreviewLoading(true);
|
|
33
57
|
try {
|
|
@@ -116,6 +140,7 @@ export function useRepoBrowser(params) {
|
|
|
116
140
|
previewLoading,
|
|
117
141
|
setPreviews,
|
|
118
142
|
loadTree,
|
|
143
|
+
refreshTree,
|
|
119
144
|
openPreview,
|
|
120
145
|
closePreview,
|
|
121
146
|
toggleDir,
|