@tea-agent/loop-agent 0.43.0 → 0.44.0-next.10
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 +408 -367
- 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/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 +398 -3384
- 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/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/init-hybrid.js +108 -37
- 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 +58 -12
- 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/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
|
@@ -8,6 +8,8 @@ import { mergePendingSubmissionOnHydration } from "./pending-user-message.js";
|
|
|
8
8
|
import { taskContextUnchanged } from "./format.js";
|
|
9
9
|
import { upsertCompactionMessage } from "./compaction-message.js";
|
|
10
10
|
import { sameTurnGroupInputs } from "./turn-group-equality.js";
|
|
11
|
+
import { conversationNodesFromEvents, } from "../../chat/conversation-nodes.js";
|
|
12
|
+
import { mergeTurnStateSummaries, setProjectedTurnState, } from "./turn-state-projection.js";
|
|
11
13
|
const EMPTY_USAGE = {
|
|
12
14
|
inputTokens: 0,
|
|
13
15
|
outputTokens: 0,
|
|
@@ -140,6 +142,14 @@ export function useChatThread(params) {
|
|
|
140
142
|
const [usage, setUsage] = useState(EMPTY_USAGE);
|
|
141
143
|
const [sessionStats, setSessionStats] = useState(EMPTY_SESSION_STATS);
|
|
142
144
|
const [turnTelemetry, setTurnTelemetry] = useState([]);
|
|
145
|
+
const [userTurnOutline, setUserTurnOutline] = useState([]);
|
|
146
|
+
const [operatorActions, setOperatorActions] = useState([]);
|
|
147
|
+
const [conversationNodes, setConversationNodes] = useState([]);
|
|
148
|
+
const [turnStatesById, setTurnStatesById] = useState({});
|
|
149
|
+
const reconcileTurnStates = useCallback((summaries) => {
|
|
150
|
+
setTurnStatesById((current) => mergeTurnStateSummaries(current, summaries));
|
|
151
|
+
}, []);
|
|
152
|
+
const [sddSessionIdentity, setSddSessionIdentity] = useState(null);
|
|
143
153
|
const [streaming, setStreaming] = useState(false);
|
|
144
154
|
const [turnPhase, setTurnPhase] = useState("idle");
|
|
145
155
|
const [deferredTurn, setDeferredTurn] = useState(null);
|
|
@@ -167,11 +177,13 @@ export function useChatThread(params) {
|
|
|
167
177
|
if (body.usage)
|
|
168
178
|
setUsage(body.usage);
|
|
169
179
|
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
180
|
+
setSddSessionIdentity(body.sddDataAlignment ?? null);
|
|
181
|
+
reconcileTurnStates(body.session?.turnStates);
|
|
170
182
|
}
|
|
171
183
|
catch {
|
|
172
184
|
// Telemetry is supplementary; the normal event stream remains authoritative.
|
|
173
185
|
}
|
|
174
|
-
}, [origin, refs]);
|
|
186
|
+
}, [origin, refs, reconcileTurnStates]);
|
|
175
187
|
// All inputs are stable (state setters + the memoized refs bag), so the
|
|
176
188
|
// applier identity is stable across renders.
|
|
177
189
|
const applyChatSseEvent = useMemo(() => createChatSseEventApplier({
|
|
@@ -188,6 +200,10 @@ export function useChatThread(params) {
|
|
|
188
200
|
setPendingQuestion,
|
|
189
201
|
setTodos,
|
|
190
202
|
setGoal,
|
|
203
|
+
setOperatorActions,
|
|
204
|
+
setConversationNodes,
|
|
205
|
+
setTurnState: (turnId, state) => setTurnStatesById((current) => setProjectedTurnState(current, turnId, state)),
|
|
206
|
+
setSddSessionIdentity,
|
|
191
207
|
setStreamingAssistantId,
|
|
192
208
|
setUnread,
|
|
193
209
|
setOperationCards,
|
|
@@ -325,6 +341,8 @@ export function useChatThread(params) {
|
|
|
325
341
|
toolCalls,
|
|
326
342
|
streaming,
|
|
327
343
|
activeAssistantId: streamingAssistantId,
|
|
344
|
+
conversationNodes,
|
|
345
|
+
turnStatesById,
|
|
328
346
|
});
|
|
329
347
|
// Preserve completed group objects across streaming tokens. Stable
|
|
330
348
|
// historical groups keep their references so
|
|
@@ -343,11 +361,13 @@ export function useChatThread(params) {
|
|
|
343
361
|
toolCalls,
|
|
344
362
|
streaming,
|
|
345
363
|
streamingAssistantId,
|
|
364
|
+
conversationNodes,
|
|
365
|
+
turnStatesById,
|
|
346
366
|
historyLimit,
|
|
347
367
|
]);
|
|
348
|
-
const
|
|
368
|
+
const loadHistoryPages = useCallback(async (input) => {
|
|
349
369
|
const targetSessionId = refs.sessionRef.current?.sessionId;
|
|
350
|
-
|
|
370
|
+
let beforeId = historyNextCursor;
|
|
351
371
|
if (!targetSessionId || !historyHasMore || !beforeId || historyLoadingRef.current)
|
|
352
372
|
return false;
|
|
353
373
|
const generation = refs.sessionGenerationRef.current;
|
|
@@ -355,17 +375,29 @@ export function useChatThread(params) {
|
|
|
355
375
|
historyLoadingRef.current = request;
|
|
356
376
|
setHistoryLoading(true);
|
|
357
377
|
try {
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
378
|
+
const collectedMessages = [];
|
|
379
|
+
const collectedTurnStates = [];
|
|
380
|
+
let hasMore = historyHasMore;
|
|
381
|
+
let found = false;
|
|
382
|
+
do {
|
|
383
|
+
const query = new URLSearchParams({ limit: "50", beforeId });
|
|
384
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(targetSessionId)}/messages?${query.toString()}`, { credentials: "include" });
|
|
385
|
+
if (!res.ok || refs.sessionRef.current?.sessionId !== targetSessionId)
|
|
386
|
+
return false;
|
|
387
|
+
const body = (await res.json());
|
|
388
|
+
if (refs.sessionRef.current?.sessionId !== targetSessionId ||
|
|
389
|
+
refs.sessionGenerationRef.current !== generation ||
|
|
390
|
+
historyLoadingRef.current !== request)
|
|
391
|
+
return false;
|
|
392
|
+
const pageMessages = body.messages ?? [];
|
|
393
|
+
collectedMessages.unshift(...pageMessages);
|
|
394
|
+
collectedTurnStates.push(...(body.turnStates ?? []));
|
|
395
|
+
found = Boolean(input?.untilMessageId && pageMessages.some((message) => message.id === input.untilMessageId));
|
|
396
|
+
hasMore = body.hasMore === true;
|
|
397
|
+
beforeId = body.nextCursor ?? "";
|
|
398
|
+
} while (input?.untilMessageId && !found && hasMore && beforeId);
|
|
399
|
+
const page = hydrateSessionThread(collectedMessages);
|
|
400
|
+
input?.beforePrepend?.();
|
|
369
401
|
setMessages((current) => {
|
|
370
402
|
const seen = new Set();
|
|
371
403
|
const merged = [...page.messages, ...current].filter((message) => {
|
|
@@ -382,10 +414,11 @@ export function useChatThread(params) {
|
|
|
382
414
|
byId.set(tool.id, tool);
|
|
383
415
|
return [...byId.values()].sort((a, b) => a.createdAt - b.createdAt);
|
|
384
416
|
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
417
|
+
setTurnStatesById((current) => mergeTurnStateSummaries(current, collectedTurnStates));
|
|
418
|
+
setHistoryHasMore(hasMore);
|
|
419
|
+
setHistoryNextCursor(beforeId || null);
|
|
420
|
+
setHistoryLimit((value) => value + collectedMessages.length);
|
|
421
|
+
return input?.untilMessageId ? found : true;
|
|
389
422
|
}
|
|
390
423
|
catch (error) {
|
|
391
424
|
if (refs.sessionRef.current?.sessionId === targetSessionId && refs.sessionGenerationRef.current === generation) {
|
|
@@ -400,6 +433,10 @@ export function useChatThread(params) {
|
|
|
400
433
|
}
|
|
401
434
|
}
|
|
402
435
|
}, [historyHasMore, historyNextCursor, origin, refs, setError]);
|
|
436
|
+
const loadEarlierMessages = useCallback((beforePrepend) => loadHistoryPages({ beforePrepend }), [loadHistoryPages]);
|
|
437
|
+
const loadThroughMessage = useCallback((messageId) => messagesRef.current.some((message) => message.id === messageId)
|
|
438
|
+
? Promise.resolve(true)
|
|
439
|
+
: loadHistoryPages({ untilMessageId: messageId }), [loadHistoryPages]);
|
|
403
440
|
const visibleMessages = useMemo(() => {
|
|
404
441
|
const next = [];
|
|
405
442
|
for (const group of turnGroups) {
|
|
@@ -421,6 +458,11 @@ export function useChatThread(params) {
|
|
|
421
458
|
setUsage(EMPTY_USAGE);
|
|
422
459
|
setSessionStats(EMPTY_SESSION_STATS);
|
|
423
460
|
setTurnTelemetry([]);
|
|
461
|
+
setUserTurnOutline([]);
|
|
462
|
+
setOperatorActions([]);
|
|
463
|
+
setConversationNodes([]);
|
|
464
|
+
setTurnStatesById({});
|
|
465
|
+
setSddSessionIdentity(null);
|
|
424
466
|
setStreaming(false);
|
|
425
467
|
setTurnPhase("idle");
|
|
426
468
|
setDeferredTurn(null);
|
|
@@ -469,6 +511,8 @@ export function useChatThread(params) {
|
|
|
469
511
|
}
|
|
470
512
|
: undefined));
|
|
471
513
|
setToolCalls(next.toolCalls);
|
|
514
|
+
setConversationNodes(conversationNodesFromEvents(next.conversationEvents ?? []));
|
|
515
|
+
setTurnStatesById(mergeTurnStateSummaries({}, next.turnStates));
|
|
472
516
|
setOperationCards([]);
|
|
473
517
|
setHumanGateCards([]);
|
|
474
518
|
setArtifactCards([]);
|
|
@@ -477,6 +521,9 @@ export function useChatThread(params) {
|
|
|
477
521
|
setUsage(next.usage ?? EMPTY_USAGE);
|
|
478
522
|
setSessionStats(next.sessionStats ?? EMPTY_SESSION_STATS);
|
|
479
523
|
setTurnTelemetry(next.turnTelemetry ?? []);
|
|
524
|
+
setUserTurnOutline(next.userTurnOutline ?? []);
|
|
525
|
+
setOperatorActions([]);
|
|
526
|
+
setSddSessionIdentity(next.sddSessionIdentity ?? null);
|
|
480
527
|
refs.usageResponseKeysRef.current = new Set();
|
|
481
528
|
setStreaming(visuallyStopped ? false : next.streaming);
|
|
482
529
|
setTurnPhase(visuallyStopped
|
|
@@ -500,13 +547,15 @@ export function useChatThread(params) {
|
|
|
500
547
|
* operation/human-gate/artifact/interview cards, which the reconnected tail
|
|
501
548
|
* stream will not replay (they live in the ring, already consumed). */
|
|
502
549
|
const recoverSession = useCallback(async (targetSessionId) => {
|
|
550
|
+
const targetGeneration = refs.sessionGenerationRef.current;
|
|
503
551
|
try {
|
|
504
552
|
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(targetSessionId)}`, { credentials: "include" });
|
|
505
553
|
if (!res.ok)
|
|
506
554
|
return false;
|
|
507
555
|
const body = (await res.json());
|
|
508
556
|
if (!body.session ||
|
|
509
|
-
refs.sessionRef.current?.sessionId !== targetSessionId
|
|
557
|
+
refs.sessionRef.current?.sessionId !== targetSessionId ||
|
|
558
|
+
refs.sessionGenerationRef.current !== targetGeneration)
|
|
510
559
|
return false;
|
|
511
560
|
const activeTurnId = body.session.activeTurnId ?? null;
|
|
512
561
|
const visuallyStopped = body.session.turnPhase === "stopping";
|
|
@@ -526,6 +575,8 @@ export function useChatThread(params) {
|
|
|
526
575
|
if (body.usage)
|
|
527
576
|
setUsage(body.usage);
|
|
528
577
|
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
578
|
+
setUserTurnOutline(body.userTurnOutline ?? []);
|
|
579
|
+
setSddSessionIdentity(body.sddDataAlignment ?? null);
|
|
529
580
|
// Resume the events stream from the snapshot tail so the next
|
|
530
581
|
// reconnect never immediately re-expires (AC-003). The refs below
|
|
531
582
|
// are reset so any follow-up tail replay re-applies cleanly.
|
|
@@ -534,7 +585,15 @@ export function useChatThread(params) {
|
|
|
534
585
|
refs.turnAssistantIdsRef.current = new Map();
|
|
535
586
|
refs.sealedAssistantIdsRef.current = new Set();
|
|
536
587
|
const hydrated = hydrateSessionThread(body.session.messages ?? []);
|
|
537
|
-
|
|
588
|
+
setConversationNodes(conversationNodesFromEvents(body.conversationEvents ?? []));
|
|
589
|
+
setTurnStatesById((current) => mergeTurnStateSummaries(current, body.session?.turnStates));
|
|
590
|
+
// Recovery is addressed to targetSessionId, so an optimistic
|
|
591
|
+
// submission from another session must never be projected into
|
|
592
|
+
// this snapshot (the activation path enforces the same owner gate).
|
|
593
|
+
const pending = refs.pendingSubmissionRef.current?.sessionId === targetSessionId &&
|
|
594
|
+
refs.pendingSubmissionRef.current.sessionGeneration === targetGeneration
|
|
595
|
+
? refs.pendingSubmissionRef.current
|
|
596
|
+
: undefined;
|
|
538
597
|
const recovered = mergePendingSubmissionOnHydration(hydrated.messages, pending
|
|
539
598
|
? {
|
|
540
599
|
clientRequestId: pending.clientRequestId,
|
|
@@ -590,6 +649,7 @@ export function useChatThread(params) {
|
|
|
590
649
|
historyNextCursor,
|
|
591
650
|
historyLoading,
|
|
592
651
|
loadEarlierMessages,
|
|
652
|
+
loadThroughMessage,
|
|
593
653
|
unread,
|
|
594
654
|
humanGateCards,
|
|
595
655
|
interview,
|
|
@@ -599,6 +659,13 @@ export function useChatThread(params) {
|
|
|
599
659
|
usage,
|
|
600
660
|
sessionStats,
|
|
601
661
|
turnTelemetry,
|
|
662
|
+
userTurnOutline,
|
|
663
|
+
operatorActions,
|
|
664
|
+
conversationNodes,
|
|
665
|
+
turnStatesById,
|
|
666
|
+
reconcileTurnStates,
|
|
667
|
+
sddSessionIdentity,
|
|
668
|
+
setSddSessionIdentity,
|
|
602
669
|
streaming,
|
|
603
670
|
setStreaming,
|
|
604
671
|
turnPhase,
|
|
@@ -621,6 +688,7 @@ export function useChatThread(params) {
|
|
|
621
688
|
applySseBlock,
|
|
622
689
|
loadTaskContext,
|
|
623
690
|
applyOperationLinkMutation,
|
|
691
|
+
refreshSessionTelemetry,
|
|
624
692
|
resetForNewSession,
|
|
625
693
|
applyActivatedSession,
|
|
626
694
|
recoverSession,
|
|
@@ -43,6 +43,9 @@ export function useComposer(params) {
|
|
|
43
43
|
const input = activeSnapshot.draft;
|
|
44
44
|
const pendingImages = activeSnapshot.images;
|
|
45
45
|
const [fileOptions, setFileOptions] = useState([]);
|
|
46
|
+
const [fileIndex, setFileIndex] = useState(0);
|
|
47
|
+
const [fileDrilled, setFileDrilled] = useState(false);
|
|
48
|
+
const [fileDismissedQuery, setFileDismissedQuery] = useState(null);
|
|
46
49
|
const [shortcutOpen, setShortcutOpen] = useState(false);
|
|
47
50
|
/** Any composer text that starts with `/` should reveal the palette,
|
|
48
51
|
* including draft restore. Escape can still close it without clearing. */
|
|
@@ -123,6 +126,10 @@ export function useComposer(params) {
|
|
|
123
126
|
forceRender();
|
|
124
127
|
setShortcutOpen(false);
|
|
125
128
|
setShortcutIndex(0);
|
|
129
|
+
setFileOptions([]);
|
|
130
|
+
setFileDrilled(false);
|
|
131
|
+
setFileDismissedQuery(null);
|
|
132
|
+
setFileIndex(0);
|
|
126
133
|
return attempt;
|
|
127
134
|
}, [ledger, refs]);
|
|
128
135
|
const claimSessionSubmission = useCallback((identity, text, images = []) => {
|
|
@@ -259,6 +266,14 @@ export function useComposer(params) {
|
|
|
259
266
|
.slice(0, textareaRef.current?.selectionStart ?? input.length)
|
|
260
267
|
.match(/(?:^|\s)@([^\s@]*)$/);
|
|
261
268
|
if (!match) {
|
|
269
|
+
setFileOptions([]);
|
|
270
|
+
setFileDrilled(false);
|
|
271
|
+
setFileDismissedQuery(null);
|
|
272
|
+
setFileIndex(0);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const query = match[1] ?? "";
|
|
276
|
+
if (fileDismissedQuery === query) {
|
|
262
277
|
setFileOptions([]);
|
|
263
278
|
return;
|
|
264
279
|
}
|
|
@@ -267,7 +282,7 @@ export function useComposer(params) {
|
|
|
267
282
|
const generation = refs.sessionGenerationRef.current;
|
|
268
283
|
const controller = new AbortController();
|
|
269
284
|
const timer = window.setTimeout(() => {
|
|
270
|
-
void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/files?q=${encodeURIComponent(
|
|
285
|
+
void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/files?q=${encodeURIComponent(query)}`, { signal: controller.signal })
|
|
271
286
|
.then((r) => (r.ok ? r.json() : null))
|
|
272
287
|
.then((body) => {
|
|
273
288
|
const active = activeIdentityRef.current;
|
|
@@ -276,7 +291,14 @@ export function useComposer(params) {
|
|
|
276
291
|
composerSessionKey(active) !== ownerKey ||
|
|
277
292
|
refs.sessionGenerationRef.current !== generation)
|
|
278
293
|
return;
|
|
279
|
-
|
|
294
|
+
const files = (body?.files ?? []);
|
|
295
|
+
setFileOptions(files
|
|
296
|
+
.filter((item) => typeof item.path === "string" && item.path.length > 0)
|
|
297
|
+
.map((item) => ({
|
|
298
|
+
path: item.path,
|
|
299
|
+
isDir: item.isDir === true || item.path.endsWith("/"),
|
|
300
|
+
})));
|
|
301
|
+
setFileIndex(0);
|
|
280
302
|
})
|
|
281
303
|
.catch(() => undefined);
|
|
282
304
|
}, 150);
|
|
@@ -284,16 +306,58 @@ export function useComposer(params) {
|
|
|
284
306
|
window.clearTimeout(timer);
|
|
285
307
|
controller.abort();
|
|
286
308
|
};
|
|
287
|
-
}, [
|
|
288
|
-
|
|
309
|
+
}, [
|
|
310
|
+
origin,
|
|
311
|
+
sessionId,
|
|
312
|
+
sessionIdentity,
|
|
313
|
+
sessionReady,
|
|
314
|
+
input,
|
|
315
|
+
refs,
|
|
316
|
+
fileDismissedQuery,
|
|
317
|
+
]);
|
|
318
|
+
const replaceAtToken = useCallback((nextPath, close) => {
|
|
289
319
|
const caret = textareaRef.current?.selectionStart ?? input.length;
|
|
290
320
|
const before = input.slice(0, caret);
|
|
291
321
|
const start = before.search(/@[^\s@]*$/);
|
|
292
322
|
if (start < 0)
|
|
323
|
+
return false;
|
|
324
|
+
const suffix = close ? " " : "";
|
|
325
|
+
setInput(`${input.slice(0, start)}@${nextPath}${suffix}${input.slice(caret)}`);
|
|
326
|
+
return true;
|
|
327
|
+
}, [input, setInput]);
|
|
328
|
+
const insertFileReference = useCallback((file) => {
|
|
329
|
+
if (!replaceAtToken(file, true))
|
|
293
330
|
return;
|
|
294
|
-
setInput(`${input.slice(0, start)}@${file} ${input.slice(caret)}`);
|
|
295
331
|
setFileOptions([]);
|
|
296
|
-
|
|
332
|
+
setFileDrilled(false);
|
|
333
|
+
setFileDismissedQuery(null);
|
|
334
|
+
setFileIndex(0);
|
|
335
|
+
}, [replaceAtToken]);
|
|
336
|
+
const drillFileReference = useCallback((file) => {
|
|
337
|
+
if (!replaceAtToken(file, false))
|
|
338
|
+
return;
|
|
339
|
+
setFileDrilled(true);
|
|
340
|
+
setFileDismissedQuery(null);
|
|
341
|
+
setFileIndex(0);
|
|
342
|
+
}, [replaceAtToken]);
|
|
343
|
+
const pickFileCrumb = useCallback((value) => {
|
|
344
|
+
if (!replaceAtToken(value, false))
|
|
345
|
+
return;
|
|
346
|
+
setFileDrilled(true);
|
|
347
|
+
setFileDismissedQuery(null);
|
|
348
|
+
setFileIndex(0);
|
|
349
|
+
}, [replaceAtToken]);
|
|
350
|
+
const dismissFilePalette = useCallback(() => {
|
|
351
|
+
const query = input
|
|
352
|
+
.slice(0, textareaRef.current?.selectionStart ?? input.length)
|
|
353
|
+
.match(/(?:^|\s)@([^\s@]*)$/)?.[1];
|
|
354
|
+
if (query === undefined) {
|
|
355
|
+
setFileOptions([]);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
setFileDismissedQuery(query);
|
|
359
|
+
setFileOptions([]);
|
|
360
|
+
}, [input]);
|
|
297
361
|
const addImagesFromFiles = useCallback((files) => {
|
|
298
362
|
const owner = activeIdentityRef.current;
|
|
299
363
|
if (!owner)
|
|
@@ -364,6 +428,9 @@ export function useComposer(params) {
|
|
|
364
428
|
useEffect(() => {
|
|
365
429
|
setShortcutIndex((index) => clampShortcutIndex(index, filteredShortcuts.length));
|
|
366
430
|
}, [filteredShortcuts.length]);
|
|
431
|
+
useEffect(() => {
|
|
432
|
+
setFileIndex((index) => clampShortcutIndex(index, fileOptions.length));
|
|
433
|
+
}, [fileOptions.length]);
|
|
367
434
|
const onCompositionStart = useCallback(() => {
|
|
368
435
|
refs.isComposingRef.current = true;
|
|
369
436
|
}, [refs]);
|
|
@@ -388,6 +455,7 @@ export function useComposer(params) {
|
|
|
388
455
|
sessionId: sessionReady ? sessionId : undefined,
|
|
389
456
|
refs,
|
|
390
457
|
onUnrecoverableSession,
|
|
458
|
+
deferInitialFetch: true,
|
|
391
459
|
});
|
|
392
460
|
}, [origin, sessionId, sessionReady, refs, onUnrecoverableSession]);
|
|
393
461
|
return {
|
|
@@ -404,6 +472,9 @@ export function useComposer(params) {
|
|
|
404
472
|
replaceSessionDraft,
|
|
405
473
|
releaseSession,
|
|
406
474
|
fileOptions,
|
|
475
|
+
fileIndex,
|
|
476
|
+
setFileIndex,
|
|
477
|
+
fileDrilled,
|
|
407
478
|
pendingImages,
|
|
408
479
|
setPendingImages,
|
|
409
480
|
addImagesFromFiles,
|
|
@@ -417,6 +488,9 @@ export function useComposer(params) {
|
|
|
417
488
|
dynamicCommandsError,
|
|
418
489
|
retryDynamicCommands,
|
|
419
490
|
insertFileReference,
|
|
491
|
+
drillFileReference,
|
|
492
|
+
pickFileCrumb,
|
|
493
|
+
dismissFilePalette,
|
|
420
494
|
textareaRef,
|
|
421
495
|
onCompositionStart,
|
|
422
496
|
onCompositionEnd,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { ACTIVE_CHAT_SESSION_STORAGE_KEY } from "../../chat/workspace-landing.js";
|
|
3
|
+
import { workspaceScopeReady } from "./useWorkspaceBrowserGate.js";
|
|
3
4
|
const REPO_HTML_PREVIEW_ROUTE_PREFIX = "/api/operator/v1/chat/repo/html/";
|
|
4
5
|
/** DSH-style path-encoded preview URL: relative HTML assets resolve under the
|
|
5
6
|
* same workspace-scoped route instead of losing a query-string workspaceId. */
|
|
@@ -46,6 +47,8 @@ export function useRepoBrowser(params) {
|
|
|
46
47
|
const loadTree = useCallback(async (directory = "") => {
|
|
47
48
|
if (blocked)
|
|
48
49
|
return; // AC-R5-002: fail-closed, zero repo requests
|
|
50
|
+
if (!workspaceScopeReady(workspaceId))
|
|
51
|
+
return;
|
|
49
52
|
const requestWorkspaceId = workspaceId ?? null;
|
|
50
53
|
try {
|
|
51
54
|
setTreeError(null);
|
|
@@ -91,9 +94,11 @@ export function useRepoBrowser(params) {
|
|
|
91
94
|
document.addEventListener("visibilitychange", onVisibility);
|
|
92
95
|
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
93
96
|
}, [refreshTree]);
|
|
94
|
-
const openPreview = useCallback(async (file) => {
|
|
97
|
+
const openPreview = useCallback(async (file, focusLine) => {
|
|
95
98
|
if (blocked)
|
|
96
99
|
return; // AC-R5-002: fail-closed
|
|
100
|
+
if (!workspaceScopeReady(workspaceId))
|
|
101
|
+
return;
|
|
97
102
|
const requestWorkspaceId = workspaceId ?? null;
|
|
98
103
|
setPreviewLoading(true);
|
|
99
104
|
try {
|
|
@@ -103,7 +108,10 @@ export function useRepoBrowser(params) {
|
|
|
103
108
|
return;
|
|
104
109
|
if (!res.ok || !body.data)
|
|
105
110
|
throw new Error(body.error?.message ?? "无法预览文件");
|
|
106
|
-
const loaded =
|
|
111
|
+
const loaded = {
|
|
112
|
+
...withHtmlPreviewUrl(body.data, origin, workspaceId),
|
|
113
|
+
...(focusLine ? { focusLine } : {}),
|
|
114
|
+
};
|
|
107
115
|
setPreviews((current) => {
|
|
108
116
|
const next = [
|
|
109
117
|
...current.filter((item) => item.path !== loaded.path),
|
|
@@ -177,6 +185,8 @@ export function useRepoBrowser(params) {
|
|
|
177
185
|
}
|
|
178
186
|
if (!paths.length)
|
|
179
187
|
return;
|
|
188
|
+
if (!workspaceScopeReady(workspaceId))
|
|
189
|
+
return;
|
|
180
190
|
void Promise.all(paths.map(async (file) => {
|
|
181
191
|
const res = await fetch(`${origin}/api/operator/v1/chat/repo/file?path=${encodeURIComponent(file)}${workspaceQuery}`, { credentials: "include" });
|
|
182
192
|
if (!res.ok)
|
|
@@ -192,7 +202,7 @@ export function useRepoBrowser(params) {
|
|
|
192
202
|
setPreviews(available);
|
|
193
203
|
setActivePreviewPath(available.at(-1)?.path ?? null);
|
|
194
204
|
});
|
|
195
|
-
}, [origin, workspaceQuery, sessionId, refs]);
|
|
205
|
+
}, [origin, workspaceQuery, workspaceId, sessionId, refs]);
|
|
196
206
|
const toggleDir = useCallback((dir) => {
|
|
197
207
|
setExpandedDirs((current) => {
|
|
198
208
|
const next = new Set(current);
|
|
@@ -3,7 +3,8 @@ import { formatModelRef, mergeSessionRuntime, sessionRuntimeMatches, } from "../
|
|
|
3
3
|
import { confirmationToken } from "./format.js";
|
|
4
4
|
import { isLostChatSessionError, mergeRuntimeContextWithSnapshot, runtimeSelectionFromSnapshot, } from "./runtime-snapshot-store.js";
|
|
5
5
|
import { buildToolCatalog } from "./tools-catalog.js";
|
|
6
|
-
import { adoptMutationSnapshot, dispatchRuntimeSnapshot, getRuntimeSnapshotState, refreshRuntimeSnapshot, settleMutationLifecycle, useRuntimeSnapshot, } from "./useRuntimeSnapshot.js";
|
|
6
|
+
import { adoptMutationSnapshot, dispatchRuntimeSnapshot, getRuntimeSnapshotState, refreshRuntimeSnapshot, releaseDeferredRuntimeSnapshotFetch, settleMutationLifecycle, useRuntimeSnapshot, } from "./useRuntimeSnapshot.js";
|
|
7
|
+
import { workspaceScopeReady } from "./useWorkspaceBrowserGate.js";
|
|
7
8
|
/**
|
|
8
9
|
* Monotonic mutation sequence shared across switchRuntime calls (RF-04). Each
|
|
9
10
|
* mutation captures its own token; only the LATEST started token may settle
|
|
@@ -19,6 +20,7 @@ export function useRuntimeControls(params) {
|
|
|
19
20
|
const [models, setModels] = useState([]);
|
|
20
21
|
const [modelsStatus, setModelsStatus] = useState("idle");
|
|
21
22
|
const [modelsError, setModelsError] = useState(null);
|
|
23
|
+
const [modelsForSessionId, setModelsForSessionId] = useState(null);
|
|
22
24
|
const [thinkingLevels, setThinkingLevels] = useState([]);
|
|
23
25
|
const [runtimeContext, setRuntimeContext] = useState(null);
|
|
24
26
|
const [runtimeContextError, setRuntimeContextError] = useState(null);
|
|
@@ -44,13 +46,14 @@ export function useRuntimeControls(params) {
|
|
|
44
46
|
const loadCapabilities = useCallback(async () => {
|
|
45
47
|
try {
|
|
46
48
|
// Workspace-scoped: the server rejects a bare capabilities call with
|
|
47
|
-
// WORKSPACE_REQUIRED
|
|
48
|
-
//
|
|
49
|
-
const scope = session?.workspaceId ??
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// WORKSPACE_REQUIRED. Wait for a selected workspace instead of firing
|
|
50
|
+
// an unscoped request that shows up as a 400 in the browser Network panel.
|
|
51
|
+
const scope = (session?.workspaceId ??
|
|
52
|
+
params.workspaceId ??
|
|
53
|
+
"").trim();
|
|
54
|
+
if (!workspaceScopeReady(scope))
|
|
55
|
+
return;
|
|
56
|
+
const res = await fetch(`${origin}/api/operator/v1/chat/capabilities?workspaceId=${encodeURIComponent(scope)}`);
|
|
54
57
|
if (!res.ok)
|
|
55
58
|
return;
|
|
56
59
|
const body = (await res.json());
|
|
@@ -63,16 +66,17 @@ export function useRuntimeControls(params) {
|
|
|
63
66
|
useEffect(() => {
|
|
64
67
|
void loadCapabilities();
|
|
65
68
|
}, [loadCapabilities]);
|
|
66
|
-
// Models + thinking levels:
|
|
67
|
-
// session
|
|
68
|
-
// never silently folded into an empty list.
|
|
69
|
+
// Models + thinking levels: GET lists the workspace catalog and does not
|
|
70
|
+
// wait for historical Pi session reopen. Failure is a STRUCTURED error
|
|
71
|
+
// (with retry), never silently folded into an empty list.
|
|
72
|
+
const sessionId = session?.sessionId ?? null;
|
|
69
73
|
const loadModels = useCallback(async () => {
|
|
70
|
-
if (!
|
|
74
|
+
if (!sessionId) {
|
|
71
75
|
setModels([]);
|
|
72
76
|
setModelsStatus("idle");
|
|
77
|
+
setModelsForSessionId(null);
|
|
73
78
|
return;
|
|
74
79
|
}
|
|
75
|
-
const sessionId = session.sessionId;
|
|
76
80
|
setModelsStatus("loading");
|
|
77
81
|
setModelsError(null);
|
|
78
82
|
try {
|
|
@@ -93,27 +97,36 @@ export function useRuntimeControls(params) {
|
|
|
93
97
|
})) {
|
|
94
98
|
setModelsStatus("idle");
|
|
95
99
|
setModelsError(null);
|
|
100
|
+
setModelsForSessionId(sessionId);
|
|
96
101
|
onUnrecoverableSession?.(sessionId);
|
|
97
102
|
return;
|
|
98
103
|
}
|
|
99
104
|
setModelsStatus("error");
|
|
100
105
|
setModelsError(error?.message ?? `模型列表读取失败(HTTP ${res.status})`);
|
|
106
|
+
setModelsForSessionId(sessionId);
|
|
101
107
|
return;
|
|
102
108
|
}
|
|
103
109
|
setModels(body.models ?? []);
|
|
104
110
|
setThinkingLevels(body.thinkingLevels ?? []);
|
|
105
111
|
setModelsStatus("ready");
|
|
112
|
+
setModelsForSessionId(sessionId);
|
|
106
113
|
}
|
|
107
114
|
catch (reason) {
|
|
108
115
|
if (refs.sessionRef.current?.sessionId !== sessionId)
|
|
109
116
|
return;
|
|
110
117
|
setModelsStatus("error");
|
|
111
118
|
setModelsError(reason instanceof Error ? reason.message : String(reason));
|
|
119
|
+
setModelsForSessionId(sessionId);
|
|
112
120
|
}
|
|
113
|
-
}, [origin,
|
|
121
|
+
}, [origin, sessionId, refs, onUnrecoverableSession]);
|
|
114
122
|
useEffect(() => {
|
|
115
123
|
void loadModels();
|
|
116
124
|
}, [loadModels]);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!session || modelsForSessionId !== session.sessionId)
|
|
127
|
+
return;
|
|
128
|
+
releaseDeferredRuntimeSnapshotFetch(session.sessionId);
|
|
129
|
+
}, [session, modelsForSessionId]);
|
|
117
130
|
useEffect(() => {
|
|
118
131
|
if (!session) {
|
|
119
132
|
setRuntimeContext(null);
|
|
@@ -121,6 +134,12 @@ export function useRuntimeControls(params) {
|
|
|
121
134
|
setRuntimeContextLoading(false);
|
|
122
135
|
return;
|
|
123
136
|
}
|
|
137
|
+
if (modelsForSessionId !== session.sessionId) {
|
|
138
|
+
setRuntimeContext(null);
|
|
139
|
+
setRuntimeContextError(null);
|
|
140
|
+
setRuntimeContextLoading(true);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
124
143
|
const sessionId = session.sessionId;
|
|
125
144
|
const base = `${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`;
|
|
126
145
|
setRuntimeContextError(null);
|
|
@@ -181,7 +200,7 @@ export function useRuntimeControls(params) {
|
|
|
181
200
|
return;
|
|
182
201
|
setRuntimeContextError("注入上下文读取失败,请重试");
|
|
183
202
|
});
|
|
184
|
-
}, [origin, session?.sessionId, refs, onUnrecoverableSession]);
|
|
203
|
+
}, [origin, session?.sessionId, modelsForSessionId, refs, onUnrecoverableSession]);
|
|
185
204
|
// AC-01/RF3-01: hydrate the Composer from the unified snapshot — real
|
|
186
205
|
// model and thinking selection; `auto` displays the Pi actual effective
|
|
187
206
|
// level. Derived session/runtimeContext state ONLY comes from a snapshot
|
|
@@ -348,8 +367,8 @@ export function useRuntimeControls(params) {
|
|
|
348
367
|
capabilities,
|
|
349
368
|
]);
|
|
350
369
|
// Snapshot is the single source of truth when ready; the /models fetch
|
|
351
|
-
// result is the fallback
|
|
352
|
-
const effectiveModels = snapshot
|
|
370
|
+
// result is the fallback so the picker is usable before Pi session reopen.
|
|
371
|
+
const effectiveModels = snapshot && snapshotState.status === "ready"
|
|
353
372
|
? (snapshot.models ?? []).map((model) => ({
|
|
354
373
|
provider: model.provider,
|
|
355
374
|
id: model.id,
|
|
@@ -14,6 +14,7 @@ import { confirmationToken } from "./format.js";
|
|
|
14
14
|
import { createRuntimeSnapshotStore, isLostChatSessionError, parseRuntimeSnapshotError, } from "./runtime-snapshot-store.js";
|
|
15
15
|
const store = createRuntimeSnapshotStore();
|
|
16
16
|
let controller;
|
|
17
|
+
let snapshotFetchStartedFor;
|
|
17
18
|
function snapshotUrl(sessionId) {
|
|
18
19
|
return `/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`;
|
|
19
20
|
}
|
|
@@ -36,12 +37,28 @@ export function attachRuntimeSnapshotController(input) {
|
|
|
36
37
|
store.dispatch({ type: "mutation-reset" });
|
|
37
38
|
}
|
|
38
39
|
if (!input.sessionId) {
|
|
40
|
+
snapshotFetchStartedFor = undefined;
|
|
39
41
|
store.dispatch({ type: "reset" });
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
42
44
|
store.dispatch({ type: "load-start", sessionId: input.sessionId });
|
|
45
|
+
if (input.deferInitialFetch) {
|
|
46
|
+
snapshotFetchStartedFor = undefined;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
snapshotFetchStartedFor = input.sessionId;
|
|
43
50
|
void fetchSnapshot(input.sessionId, input.origin);
|
|
44
51
|
}
|
|
52
|
+
/** Start the deferred GET /runtime-snapshot after the workspace model catalog settles. */
|
|
53
|
+
export function releaseDeferredRuntimeSnapshotFetch(sessionId) {
|
|
54
|
+
const ctrl = controller;
|
|
55
|
+
if (!ctrl?.sessionId || ctrl.sessionId !== sessionId)
|
|
56
|
+
return;
|
|
57
|
+
if (snapshotFetchStartedFor === sessionId)
|
|
58
|
+
return;
|
|
59
|
+
snapshotFetchStartedFor = sessionId;
|
|
60
|
+
void fetchSnapshot(sessionId, ctrl.origin);
|
|
61
|
+
}
|
|
45
62
|
async function fetchSnapshot(sessionId, origin) {
|
|
46
63
|
try {
|
|
47
64
|
const res = await fetch(`${origin}${snapshotUrl(sessionId)}/runtime-snapshot`, { credentials: "include" });
|
|
@@ -144,6 +161,7 @@ export async function refreshRuntimeSnapshot() {
|
|
|
144
161
|
return false;
|
|
145
162
|
const sessionId = ctrl.sessionId;
|
|
146
163
|
store.dispatch({ type: "load-start", sessionId });
|
|
164
|
+
snapshotFetchStartedFor = sessionId;
|
|
147
165
|
void fetchSnapshot(sessionId, ctrl.origin);
|
|
148
166
|
return true;
|
|
149
167
|
}
|
|
@@ -2,3 +2,7 @@ export const WORKSPACE_BROWSER_OK = { status: "ok" };
|
|
|
2
2
|
export function workspaceBrowserGateActive(gate) {
|
|
3
3
|
return gate.status !== "ok";
|
|
4
4
|
}
|
|
5
|
+
/** Non-session chat APIs require a selected workspace; empty/whitespace is unset. */
|
|
6
|
+
export function workspaceScopeReady(workspaceId) {
|
|
7
|
+
return Boolean(workspaceId?.trim());
|
|
8
|
+
}
|
|
@@ -22,7 +22,7 @@ export async function reloadWhenConsoleReady(options = {}) {
|
|
|
22
22
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
23
23
|
const reload = options.reload ?? (() => window.location.reload());
|
|
24
24
|
const started = Date.now();
|
|
25
|
-
let previousConsoleInstanceId = null;
|
|
25
|
+
let previousConsoleInstanceId = options.previousConsoleInstanceId?.trim() || null;
|
|
26
26
|
while (Date.now() - started < timeoutMs) {
|
|
27
27
|
try {
|
|
28
28
|
const res = await fetchImpl(`/api/health?ts=${Date.now()}`, {
|