@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.11
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/CHANGELOG.md +379 -416
- package/dist/application/evaluation/budget.js +21 -0
- package/dist/application/evaluation/corpus-hash.js +10 -15
- package/dist/application/evaluation/corpus.js +2 -1
- package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
- package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
- package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
- package/dist/application/evaluation/frontend-pair-registration.js +143 -0
- package/dist/application/evaluation/frontend-paired-summary.js +150 -0
- package/dist/application/evaluation/frontend-run-observation.js +144 -0
- package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
- package/dist/application/evaluation/types.js +45 -12
- package/dist/application/task-lifecycle/observe.js +26 -1
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +11 -0
- package/dist/cli/program.js +4 -0
- package/dist/commands/task-advance.js +3 -5
- package/dist/commands/task-source-prepare.js +13 -1
- package/dist/executors/dag-pi/guards.js +4 -0
- package/dist/executors/dag-pi/model-config.js +30 -0
- package/dist/executors/dag-pi/plan/fact-adoption.js +55 -0
- package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +22 -28
- package/dist/executors/dag-pi/plan/finalize-tools.js +259 -0
- package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
- package/dist/executors/dag-pi/plan/record-tools.js +861 -0
- package/dist/executors/dag-pi/plan/schema.js +129 -0
- package/dist/executors/dag-pi/plan/session.js +1 -0
- package/dist/executors/dag-pi/plan/tool-helpers.js +16 -0
- package/dist/executors/dag-pi/sessions/index.js +6 -0
- package/dist/executors/dag-pi/sessions/plan-batches.js +280 -0
- package/dist/executors/dag-pi/sessions/plan-prompts.js +367 -0
- package/dist/executors/dag-pi/sessions/scout-parallel.js +197 -0
- package/dist/executors/dag-pi/sessions/segmented-plan.js +1184 -0
- package/dist/executors/dag-pi/sessions/writer-evidence.js +60 -0
- package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
- package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
- package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
- package/dist/executors/dag-pi-executor.js +733 -5782
- package/dist/executors/pi-executor.js +6 -1
- package/dist/executors/pi-sdk-executor.js +112 -19
- package/dist/executors/shell-executor.js +88 -12
- package/dist/executors/shell-presets.js +1 -1
- package/dist/executors/shell-write-guard.js +26 -3
- package/dist/infrastructure/console/operation-store.js +48 -14
- package/dist/infrastructure/evaluation/corpus-store.js +37 -12
- package/dist/shared/file-identity.js +23 -0
- package/dist/shared/git-workspace-status.js +482 -0
- package/dist/shared/operator/capabilities.js +10 -5
- package/dist/shared/path-safety.js +2 -2
- package/dist/shared/wizard-local-telemetry.js +51 -0
- package/dist/task/config-types.js +22 -8
- package/dist/task/contract/project.js +13 -0
- package/dist/task/contract/schema.js +2 -7
- package/dist/task/frontend-preflight.js +1 -1
- package/dist/task/source-prepare/parse-intent.js +124 -5
- package/dist/task/source-prepare/prepare.js +6 -1
- package/dist/task/source-prepare/semantic-intake.js +18 -3
- package/dist/task/source-prepare/source-execution.js +65 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
- package/dist/task/source-prepare/source-provider-budget.js +290 -0
- package/dist/worker/console/chat/assistant-content.js +5 -34
- package/dist/worker/console/chat/chat-event-store.js +62 -10
- package/dist/worker/console/chat/context-insights.js +723 -0
- package/dist/worker/console/chat/conversation-nodes.js +383 -0
- package/dist/worker/console/chat/db-connections.js +854 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
- package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
- package/dist/worker/console/chat/pi-runtime.js +445 -36
- package/dist/worker/console/chat/provider-error.js +0 -12
- package/dist/worker/console/chat/repo-browser.js +52 -4
- package/dist/worker/console/chat/routes.js +577 -72
- package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
- package/dist/worker/console/chat/session-store.js +147 -19
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/timeline-message-order.js +22 -0
- package/dist/worker/console/chat/tools.js +34 -0
- package/dist/worker/console/chat/turn-process.js +237 -66
- package/dist/worker/console/chat/user-turn-outline.js +19 -0
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/dag-confirmation.js +3 -0
- package/dist/worker/console/dag-execution-receipt.js +3 -0
- package/dist/worker/console/operator-actions.js +32 -3
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/prd-intake-bridge.js +5 -12
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/server.js +5 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
- package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
- package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
- package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
- package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
- package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
- package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
- package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
- package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-context.js +2 -0
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/dist/worker/observe/node-context-insights.js +479 -0
- package/dist/worker/observe/static/console-theme.css +174 -0
- package/dist/worker/observe/static/console-theme.js +36 -0
- package/dist/worker/observe/static/operator-chrome.css +85 -0
- package/dist/worker/observe/static/operator-chrome.js +51 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
- package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
- package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
- package/dist/workflows/dag/budget-enforcement.js +31 -2
- package/dist/workflows/dag/checkpoint.js +686 -0
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-contract-facts.js +11 -8
- package/dist/workflows/dag/frontend-design-policy.js +6 -6
- package/dist/workflows/dag/frontend-durable-tools.js +90 -70
- package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
- package/dist/workflows/dag/frontend-plan-canary.js +66 -16
- package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
- package/dist/workflows/dag/frontend-plan-progress.js +92 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
- package/dist/workflows/dag/frontend-recovery-run.js +51 -46
- package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
- package/dist/workflows/dag/frontend-review-context.js +29 -1
- package/dist/workflows/dag/frontend-review-scopes.js +92 -2
- package/dist/workflows/dag/frontend-risk.js +8 -3
- package/dist/workflows/dag/frontend-root-observation.js +261 -0
- package/dist/workflows/dag/frontend-session-budget.js +52 -39
- package/dist/workflows/dag/frontend-session-context.js +10 -0
- package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
- package/dist/workflows/dag/frontend-verification-trace.js +6 -0
- package/dist/workflows/dag/frontend-writer-admission.js +3 -1
- package/dist/workflows/dag/hybrid/sources.js +1812 -0
- package/dist/workflows/dag/hybrid/templates/backend-test.js +1807 -0
- package/dist/workflows/dag/hybrid/templates/frontend-test.js +702 -0
- package/dist/workflows/dag/hybrid/templates/frontend.js +1529 -0
- package/dist/workflows/dag/hybrid/templates/index.js +8 -0
- package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +449 -0
- package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +495 -0
- package/dist/workflows/dag/hybrid/templates/shared.js +101 -0
- package/dist/workflows/dag/hybrid/templates/standard.js +265 -0
- package/dist/workflows/dag/hybrid/types.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +57 -7106
- package/dist/workflows/dag/node-execution.js +11 -11
- package/dist/workflows/dag/output-protocol.js +1 -0
- package/dist/workflows/dag/rerun-feedback.js +35 -4
- package/dist/workflows/dag/rerun-task.js +62 -66
- package/dist/workflows/dag/runner.js +38 -1159
- package/dist/workflows/dag/terminal-status.js +502 -0
- package/dist/workflows/dag/types.js +15 -8
- package/dist/workflows/loop/benchmark.js +11 -8
- package/docs/README.md +1 -1
- package/docs/architecture/dag-execution.md +1 -1
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/governance/harness-methodology-verification.md +11 -0
- package/docs/templates/backend-test-dag.json +5 -4
- package/docs/templates/frontend-implementation-contract.schema.json +31 -11
- package/package.json +6 -4
- package/skills/frontend-design-review/SKILL.md +8 -11
- package/skills/frontend-design-review/references/review-checklist.md +3 -4
- package/skills/frontend-review/SKILL.md +5 -1
- package/skills/loop-agent/references/command-reference.md +8 -0
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
|
@@ -5,6 +5,8 @@ import { requestId } from "./format.js";
|
|
|
5
5
|
import { parseAssistantContent, } from "../../chat/assistant-content.js";
|
|
6
6
|
import { sortChatTimelineMessages, upsertCompactionMessage, } from "./compaction-message.js";
|
|
7
7
|
import { replacePendingUserMessage } from "./pending-user-message.js";
|
|
8
|
+
import { conversationNodeFromEvent, upsertConversationNode, } from "../../chat/conversation-nodes.js";
|
|
9
|
+
import { createAnimationFrameBatcher } from "./animation-frame-batcher.js";
|
|
8
10
|
/** Shared handler for a single parsed Chat SSE event (M1 reconnect).
|
|
9
11
|
*
|
|
10
12
|
* Used by BOTH the inline prompt POST SSE and the background reconnect
|
|
@@ -12,7 +14,7 @@ import { replacePendingUserMessage } from "./pending-user-message.js";
|
|
|
12
14
|
* original request would have. `assistantId` is the in-flight assistant
|
|
13
15
|
* message id to patch for streaming text; reconnect creates one if none. */
|
|
14
16
|
export function createChatSseEventApplier(deps) {
|
|
15
|
-
const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setDeferredTurns = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, onTelemetryRefresh, setMessages, setToolCalls, setPendingQuestion = () => undefined, setTodos = () => undefined, setGoal = () => undefined, getMessages, } = deps;
|
|
17
|
+
const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setDeferredTurns = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, onTelemetryRefresh, setMessages, setToolCalls, setPendingQuestion = () => undefined, setTodos = () => undefined, setGoal = () => undefined, setOperatorActions = () => undefined, setSddSessionIdentity = () => undefined, setConversationNodes = () => undefined, setTurnState = () => undefined, getMessages, } = deps;
|
|
16
18
|
let queueOwner;
|
|
17
19
|
let queueRevision = -1;
|
|
18
20
|
/** Retained-replay text per turn. Pi `message_update.text` is the cumulative
|
|
@@ -20,6 +22,36 @@ export function createChatSseEventApplier(deps) {
|
|
|
20
22
|
* turn matters and buffering cannot lose content. Flushed on `stream_ready`
|
|
21
23
|
* onto the turn's newest assistant row (2026-09-08). */
|
|
22
24
|
const replayTextByTurn = new Map();
|
|
25
|
+
const messageUpdateBatcher = createAnimationFrameBatcher({
|
|
26
|
+
schedule: (callback) => {
|
|
27
|
+
if (typeof window !== "undefined" && window.requestAnimationFrame) {
|
|
28
|
+
return window.requestAnimationFrame(callback);
|
|
29
|
+
}
|
|
30
|
+
return globalThis.setTimeout(callback, 16);
|
|
31
|
+
},
|
|
32
|
+
cancel: (handle) => {
|
|
33
|
+
if (typeof window !== "undefined" && window.cancelAnimationFrame) {
|
|
34
|
+
window.cancelAnimationFrame(handle);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
globalThis.clearTimeout(handle);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
publish: (values) => {
|
|
41
|
+
const sessionId = refs.sessionRef.current?.sessionId;
|
|
42
|
+
const generation = refs.sessionGenerationRef.current;
|
|
43
|
+
setMessages((messages) => {
|
|
44
|
+
let next = messages;
|
|
45
|
+
for (const item of values.values()) {
|
|
46
|
+
if (item.sessionId !== sessionId || item.generation !== generation)
|
|
47
|
+
continue;
|
|
48
|
+
next = item.apply(next);
|
|
49
|
+
}
|
|
50
|
+
return next;
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const useFrameBatching = typeof window !== "undefined" && typeof window.requestAnimationFrame === "function";
|
|
23
55
|
const flushReplayText = () => {
|
|
24
56
|
if (replayTextByTurn.size === 0)
|
|
25
57
|
return;
|
|
@@ -94,6 +126,9 @@ export function createChatSseEventApplier(deps) {
|
|
|
94
126
|
if (options?.generation !== undefined &&
|
|
95
127
|
refs.sessionGenerationRef.current !== options.generation)
|
|
96
128
|
return;
|
|
129
|
+
if (typeof data.sessionId === "string" &&
|
|
130
|
+
data.sessionId !== refs.sessionRef.current?.sessionId)
|
|
131
|
+
return;
|
|
97
132
|
const replay = options?.replay === true;
|
|
98
133
|
if (eventName === "stream_ready") {
|
|
99
134
|
// The retained replay is fully buffered: publish it now so a reconnect
|
|
@@ -101,6 +136,10 @@ export function createChatSseEventApplier(deps) {
|
|
|
101
136
|
flushReplayText();
|
|
102
137
|
return;
|
|
103
138
|
}
|
|
139
|
+
// Structural events define visible boundaries in the DSH timeline. Publish
|
|
140
|
+
// any coalesced text before applying them so rows never appear out of order.
|
|
141
|
+
if (eventName !== "message_update" || replay)
|
|
142
|
+
messageUpdateBatcher.flush();
|
|
104
143
|
if (eventName === "reconcile") {
|
|
105
144
|
// Cursor aged out of the server ring (or the turn changed under us):
|
|
106
145
|
// retained history may be incomplete. Surface recovery via the callback
|
|
@@ -173,6 +212,64 @@ export function createChatSseEventApplier(deps) {
|
|
|
173
212
|
const eventAt = typeof data.at === "string" && Number.isFinite(Date.parse(data.at))
|
|
174
213
|
? Date.parse(data.at)
|
|
175
214
|
: Date.now();
|
|
215
|
+
const nodeKinds = new Set([
|
|
216
|
+
"step_start",
|
|
217
|
+
"step_end",
|
|
218
|
+
"model_retry",
|
|
219
|
+
"turn_error",
|
|
220
|
+
"turn_max_tokens",
|
|
221
|
+
"request_context",
|
|
222
|
+
"command",
|
|
223
|
+
"unknown",
|
|
224
|
+
]);
|
|
225
|
+
if (nodeKinds.has(eventName) &&
|
|
226
|
+
turnId &&
|
|
227
|
+
eventSeq !== undefined &&
|
|
228
|
+
typeof data.eventId === "string") {
|
|
229
|
+
const node = conversationNodeFromEvent({
|
|
230
|
+
eventId: data.eventId,
|
|
231
|
+
kind: eventName,
|
|
232
|
+
seq: eventSeq,
|
|
233
|
+
turnId,
|
|
234
|
+
at: new Date(eventAt).toISOString(),
|
|
235
|
+
data: payload,
|
|
236
|
+
});
|
|
237
|
+
if (node)
|
|
238
|
+
setConversationNodes((current) => upsertConversationNode(current, node));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (eventName === "operator-action") {
|
|
242
|
+
const eventId = typeof data.eventId === "string" ? data.eventId : undefined;
|
|
243
|
+
if (!eventId ||
|
|
244
|
+
eventSeq === undefined ||
|
|
245
|
+
data.sessionId !== refs.sessionRef.current?.sessionId ||
|
|
246
|
+
payload.action !== "sdd-bingo" ||
|
|
247
|
+
typeof payload.skillName !== "string" ||
|
|
248
|
+
!payload.skillName.trim() ||
|
|
249
|
+
typeof payload.telemetrySessionId !== "string" ||
|
|
250
|
+
!payload.telemetrySessionId.trim())
|
|
251
|
+
return;
|
|
252
|
+
const action = {
|
|
253
|
+
eventId,
|
|
254
|
+
seq: eventSeq,
|
|
255
|
+
at: eventAt,
|
|
256
|
+
...(turnId ? { turnId } : {}),
|
|
257
|
+
action: "sdd-bingo",
|
|
258
|
+
skillName: payload.skillName.trim(),
|
|
259
|
+
telemetrySessionId: payload.telemetrySessionId.trim(),
|
|
260
|
+
};
|
|
261
|
+
setOperatorActions((current) => {
|
|
262
|
+
const existing = current.findIndex((item) => item.eventId === eventId);
|
|
263
|
+
const next = existing < 0
|
|
264
|
+
? [...current, action]
|
|
265
|
+
: current.map((item, index) => (index === existing ? action : item));
|
|
266
|
+
return next.sort((left, right) => left.seq - right.seq || left.at - right.at);
|
|
267
|
+
});
|
|
268
|
+
setSddSessionIdentity({
|
|
269
|
+
telemetrySessionId: action.telemetrySessionId,
|
|
270
|
+
});
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
176
273
|
if (eventName === "queue_update") {
|
|
177
274
|
const revision = typeof payload.revision === "number" ? payload.revision : -1;
|
|
178
275
|
const owner = refs.sessionRef.current?.sessionId;
|
|
@@ -228,6 +325,9 @@ export function createChatSseEventApplier(deps) {
|
|
|
228
325
|
: {}),
|
|
229
326
|
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
230
327
|
...(turnId ? { turnId } : {}),
|
|
328
|
+
...(Array.isArray(message.attachments)
|
|
329
|
+
? { attachments: message.attachments }
|
|
330
|
+
: {}),
|
|
231
331
|
};
|
|
232
332
|
// Any frame carrying the clientRequestId replaces the optimistic
|
|
233
333
|
// row in place — even after a /state reconcile already released
|
|
@@ -407,6 +507,8 @@ export function createChatSseEventApplier(deps) {
|
|
|
407
507
|
if (turnId) {
|
|
408
508
|
refs.settledTurnIdsRef.current.delete(turnId);
|
|
409
509
|
refs.activeTurnIdRef.current = turnId;
|
|
510
|
+
if (!replay)
|
|
511
|
+
setTurnState(turnId, "running");
|
|
410
512
|
}
|
|
411
513
|
if (refs.stopRequestedRef.current &&
|
|
412
514
|
(!turnId ||
|
|
@@ -425,13 +527,17 @@ export function createChatSseEventApplier(deps) {
|
|
|
425
527
|
return;
|
|
426
528
|
}
|
|
427
529
|
if (eventName === "agent_settled" || eventName === "agent_end") {
|
|
428
|
-
if (turnId)
|
|
429
|
-
refs.settledTurnIdsRef.current.add(turnId);
|
|
430
530
|
// `agent_end` means the SDK run ended, but the Console keeps the only
|
|
431
531
|
// GET stream through an idle grace for agent_settled/state reconciliation
|
|
432
532
|
// (M4.4). It must not prematurely close the visible turn.
|
|
433
533
|
if (eventName === "agent_end")
|
|
434
534
|
return;
|
|
535
|
+
if (turnId) {
|
|
536
|
+
refs.settledTurnIdsRef.current.add(turnId);
|
|
537
|
+
// Settlement proves closure but not its outcome. The stream owner starts
|
|
538
|
+
// an authoritative /state reconcile; do not guess settled here because
|
|
539
|
+
// abort/error closeout persists the exact state just after this event.
|
|
540
|
+
}
|
|
435
541
|
const pending = refs.pendingSubmissionRef.current;
|
|
436
542
|
if (pending && turnId && pending.acceptedTurnId === turnId) {
|
|
437
543
|
refs.releasedClientRequestIdsRef.current.add(pending.clientRequestId);
|
|
@@ -643,7 +749,7 @@ export function createChatSseEventApplier(deps) {
|
|
|
643
749
|
refs.sealedAssistantIdsRef.current.add(durableMessageId);
|
|
644
750
|
}
|
|
645
751
|
const assistantIdForEvent = resolvedAssistantId;
|
|
646
|
-
|
|
752
|
+
const applyMessageUpdate = (m) => {
|
|
647
753
|
// Rename a locally-generated row to the durable id in place so we
|
|
648
754
|
// never render a twin bubble for the same persistence.
|
|
649
755
|
const rows = durableMessageId
|
|
@@ -686,7 +792,20 @@ export function createChatSseEventApplier(deps) {
|
|
|
686
792
|
...(content ? { content } : {}),
|
|
687
793
|
},
|
|
688
794
|
]);
|
|
689
|
-
}
|
|
795
|
+
};
|
|
796
|
+
if (eventName === "message_update" && useFrameBatching) {
|
|
797
|
+
const sessionId = refs.sessionRef.current?.sessionId;
|
|
798
|
+
if (sessionId) {
|
|
799
|
+
messageUpdateBatcher.enqueue(`${sessionId}:${assistantIdForEvent}`, {
|
|
800
|
+
sessionId,
|
|
801
|
+
generation: refs.sessionGenerationRef.current,
|
|
802
|
+
apply: applyMessageUpdate,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
setMessages(applyMessageUpdate);
|
|
808
|
+
}
|
|
690
809
|
if (eventName === "message_end")
|
|
691
810
|
sealedIds.add(assistantIdForEvent);
|
|
692
811
|
}
|
|
@@ -696,6 +815,10 @@ export function createChatSseEventApplier(deps) {
|
|
|
696
815
|
: requestId("t");
|
|
697
816
|
const toolName = typeof payload.toolName === "string" ? payload.toolName : "tool";
|
|
698
817
|
const argsPreview = typeof payload.argsPreview === "string" ? payload.argsPreview : "{}";
|
|
818
|
+
const toolStep = typeof payload.step === "number" ? payload.step : undefined;
|
|
819
|
+
const parentToolCallId = typeof payload.parentToolCallId === "string" && payload.parentToolCallId
|
|
820
|
+
? payload.parentToolCallId
|
|
821
|
+
: undefined;
|
|
699
822
|
setToolCalls((prev) => {
|
|
700
823
|
if (prev.some((t) => t.id === id)) {
|
|
701
824
|
return prev.map((t) => {
|
|
@@ -707,6 +830,8 @@ export function createChatSseEventApplier(deps) {
|
|
|
707
830
|
status: "running",
|
|
708
831
|
turnId: turnId ?? t.turnId,
|
|
709
832
|
assistantMessageId: resolvedAssistantId,
|
|
833
|
+
...(toolStep === undefined ? {} : { step: toolStep }),
|
|
834
|
+
...(parentToolCallId ? { parentToolCallId } : {}),
|
|
710
835
|
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
711
836
|
};
|
|
712
837
|
});
|
|
@@ -722,6 +847,54 @@ export function createChatSseEventApplier(deps) {
|
|
|
722
847
|
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
723
848
|
turnId,
|
|
724
849
|
assistantMessageId: resolvedAssistantId,
|
|
850
|
+
...(toolStep === undefined ? {} : { step: toolStep }),
|
|
851
|
+
...(parentToolCallId ? { parentToolCallId } : {}),
|
|
852
|
+
},
|
|
853
|
+
];
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
else if (eventName === "tool_update") {
|
|
857
|
+
const id = typeof payload.toolCallId === "string"
|
|
858
|
+
? payload.toolCallId
|
|
859
|
+
: requestId("u");
|
|
860
|
+
const toolName = typeof payload.toolName === "string" ? payload.toolName : "tool";
|
|
861
|
+
const resultText = typeof payload.resultPreview === "string"
|
|
862
|
+
? payload.resultPreview
|
|
863
|
+
: "";
|
|
864
|
+
const toolStep = typeof payload.step === "number" ? payload.step : undefined;
|
|
865
|
+
const parentToolCallId = typeof payload.parentToolCallId === "string" && payload.parentToolCallId
|
|
866
|
+
? payload.parentToolCallId
|
|
867
|
+
: undefined;
|
|
868
|
+
setToolCalls((prev) => {
|
|
869
|
+
const exists = prev.some((tool) => tool.id === id);
|
|
870
|
+
if (exists) {
|
|
871
|
+
return prev.map((tool) => tool.id !== id
|
|
872
|
+
? tool
|
|
873
|
+
: {
|
|
874
|
+
...tool,
|
|
875
|
+
result: resultText,
|
|
876
|
+
resultText,
|
|
877
|
+
status: "running",
|
|
878
|
+
turnId: turnId ?? tool.turnId,
|
|
879
|
+
assistantMessageId: tool.assistantMessageId ?? resolvedAssistantId,
|
|
880
|
+
...(toolStep === undefined ? {} : { step: toolStep }),
|
|
881
|
+
...(parentToolCallId ? { parentToolCallId } : {}),
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
return [
|
|
885
|
+
...prev,
|
|
886
|
+
{
|
|
887
|
+
id,
|
|
888
|
+
toolName,
|
|
889
|
+
result: resultText,
|
|
890
|
+
resultText,
|
|
891
|
+
status: "running",
|
|
892
|
+
createdAt: eventAt,
|
|
893
|
+
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
894
|
+
turnId,
|
|
895
|
+
assistantMessageId: resolvedAssistantId,
|
|
896
|
+
...(toolStep === undefined ? {} : { step: toolStep }),
|
|
897
|
+
...(parentToolCallId ? { parentToolCallId } : {}),
|
|
725
898
|
},
|
|
726
899
|
];
|
|
727
900
|
});
|
|
@@ -735,6 +908,10 @@ export function createChatSseEventApplier(deps) {
|
|
|
735
908
|
const resultText = typeof payload.resultPreview === "string"
|
|
736
909
|
? payload.resultPreview
|
|
737
910
|
: "{}";
|
|
911
|
+
const toolStep = typeof payload.step === "number" ? payload.step : undefined;
|
|
912
|
+
const parentToolCallId = typeof payload.parentToolCallId === "string" && payload.parentToolCallId
|
|
913
|
+
? payload.parentToolCallId
|
|
914
|
+
: undefined;
|
|
738
915
|
// Plan A (2026-08-16): the server carries the accepted-operation link on
|
|
739
916
|
// the frame; persist it on the tool entry so the semantic event resolves
|
|
740
917
|
// an operation:<id> ref (mirrors the hydrated path backfill).
|
|
@@ -756,6 +933,8 @@ export function createChatSseEventApplier(deps) {
|
|
|
756
933
|
...(eventSeq === undefined ? {} : { finishedSeq: eventSeq }),
|
|
757
934
|
turnId: turnId ?? t.turnId,
|
|
758
935
|
assistantMessageId: t.assistantMessageId ?? resolvedAssistantId,
|
|
936
|
+
...(toolStep === undefined ? {} : { step: toolStep }),
|
|
937
|
+
...(parentToolCallId ? { parentToolCallId } : {}),
|
|
759
938
|
...(resultOperationId
|
|
760
939
|
? { operationId: resultOperationId }
|
|
761
940
|
: t.operationId
|
|
@@ -779,6 +958,8 @@ export function createChatSseEventApplier(deps) {
|
|
|
779
958
|
: { eventSeq, finishedSeq: eventSeq }),
|
|
780
959
|
turnId,
|
|
781
960
|
assistantMessageId: resolvedAssistantId,
|
|
961
|
+
...(toolStep === undefined ? {} : { step: toolStep }),
|
|
962
|
+
...(parentToolCallId ? { parentToolCallId } : {}),
|
|
782
963
|
...(resultOperationId ? { operationId: resultOperationId } : {}),
|
|
783
964
|
},
|
|
784
965
|
];
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Parse the persisted Chat SSE cursor shape `<epoch>:<seq>`. */
|
|
2
|
+
export function parseChatEventCursor(eventId) {
|
|
3
|
+
const raw = eventId?.trim();
|
|
4
|
+
if (!raw)
|
|
5
|
+
return null;
|
|
6
|
+
const separator = raw.lastIndexOf(":");
|
|
7
|
+
if (separator <= 0)
|
|
8
|
+
return null;
|
|
9
|
+
const epoch = raw.slice(0, separator);
|
|
10
|
+
const seqPart = raw.slice(separator + 1);
|
|
11
|
+
if (!/^\d+$/.test(seqPart))
|
|
12
|
+
return null;
|
|
13
|
+
const seq = Number(seqPart);
|
|
14
|
+
if (!Number.isSafeInteger(seq) || seq < 0)
|
|
15
|
+
return null;
|
|
16
|
+
return { epoch, seq };
|
|
17
|
+
}
|
|
18
|
+
/** Keep a cursor monotonic within one epoch; a valid new epoch replaces it. */
|
|
19
|
+
export function selectLaterChatEventId(currentEventId, candidateEventId) {
|
|
20
|
+
const candidate = parseChatEventCursor(candidateEventId);
|
|
21
|
+
if (!candidate)
|
|
22
|
+
return currentEventId ?? null;
|
|
23
|
+
const current = parseChatEventCursor(currentEventId);
|
|
24
|
+
if (!current ||
|
|
25
|
+
current.epoch !== candidate.epoch ||
|
|
26
|
+
candidate.seq > current.seq) {
|
|
27
|
+
return candidateEventId.trim();
|
|
28
|
+
}
|
|
29
|
+
return currentEventId.trim();
|
|
30
|
+
}
|
|
31
|
+
function sseBlockEventId(block) {
|
|
32
|
+
for (const rawLine of block.split("\n")) {
|
|
33
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
34
|
+
if (!line.startsWith("id:"))
|
|
35
|
+
continue;
|
|
36
|
+
return line.slice(3).trimStart().trimEnd() || null;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A repair buffer belongs to the event-log generation that produced it. If the
|
|
42
|
+
* reconnect crossed a Console restart, replay from the replacement epoch is
|
|
43
|
+
* authoritative and old buffered frames must not be applied or restore the old
|
|
44
|
+
* Last-Event-ID. Control/comment frames are not persisted repair facts.
|
|
45
|
+
*/
|
|
46
|
+
export function selectChatSseRepairBlocks(blocks, readyCursor) {
|
|
47
|
+
const ready = parseChatEventCursor(readyCursor);
|
|
48
|
+
if (!ready)
|
|
49
|
+
return [];
|
|
50
|
+
return blocks.filter((block) => {
|
|
51
|
+
const buffered = parseChatEventCursor(sseBlockEventId(block));
|
|
52
|
+
return buffered?.epoch === ready.epoch;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Read the dense event-store watermark that starts the live follow phase. */
|
|
56
|
+
export function readChatStreamReadyCursor(block) {
|
|
57
|
+
let eventName = "message";
|
|
58
|
+
let dataText = "";
|
|
59
|
+
for (const rawLine of block.split("\n")) {
|
|
60
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
61
|
+
if (line.startsWith("event:"))
|
|
62
|
+
eventName = line.slice(6).trim();
|
|
63
|
+
else if (line.startsWith("data:"))
|
|
64
|
+
dataText += line.slice(5).trimStart();
|
|
65
|
+
}
|
|
66
|
+
if (eventName !== "stream_ready" || !dataText)
|
|
67
|
+
return null;
|
|
68
|
+
try {
|
|
69
|
+
const data = JSON.parse(dataText);
|
|
70
|
+
if (typeof data.epoch !== "string" ||
|
|
71
|
+
!data.epoch ||
|
|
72
|
+
typeof data.lastSeq !== "number" ||
|
|
73
|
+
!Number.isSafeInteger(data.lastSeq) ||
|
|
74
|
+
data.lastSeq < 0)
|
|
75
|
+
return null;
|
|
76
|
+
return `${data.epoch}:${data.lastSeq}`;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Detect a dropped live persisted frame before it can advance the replay cursor.
|
|
84
|
+
* Epoch changes remain server-owned because an epoch_mismatch reconcile frame
|
|
85
|
+
* is followed by snapshot events from the replacement epoch.
|
|
86
|
+
*/
|
|
87
|
+
export function inspectChatSseContinuity(block, lastEventId) {
|
|
88
|
+
const current = parseChatEventCursor(lastEventId);
|
|
89
|
+
const incoming = parseChatEventCursor(sseBlockEventId(block));
|
|
90
|
+
if (!current || !incoming || current.epoch !== incoming.epoch) {
|
|
91
|
+
return { action: "apply" };
|
|
92
|
+
}
|
|
93
|
+
if (incoming.seq > current.seq + 1) {
|
|
94
|
+
return {
|
|
95
|
+
action: "reconnect",
|
|
96
|
+
expectedSeq: current.seq + 1,
|
|
97
|
+
receivedSeq: incoming.seq,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { action: "apply" };
|
|
101
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ensureLinkedSubmissionOrder } from "../../chat/timeline-message-order.js";
|
|
2
2
|
function finiteToken(value) {
|
|
3
3
|
return typeof value === "number" && Number.isFinite(value)
|
|
4
4
|
? Math.max(0, Math.floor(value))
|
|
@@ -59,9 +59,24 @@ export function compactionMessageFromEvent(item) {
|
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
/** Stable chronological order: server sequence first, legacy time fallback. */
|
|
62
|
+
/** Stable chronological order: server sequence first, legacy time/id fallback. */
|
|
63
63
|
export function sortChatTimelineMessages(messages) {
|
|
64
|
-
return [...messages].sort(
|
|
64
|
+
return ensureLinkedSubmissionOrder([...messages].sort((a, b) => {
|
|
65
|
+
if (a.eventSeq !== undefined &&
|
|
66
|
+
b.eventSeq !== undefined &&
|
|
67
|
+
a.eventSeq !== b.eventSeq) {
|
|
68
|
+
return a.eventSeq - b.eventSeq;
|
|
69
|
+
}
|
|
70
|
+
if (a.createdAt !== b.createdAt)
|
|
71
|
+
return a.createdAt - b.createdAt;
|
|
72
|
+
if (a.eventSeq !== undefined && b.eventSeq === undefined)
|
|
73
|
+
return 1;
|
|
74
|
+
if (a.eventSeq === undefined && b.eventSeq !== undefined)
|
|
75
|
+
return -1;
|
|
76
|
+
// Modern JS sort is stable; keep insertion order for indistinguishable
|
|
77
|
+
// legacy rows instead of inventing chronology from random ids.
|
|
78
|
+
return 0;
|
|
79
|
+
}));
|
|
65
80
|
}
|
|
66
81
|
export function upsertCompactionMessage(messages, item) {
|
|
67
82
|
const next = compactionMessageFromEvent(item);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator Chat — context insights store (dsh-context parity).
|
|
3
|
+
*
|
|
4
|
+
* Module-level external store so every surface that needs the dashboard
|
|
5
|
+
* (Details-rail context tab, /context overlay) shares one fetch lifecycle
|
|
6
|
+
* keyed by sessionId. Read-only GET; no mutation gate needed.
|
|
7
|
+
*/
|
|
8
|
+
import { useSyncExternalStore } from "react";
|
|
9
|
+
let state = { sessionId: null, loading: false };
|
|
10
|
+
const listeners = new Set();
|
|
11
|
+
function emit() {
|
|
12
|
+
for (const listener of listeners)
|
|
13
|
+
listener();
|
|
14
|
+
}
|
|
15
|
+
function setState(next) {
|
|
16
|
+
state = next;
|
|
17
|
+
emit();
|
|
18
|
+
}
|
|
19
|
+
/** Attach the store to a session; null detaches and clears. */
|
|
20
|
+
export function attachContextInsights(sessionId) {
|
|
21
|
+
if (state.sessionId === sessionId)
|
|
22
|
+
return;
|
|
23
|
+
setState({ sessionId, loading: Boolean(sessionId) });
|
|
24
|
+
if (!sessionId)
|
|
25
|
+
return;
|
|
26
|
+
void refreshContextInsights();
|
|
27
|
+
}
|
|
28
|
+
/** Force a refetch (spatial overlay open, manual refresh button). */
|
|
29
|
+
export async function refreshContextInsights() {
|
|
30
|
+
const sessionId = state.sessionId;
|
|
31
|
+
if (!sessionId)
|
|
32
|
+
return;
|
|
33
|
+
setState({ ...state, loading: true });
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(`/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/context-insights`, { credentials: "include" });
|
|
36
|
+
const body = (await res.json().catch(() => null));
|
|
37
|
+
if (state.sessionId !== sessionId)
|
|
38
|
+
return; // stale reply
|
|
39
|
+
if (!res.ok || !body?.insights) {
|
|
40
|
+
setState({
|
|
41
|
+
sessionId,
|
|
42
|
+
loading: false,
|
|
43
|
+
error: body?.error?.message ?? `加载上下文洞察失败(${res.status})`,
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
setState({ sessionId, loading: false, insights: body.insights });
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (state.sessionId !== sessionId)
|
|
51
|
+
return;
|
|
52
|
+
setState({
|
|
53
|
+
sessionId,
|
|
54
|
+
loading: false,
|
|
55
|
+
error: error instanceof Error ? error.message : String(error),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function useContextInsights() {
|
|
60
|
+
return useSyncExternalStore((onStoreChange) => {
|
|
61
|
+
listeners.add(onStoreChange);
|
|
62
|
+
return () => listeners.delete(onStoreChange);
|
|
63
|
+
}, () => state, () => state);
|
|
64
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export const DETAILS_SURFACE_ORDER = [
|
|
2
|
+
"repo",
|
|
3
|
+
"session",
|
|
4
|
+
"bench",
|
|
5
|
+
];
|
|
6
|
+
export const DETAILS_SURFACES = {
|
|
7
|
+
repo: {
|
|
8
|
+
id: "repo",
|
|
9
|
+
label: "仓库",
|
|
10
|
+
description: "当前仓库的文件与 Git",
|
|
11
|
+
pages: ["files", "scm"],
|
|
12
|
+
},
|
|
13
|
+
session: {
|
|
14
|
+
id: "session",
|
|
15
|
+
label: "会话",
|
|
16
|
+
description: "这次对话的注入与检查",
|
|
17
|
+
pages: [
|
|
18
|
+
"context",
|
|
19
|
+
"insights",
|
|
20
|
+
"calls",
|
|
21
|
+
"activity",
|
|
22
|
+
"trajectory",
|
|
23
|
+
"goal",
|
|
24
|
+
"dag",
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
bench: {
|
|
28
|
+
id: "bench",
|
|
29
|
+
label: "工作台",
|
|
30
|
+
description: "终端、预览、数据库",
|
|
31
|
+
pages: ["terminals", "browser", "databases"],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const TAB_TO_SURFACE = {
|
|
35
|
+
files: "repo",
|
|
36
|
+
scm: "repo",
|
|
37
|
+
context: "session",
|
|
38
|
+
insights: "session",
|
|
39
|
+
calls: "session",
|
|
40
|
+
activity: "session",
|
|
41
|
+
trajectory: "session",
|
|
42
|
+
goal: "session",
|
|
43
|
+
dag: "session",
|
|
44
|
+
terminals: "bench",
|
|
45
|
+
browser: "bench",
|
|
46
|
+
databases: "bench",
|
|
47
|
+
};
|
|
48
|
+
export const DEFAULT_PAGE_BY_SURFACE = {
|
|
49
|
+
repo: "files",
|
|
50
|
+
session: "context",
|
|
51
|
+
bench: "terminals",
|
|
52
|
+
};
|
|
53
|
+
export function surfaceForTab(tab) {
|
|
54
|
+
return TAB_TO_SURFACE[tab];
|
|
55
|
+
}
|
|
56
|
+
export function pagesOnSurface(surface, available) {
|
|
57
|
+
const allowed = new Set(DETAILS_SURFACES[surface].pages);
|
|
58
|
+
return available.filter((tab) => allowed.has(tab));
|
|
59
|
+
}
|
|
60
|
+
export function surfacesWithPages(available) {
|
|
61
|
+
return DETAILS_SURFACE_ORDER.filter((surface) => pagesOnSurface(surface, available).length > 0);
|
|
62
|
+
}
|
|
63
|
+
export function restorePageOnSurface(surface, available, last) {
|
|
64
|
+
const pages = pagesOnSurface(surface, available);
|
|
65
|
+
if (pages.length === 0)
|
|
66
|
+
return null;
|
|
67
|
+
if (last && pages.includes(last))
|
|
68
|
+
return last;
|
|
69
|
+
return pages[0] ?? null;
|
|
70
|
+
}
|
|
71
|
+
export function adjacentSurface(current, availableSurfaces, delta) {
|
|
72
|
+
if (availableSurfaces.length < 2)
|
|
73
|
+
return null;
|
|
74
|
+
const index = availableSurfaces.indexOf(current);
|
|
75
|
+
if (index < 0)
|
|
76
|
+
return availableSurfaces[0] ?? null;
|
|
77
|
+
return availableSurfaces[(index + delta + availableSurfaces.length) % availableSurfaces.length] ?? null;
|
|
78
|
+
}
|
|
79
|
+
export function adjacentPage(current, pages, delta) {
|
|
80
|
+
if (pages.length < 2)
|
|
81
|
+
return null;
|
|
82
|
+
const index = pages.indexOf(current);
|
|
83
|
+
if (index < 0)
|
|
84
|
+
return pages[0] ?? null;
|
|
85
|
+
return pages[(index + delta + pages.length) % pages.length] ?? null;
|
|
86
|
+
}
|
|
87
|
+
export function temporaryDetailsTab(id) {
|
|
88
|
+
switch (id) {
|
|
89
|
+
case "context":
|
|
90
|
+
case "insights":
|
|
91
|
+
case "calls":
|
|
92
|
+
case "activity":
|
|
93
|
+
case "goal":
|
|
94
|
+
return id;
|
|
95
|
+
default:
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|