@tea-agent/loop-agent 0.37.0 → 0.37.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -6
- package/CHANGELOG.md +71 -144
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/contract/project.js +0 -4
- package/dist/task/source-prepare/completeness.js +0 -53
- package/dist/task/source-prepare/parse-intent.js +12 -82
- package/dist/task/source-prepare/prepare.js +5 -102
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -152
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -111
- package/dist/worker/console/chat/routes.js +10 -43
- package/dist/worker/console/chat/session-store.js +1 -9
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -204
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +14 -391
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +3 -10
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +82 -165
- package/dist/workflows/dag/node-execution.js +0 -89
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/runner.js +15 -333
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -130
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -9
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -4
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -477
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -2,7 +2,6 @@ import { artifactFromEventPayload, mergeArtifactCardState, } from "../../chat/ar
|
|
|
2
2
|
import { humanGateCardFromEventPayload, mergeHumanGateCardState, } from "../../chat/human-gate-card.js";
|
|
3
3
|
import { mergeOperationCardState, operationFromEventPayload, } from "../../chat/operation-card.js";
|
|
4
4
|
import { requestId } from "./format.js";
|
|
5
|
-
import { parseAssistantContent } from "../../chat/assistant-content.js";
|
|
6
5
|
/** Shared handler for a single parsed Chat SSE event (M1 reconnect).
|
|
7
6
|
*
|
|
8
7
|
* Used by BOTH the inline prompt POST SSE and the background reconnect
|
|
@@ -25,30 +24,25 @@ export function createChatSseEventApplier(deps) {
|
|
|
25
24
|
// global error; the snapshot replay frames that follow are still applied
|
|
26
25
|
// as ordinary blocks (deduped by eventId, replay text skipped).
|
|
27
26
|
const reason = typeof data.reason === "string" ? data.reason : "cursor_expired";
|
|
28
|
-
|
|
27
|
+
onReconcile?.({
|
|
29
28
|
reason,
|
|
30
29
|
...(typeof data.minSeq === "number" ? { minSeq: data.minSeq } : {}),
|
|
31
30
|
...(typeof data.epoch === "string" ? { epoch: data.epoch } : {}),
|
|
32
31
|
...(typeof data.latestTurnId === "string"
|
|
33
32
|
? { latestTurnId: data.latestTurnId }
|
|
34
33
|
: {}),
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
...(typeof data.latestEventId === "string"
|
|
35
|
+
? { latestEventId: data.latestEventId }
|
|
36
|
+
: data.latestEventId === null
|
|
37
|
+
? { latestEventId: null }
|
|
38
|
+
: {}),
|
|
39
|
+
});
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
const payload = data.data && typeof data.data === "object"
|
|
43
43
|
? data.data
|
|
44
44
|
: data;
|
|
45
45
|
const turnId = typeof data.turnId === "string" ? data.turnId : undefined;
|
|
46
|
-
const eventSeq = typeof data.seq === "number" && Number.isFinite(data.seq)
|
|
47
|
-
? data.seq
|
|
48
|
-
: undefined;
|
|
49
|
-
const eventAt = typeof data.at === "string" && Number.isFinite(Date.parse(data.at))
|
|
50
|
-
? Date.parse(data.at)
|
|
51
|
-
: Date.now();
|
|
52
46
|
if (turnId &&
|
|
53
47
|
(eventName === "agent_start" || eventName === "message_update") &&
|
|
54
48
|
refs.settledTurnIdsRef.current.has(turnId)) {
|
|
@@ -113,26 +107,14 @@ export function createChatSseEventApplier(deps) {
|
|
|
113
107
|
}
|
|
114
108
|
if (eventName === "human-gate-card") {
|
|
115
109
|
const card = humanGateCardFromEventPayload(payload);
|
|
116
|
-
if (card)
|
|
117
|
-
setHumanGateCards((current) => mergeHumanGateCardState(current,
|
|
118
|
-
...card,
|
|
119
|
-
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
120
|
-
...(turnId ? { turnId } : {}),
|
|
121
|
-
activityRef: `gate:${card.cardId}`,
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
110
|
+
if (card)
|
|
111
|
+
setHumanGateCards((current) => mergeHumanGateCardState(current, card));
|
|
124
112
|
return;
|
|
125
113
|
}
|
|
126
114
|
if (eventName === "artifact-ref") {
|
|
127
115
|
const artifact = artifactFromEventPayload(payload);
|
|
128
|
-
if (artifact)
|
|
129
|
-
setArtifactCards((current) => mergeArtifactCardState(current,
|
|
130
|
-
...artifact,
|
|
131
|
-
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
132
|
-
...(turnId ? { turnId } : {}),
|
|
133
|
-
activityRef: `artifact:${artifact.artifactId}`,
|
|
134
|
-
}));
|
|
135
|
-
}
|
|
116
|
+
if (artifact)
|
|
117
|
+
setArtifactCards((current) => mergeArtifactCardState(current, artifact));
|
|
136
118
|
return;
|
|
137
119
|
}
|
|
138
120
|
if (eventName === "agent_start") {
|
|
@@ -189,18 +171,7 @@ export function createChatSseEventApplier(deps) {
|
|
|
189
171
|
if (eventName === "operation-ref" || eventName === "operation-status") {
|
|
190
172
|
const operation = operationFromEventPayload(payload);
|
|
191
173
|
if (operation) {
|
|
192
|
-
|
|
193
|
-
? payload.toolCallId
|
|
194
|
-
: undefined;
|
|
195
|
-
setOperationCards((current) => mergeOperationCardState(current, {
|
|
196
|
-
...operation,
|
|
197
|
-
...(eventSeq === undefined
|
|
198
|
-
? {}
|
|
199
|
-
: { eventSeq, updatedSeq: eventSeq }),
|
|
200
|
-
...(turnId ? { turnId } : {}),
|
|
201
|
-
...(toolCallId ? { toolCallId } : {}),
|
|
202
|
-
activityRef: `operation:${operation.operationId}`,
|
|
203
|
-
}));
|
|
174
|
+
setOperationCards((current) => mergeOperationCardState(current, operation));
|
|
204
175
|
}
|
|
205
176
|
return;
|
|
206
177
|
}
|
|
@@ -222,7 +193,6 @@ export function createChatSseEventApplier(deps) {
|
|
|
222
193
|
const cacheRead = sample.cacheReadTokens ?? 0;
|
|
223
194
|
const cacheWrite = sample.cacheWriteTokens ?? 0;
|
|
224
195
|
const total = sample.totalTokens ?? input + output + cacheRead + cacheWrite;
|
|
225
|
-
const cu = sample.contextUsage;
|
|
226
196
|
return {
|
|
227
197
|
inputTokens: current.inputTokens + input,
|
|
228
198
|
outputTokens: current.outputTokens + output,
|
|
@@ -232,18 +202,6 @@ export function createChatSseEventApplier(deps) {
|
|
|
232
202
|
cost: sample.cost === undefined
|
|
233
203
|
? current.cost
|
|
234
204
|
: (current.cost ?? 0) + sample.cost,
|
|
235
|
-
// Context usage is a live snapshot, not an accumulator: replace it.
|
|
236
|
-
...(cu && typeof cu === "object"
|
|
237
|
-
? {
|
|
238
|
-
contextUsage: {
|
|
239
|
-
tokens: typeof cu.tokens === "number" ? cu.tokens : null,
|
|
240
|
-
contextWindow: typeof cu.contextWindow === "number"
|
|
241
|
-
? cu.contextWindow
|
|
242
|
-
: 0,
|
|
243
|
-
percent: typeof cu.percent === "number" ? cu.percent : null,
|
|
244
|
-
},
|
|
245
|
-
}
|
|
246
|
-
: {}),
|
|
247
205
|
};
|
|
248
206
|
});
|
|
249
207
|
return;
|
|
@@ -255,46 +213,24 @@ export function createChatSseEventApplier(deps) {
|
|
|
255
213
|
// a duplicate bubble.
|
|
256
214
|
return;
|
|
257
215
|
}
|
|
258
|
-
const sealedIds = refs.sealedAssistantIdsRef.current;
|
|
259
|
-
if (eventName === "message_update" &&
|
|
260
|
-
sealedIds.has(resolvedAssistantId)) {
|
|
261
|
-
const nextId = requestId("a");
|
|
262
|
-
if (turnId)
|
|
263
|
-
refs.turnAssistantIdsRef.current.set(turnId, nextId);
|
|
264
|
-
resolvedAssistantId = nextId;
|
|
265
|
-
setStreamingAssistantId(nextId);
|
|
266
|
-
}
|
|
267
216
|
const text = typeof payload.text === "string" ? payload.text : "";
|
|
268
|
-
const content = parseAssistantContent(payload.content);
|
|
269
|
-
const assistantIdForEvent = resolvedAssistantId;
|
|
270
217
|
setMessages((m) => {
|
|
271
|
-
const exists = m.some((msg) => msg.id ===
|
|
218
|
+
const exists = m.some((msg) => msg.id === resolvedAssistantId);
|
|
272
219
|
if (exists) {
|
|
273
|
-
return m.map((msg) => {
|
|
274
|
-
if (msg.id !== assistantIdForEvent)
|
|
275
|
-
return msg;
|
|
276
|
-
return {
|
|
277
|
-
...msg,
|
|
278
|
-
text,
|
|
279
|
-
...(content ? { content } : {}),
|
|
280
|
-
};
|
|
281
|
-
});
|
|
220
|
+
return m.map((msg) => msg.id === resolvedAssistantId ? { ...msg, text } : msg);
|
|
282
221
|
}
|
|
283
222
|
// Reconnect path: the assistant message may not exist yet
|
|
284
223
|
// (e.g. only tool_call events arrived). Append a placeholder.
|
|
285
224
|
return [
|
|
286
225
|
...m,
|
|
287
226
|
{
|
|
288
|
-
id:
|
|
227
|
+
id: resolvedAssistantId,
|
|
289
228
|
role: "assistant",
|
|
290
229
|
text,
|
|
291
|
-
createdAt:
|
|
292
|
-
...(content ? { content } : {}),
|
|
230
|
+
createdAt: Date.now(),
|
|
293
231
|
},
|
|
294
232
|
];
|
|
295
233
|
});
|
|
296
|
-
if (eventName === "message_end")
|
|
297
|
-
sealedIds.add(assistantIdForEvent);
|
|
298
234
|
}
|
|
299
235
|
else if (eventName === "tool_call") {
|
|
300
236
|
const id = typeof payload.toolCallId === "string"
|
|
@@ -302,20 +238,18 @@ export function createChatSseEventApplier(deps) {
|
|
|
302
238
|
: requestId("t");
|
|
303
239
|
const toolName = typeof payload.toolName === "string" ? payload.toolName : "tool";
|
|
304
240
|
const argsPreview = typeof payload.argsPreview === "string" ? payload.argsPreview : "{}";
|
|
241
|
+
const now = Date.now();
|
|
305
242
|
setToolCalls((prev) => {
|
|
306
243
|
if (prev.some((t) => t.id === id)) {
|
|
307
|
-
return prev.map((t) =>
|
|
308
|
-
|
|
309
|
-
return t;
|
|
310
|
-
return {
|
|
244
|
+
return prev.map((t) => t.id === id
|
|
245
|
+
? {
|
|
311
246
|
...t,
|
|
312
247
|
args: argsPreview,
|
|
313
248
|
status: "running",
|
|
314
249
|
turnId: turnId ?? t.turnId,
|
|
315
250
|
assistantMessageId: resolvedAssistantId,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
});
|
|
251
|
+
}
|
|
252
|
+
: t);
|
|
319
253
|
}
|
|
320
254
|
return [
|
|
321
255
|
...prev,
|
|
@@ -324,8 +258,7 @@ export function createChatSseEventApplier(deps) {
|
|
|
324
258
|
toolName,
|
|
325
259
|
args: argsPreview,
|
|
326
260
|
status: "running",
|
|
327
|
-
createdAt:
|
|
328
|
-
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
261
|
+
createdAt: now,
|
|
329
262
|
turnId,
|
|
330
263
|
assistantMessageId: resolvedAssistantId,
|
|
331
264
|
},
|
|
@@ -341,34 +274,21 @@ export function createChatSseEventApplier(deps) {
|
|
|
341
274
|
const resultText = typeof payload.resultPreview === "string"
|
|
342
275
|
? payload.resultPreview
|
|
343
276
|
: "{}";
|
|
344
|
-
|
|
345
|
-
// the frame; persist it on the tool entry so the semantic event resolves
|
|
346
|
-
// an operation:<id> ref (mirrors the hydrated path backfill).
|
|
347
|
-
const resultOperationId = typeof payload.operationId === "string" && payload.operationId
|
|
348
|
-
? payload.operationId
|
|
349
|
-
: undefined;
|
|
277
|
+
const now = Date.now();
|
|
350
278
|
setToolCalls((prev) => {
|
|
351
279
|
const exists = prev.find((t) => t.id === id);
|
|
352
280
|
if (exists) {
|
|
353
|
-
return prev.map((t) =>
|
|
354
|
-
|
|
355
|
-
return t;
|
|
356
|
-
return {
|
|
281
|
+
return prev.map((t) => t.id === id
|
|
282
|
+
? {
|
|
357
283
|
...t,
|
|
358
284
|
result: resultText,
|
|
359
285
|
resultText,
|
|
360
286
|
status: isError ? "error" : "ok",
|
|
361
|
-
finishedAt:
|
|
362
|
-
...(eventSeq === undefined ? {} : { finishedSeq: eventSeq }),
|
|
287
|
+
finishedAt: now,
|
|
363
288
|
turnId: turnId ?? t.turnId,
|
|
364
289
|
assistantMessageId: t.assistantMessageId ?? resolvedAssistantId,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
: t.operationId
|
|
368
|
-
? { operationId: t.operationId }
|
|
369
|
-
: {}),
|
|
370
|
-
};
|
|
371
|
-
});
|
|
290
|
+
}
|
|
291
|
+
: t);
|
|
372
292
|
}
|
|
373
293
|
return [
|
|
374
294
|
...prev,
|
|
@@ -378,14 +298,10 @@ export function createChatSseEventApplier(deps) {
|
|
|
378
298
|
result: resultText,
|
|
379
299
|
resultText,
|
|
380
300
|
status: isError ? "error" : "ok",
|
|
381
|
-
createdAt:
|
|
382
|
-
finishedAt:
|
|
383
|
-
...(eventSeq === undefined
|
|
384
|
-
? {}
|
|
385
|
-
: { eventSeq, finishedSeq: eventSeq }),
|
|
301
|
+
createdAt: now,
|
|
302
|
+
finishedAt: now,
|
|
386
303
|
turnId,
|
|
387
304
|
assistantMessageId: resolvedAssistantId,
|
|
388
|
-
...(resultOperationId ? { operationId: resultOperationId } : {}),
|
|
389
305
|
},
|
|
390
306
|
];
|
|
391
307
|
});
|
|
@@ -12,7 +12,6 @@ export function useChatRefs() {
|
|
|
12
12
|
const settledTurnIdsRef = useRef(new Set());
|
|
13
13
|
const seenEventIdsRef = useRef(new Set());
|
|
14
14
|
const turnAssistantIdsRef = useRef(new Map());
|
|
15
|
-
const sealedAssistantIdsRef = useRef(new Set());
|
|
16
15
|
const reconnectAbortRef = useRef(null);
|
|
17
16
|
const activeTurnIdRef = useRef(null);
|
|
18
17
|
const submitInFlightRef = useRef(false);
|
|
@@ -41,7 +40,6 @@ export function useChatRefs() {
|
|
|
41
40
|
settledTurnIdsRef,
|
|
42
41
|
seenEventIdsRef,
|
|
43
42
|
turnAssistantIdsRef,
|
|
44
|
-
sealedAssistantIdsRef,
|
|
45
43
|
reconnectAbortRef,
|
|
46
44
|
activeTurnIdRef,
|
|
47
45
|
submitInFlightRef,
|
|
@@ -68,7 +66,6 @@ export function useChatRefs() {
|
|
|
68
66
|
settledTurnIdsRef,
|
|
69
67
|
seenEventIdsRef,
|
|
70
68
|
turnAssistantIdsRef,
|
|
71
|
-
sealedAssistantIdsRef,
|
|
72
69
|
reconnectAbortRef,
|
|
73
70
|
activeTurnIdRef,
|
|
74
71
|
submitInFlightRef,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mutual-exclusion state for large spatial overlays in Operator Chat.
|
|
3
|
-
* At most one of: sidebar drawer, process, runtime context, tools
|
|
3
|
+
* At most one of: sidebar drawer, process, runtime context, tools.
|
|
4
4
|
* Small composer menus (model/thinking) are out of scope.
|
|
5
5
|
*/
|
|
6
6
|
export function createSpatialOverlayState(active = "none") {
|
|
@@ -33,6 +33,5 @@ export function spatialOverlayFlags(state) {
|
|
|
33
33
|
processOpen: state.active === "process",
|
|
34
34
|
runtimeContextOpen: state.active === "runtime-context",
|
|
35
35
|
toolsOpen: state.active === "tools",
|
|
36
|
-
activityOpen: state.active === "activity",
|
|
37
36
|
};
|
|
38
37
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import { parseAssistantContent } from "../../chat/assistant-content.js";
|
|
3
2
|
import { runtimeSelectionFromRecord } from "../../chat/runtime-selection.js";
|
|
4
3
|
import { hydrateSessionThread } from "../../chat/turn-process.js";
|
|
5
4
|
import { ACTIVE_CHAT_SESSION_STORAGE_KEY } from "../../chat/workspace-landing.js";
|
|
@@ -58,7 +57,7 @@ export async function createSessionAndPrimeTitleWatcher(input) {
|
|
|
58
57
|
/** Session lifecycle: list / activate / create / delete / rename-archive /
|
|
59
58
|
* compact, plus browser-refresh recovery from the durable session store. */
|
|
60
59
|
export function useChatSessions(params) {
|
|
61
|
-
const { origin, refs, setSession, thread, composer, browser, setError,
|
|
60
|
+
const { origin, refs, setSession, thread, composer, browser, setError, setProcessOpen, setAutoScroll, } = params;
|
|
62
61
|
const { applyActivatedSession, resetForNewSession, setMessages } = thread;
|
|
63
62
|
const { setInput, setPendingImages } = composer;
|
|
64
63
|
const { setPreviews, setActivePreviewPath } = browser;
|
|
@@ -90,7 +89,6 @@ export function useChatSessions(params) {
|
|
|
90
89
|
refs.lastEventIdRef.current = null;
|
|
91
90
|
refs.seenEventIdsRef.current = new Set();
|
|
92
91
|
refs.turnAssistantIdsRef.current = new Map();
|
|
93
|
-
refs.sealedAssistantIdsRef.current = new Set();
|
|
94
92
|
refs.settledTurnIdsRef.current = new Set();
|
|
95
93
|
refs.activeTurnIdRef.current = null;
|
|
96
94
|
resetForNewSession();
|
|
@@ -146,6 +144,8 @@ export function useChatSessions(params) {
|
|
|
146
144
|
}
|
|
147
145
|
}, [origin, refs, resetActiveProjection, setSession]);
|
|
148
146
|
const deleteSession = useCallback(async (sessionId) => {
|
|
147
|
+
if (!window.confirm("确定删除该会话?删除后不可恢复。"))
|
|
148
|
+
return;
|
|
149
149
|
try {
|
|
150
150
|
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`, {
|
|
151
151
|
method: "DELETE",
|
|
@@ -156,7 +156,7 @@ export function useChatSessions(params) {
|
|
|
156
156
|
});
|
|
157
157
|
if (!res.ok) {
|
|
158
158
|
setError("删除会话失败");
|
|
159
|
-
return
|
|
159
|
+
return;
|
|
160
160
|
}
|
|
161
161
|
titleWatcherRef.current?.cancel(sessionId);
|
|
162
162
|
if (refs.sessionRef.current?.sessionId === sessionId) {
|
|
@@ -167,11 +167,9 @@ export function useChatSessions(params) {
|
|
|
167
167
|
window.localStorage.removeItem(ACTIVE_CHAT_SESSION_STORAGE_KEY);
|
|
168
168
|
}
|
|
169
169
|
await refreshSessionList();
|
|
170
|
-
return true;
|
|
171
170
|
}
|
|
172
171
|
catch (error) {
|
|
173
172
|
setError(error instanceof Error ? error.message : String(error));
|
|
174
|
-
return false;
|
|
175
173
|
}
|
|
176
174
|
}, [
|
|
177
175
|
origin,
|
|
@@ -200,7 +198,6 @@ export function useChatSessions(params) {
|
|
|
200
198
|
refs.activeTurnIdRef.current = body.session.activeTurnId ?? null;
|
|
201
199
|
refs.seenEventIdsRef.current = new Set();
|
|
202
200
|
refs.turnAssistantIdsRef.current = new Map();
|
|
203
|
-
refs.sealedAssistantIdsRef.current = new Set();
|
|
204
201
|
refs.settledTurnIdsRef.current = new Set();
|
|
205
202
|
refs.lastEventIdRef.current = null;
|
|
206
203
|
setSession(next);
|
|
@@ -298,23 +295,18 @@ export function useChatSessions(params) {
|
|
|
298
295
|
refs.activeTurnIdRef.current = body.session.activeTurnId ?? null;
|
|
299
296
|
refs.seenEventIdsRef.current = new Set();
|
|
300
297
|
refs.turnAssistantIdsRef.current = new Map();
|
|
301
|
-
refs.sealedAssistantIdsRef.current = new Set();
|
|
302
298
|
refs.settledTurnIdsRef.current = new Set();
|
|
303
299
|
refs.lastEventIdRef.current = null;
|
|
304
300
|
setSession(restored);
|
|
305
301
|
setInput(body.session.composerDraft ?? "");
|
|
306
302
|
setMessages((body.session.messages ?? [])
|
|
307
303
|
.filter((message) => message.role === "user" || message.role === "assistant")
|
|
308
|
-
.map((message) => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
createdAt: Date.parse(message.at) || Date.now(),
|
|
315
|
-
...(content && content.length > 0 ? { content } : {}),
|
|
316
|
-
};
|
|
317
|
-
}));
|
|
304
|
+
.map((message) => ({
|
|
305
|
+
id: message.id,
|
|
306
|
+
role: message.role,
|
|
307
|
+
text: message.text,
|
|
308
|
+
createdAt: Date.parse(message.at) || Date.now(),
|
|
309
|
+
})));
|
|
318
310
|
}
|
|
319
311
|
catch {
|
|
320
312
|
// Recovery is best-effort; a new session remains available.
|
|
@@ -372,27 +364,15 @@ export function useChatSessions(params) {
|
|
|
372
364
|
createSessionAndPrimeTitleWatcherForHook,
|
|
373
365
|
]);
|
|
374
366
|
// Phase-1: fork / in-session branch / mainline switch UI deferred; API routes remain.
|
|
375
|
-
|
|
376
|
-
const compactSession = useCallback(async (instructions) => {
|
|
367
|
+
const compactSession = useCallback(async () => {
|
|
377
368
|
const current = refs.sessionRef.current;
|
|
378
369
|
if (!current || refs.streamingRef.current)
|
|
379
370
|
return;
|
|
380
|
-
const trimmed = typeof instructions === "string" ? instructions.trim() : "";
|
|
381
|
-
onCompactionChange?.(true);
|
|
382
371
|
try {
|
|
383
|
-
const headers = {
|
|
384
|
-
"x-loop-console-confirmation": confirmationToken(),
|
|
385
|
-
};
|
|
386
|
-
if (trimmed) {
|
|
387
|
-
headers["content-type"] = "application/json";
|
|
388
|
-
}
|
|
389
372
|
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(current.sessionId)}/compact`, {
|
|
390
373
|
method: "POST",
|
|
391
374
|
credentials: "include",
|
|
392
|
-
headers,
|
|
393
|
-
...(trimmed
|
|
394
|
-
? { body: JSON.stringify({ instructions: trimmed }) }
|
|
395
|
-
: {}),
|
|
375
|
+
headers: { "x-loop-console-confirmation": confirmationToken() },
|
|
396
376
|
});
|
|
397
377
|
const body = (await response.json().catch(() => ({})));
|
|
398
378
|
if (!response.ok) {
|
|
@@ -400,45 +380,11 @@ export function useChatSessions(params) {
|
|
|
400
380
|
return;
|
|
401
381
|
}
|
|
402
382
|
setError(null);
|
|
403
|
-
// pi-web parity: reload the compacted session, then leave a distinct
|
|
404
|
-
// compaction summary card in the timeline plus a transient toast.
|
|
405
|
-
const summary = body.snapshot?.summary?.trim();
|
|
406
|
-
const tokensBefore = body.snapshot?.tokensBefore;
|
|
407
|
-
const summaryText = summary || "历史上下文已精简,关键决策与当前任务已保留。";
|
|
408
|
-
await activateSession(current.sessionId);
|
|
409
|
-
setMessages((messages) => [
|
|
410
|
-
...messages,
|
|
411
|
-
{
|
|
412
|
-
id: `compaction-${Date.now()}`,
|
|
413
|
-
role: "assistant",
|
|
414
|
-
kind: "compaction",
|
|
415
|
-
text: summaryText,
|
|
416
|
-
createdAt: Date.now(),
|
|
417
|
-
...(tokensBefore
|
|
418
|
-
? { compaction: { tokensBefore } }
|
|
419
|
-
: {}),
|
|
420
|
-
},
|
|
421
|
-
]);
|
|
422
|
-
setNotice({
|
|
423
|
-
id: Date.now(),
|
|
424
|
-
message: `已压缩上下文:${summaryText.slice(0, 72)}${summaryText.length > 72 ? "…" : ""}`,
|
|
425
|
-
});
|
|
426
383
|
}
|
|
427
384
|
catch (error) {
|
|
428
385
|
setError(error instanceof Error ? error.message : "上下文压缩失败");
|
|
429
386
|
}
|
|
430
|
-
|
|
431
|
-
onCompactionChange?.(false);
|
|
432
|
-
}
|
|
433
|
-
}, [
|
|
434
|
-
origin,
|
|
435
|
-
refs,
|
|
436
|
-
setError,
|
|
437
|
-
setNotice,
|
|
438
|
-
setMessages,
|
|
439
|
-
onCompactionChange,
|
|
440
|
-
activateSession,
|
|
441
|
-
]);
|
|
387
|
+
}, [origin, refs, setError]);
|
|
442
388
|
const updateSession = async (sessionId, patch) => {
|
|
443
389
|
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}`, {
|
|
444
390
|
method: "PATCH",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { parseAssistantContent } from "../../chat/assistant-content.js";
|
|
3
2
|
import { buildTurnProcessGroups } from "../../chat/turn-process.js";
|
|
4
3
|
import { createChatSseEventApplier, } from "./chat-sse-events.js";
|
|
5
4
|
import { taskContextUnchanged } from "./format.js";
|
|
@@ -219,19 +218,14 @@ export function useChatThread(params) {
|
|
|
219
218
|
refs.lastEventIdRef.current = body.lastEventId ?? null;
|
|
220
219
|
refs.seenEventIdsRef.current = new Set();
|
|
221
220
|
refs.turnAssistantIdsRef.current = new Map();
|
|
222
|
-
refs.sealedAssistantIdsRef.current = new Set();
|
|
223
221
|
setMessages((body.session.messages ?? [])
|
|
224
222
|
.filter((message) => message.role === "user" || message.role === "assistant")
|
|
225
|
-
.map((message) => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
createdAt: Date.parse(message.at) || Date.now(),
|
|
232
|
-
...(content && content.length > 0 ? { content } : {}),
|
|
233
|
-
};
|
|
234
|
-
}));
|
|
223
|
+
.map((message) => ({
|
|
224
|
+
id: message.id,
|
|
225
|
+
role: message.role,
|
|
226
|
+
text: message.text,
|
|
227
|
+
createdAt: Date.parse(message.at) || Date.now(),
|
|
228
|
+
})));
|
|
235
229
|
setStreamingAssistantId(null);
|
|
236
230
|
return true;
|
|
237
231
|
}
|
|
@@ -1,52 +1,27 @@
|
|
|
1
1
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
2
2
|
import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
|
|
3
3
|
import { confirmationToken } from "./format.js";
|
|
4
|
-
import { clampShortcutIndex } from "./slash-palette-nav.js";
|
|
5
4
|
import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
|
|
6
5
|
/** Composer state: input text, @file palette, image attachments, /shortcuts,
|
|
7
6
|
* IME composition guards, auto-grow and server-side draft sync. */
|
|
8
7
|
export function useComposer(params) {
|
|
9
8
|
const { origin, sessionId, refs, setError } = params;
|
|
10
|
-
const [input,
|
|
9
|
+
const [input, setInput] = useState("");
|
|
11
10
|
const [fileOptions, setFileOptions] = useState([]);
|
|
12
11
|
const [pendingImages, setPendingImages] = useState([]);
|
|
13
12
|
const [shortcutOpen, setShortcutOpen] = useState(false);
|
|
14
|
-
/** Any composer text that starts with `/` should reveal the palette,
|
|
15
|
-
* including draft restore. Escape can still close it without clearing. */
|
|
16
|
-
const setInput = useCallback((value) => {
|
|
17
|
-
if (typeof value === "function") {
|
|
18
|
-
setInputState((prev) => {
|
|
19
|
-
const next = value(prev);
|
|
20
|
-
setShortcutOpen(next.startsWith("/"));
|
|
21
|
-
return next;
|
|
22
|
-
});
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
setInputState(value);
|
|
26
|
-
setShortcutOpen(value.startsWith("/"));
|
|
27
|
-
}, []);
|
|
28
13
|
const [shortcutIndex, setShortcutIndex] = useState(0);
|
|
29
14
|
const [dynamicCommands, setDynamicCommands] = useState([]);
|
|
30
15
|
const [dynamicCommandsLoading, setDynamicCommandsLoading] = useState(false);
|
|
31
16
|
const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
|
|
32
17
|
const [commandsEpoch, setCommandsEpoch] = useState(0);
|
|
33
18
|
const textareaRef = useRef(null);
|
|
34
|
-
|
|
35
|
-
const commandsSessionRef = useRef(sessionId);
|
|
36
|
-
const commandsGenerationRef = useRef(0);
|
|
19
|
+
const commandsSessionRef = useRef(undefined);
|
|
37
20
|
const handleInputChange = useCallback((value) => {
|
|
38
21
|
setInput(value);
|
|
39
22
|
setShortcutOpen(value.startsWith("/"));
|
|
40
23
|
setShortcutIndex(0);
|
|
41
24
|
}, []);
|
|
42
|
-
// Immediate invalidation on A→B even when B palette never opens (AC-2).
|
|
43
|
-
useLayoutEffect(() => {
|
|
44
|
-
commandsSessionRef.current = sessionId;
|
|
45
|
-
commandsGenerationRef.current += 1;
|
|
46
|
-
setDynamicCommands([]);
|
|
47
|
-
setDynamicCommandsError(null);
|
|
48
|
-
setDynamicCommandsLoading(false);
|
|
49
|
-
}, [sessionId]);
|
|
50
25
|
const loadDynamicCommands = useCallback(async () => {
|
|
51
26
|
if (!sessionId) {
|
|
52
27
|
setDynamicCommands([]);
|
|
@@ -55,15 +30,12 @@ export function useComposer(params) {
|
|
|
55
30
|
return;
|
|
56
31
|
}
|
|
57
32
|
const target = sessionId;
|
|
58
|
-
const generation = commandsGenerationRef.current;
|
|
59
33
|
commandsSessionRef.current = target;
|
|
60
34
|
setDynamicCommandsLoading(true);
|
|
61
35
|
setDynamicCommandsError(null);
|
|
62
|
-
const isCurrent = () => commandsSessionRef.current === target &&
|
|
63
|
-
commandsGenerationRef.current === generation;
|
|
64
36
|
try {
|
|
65
37
|
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(target)}/commands`, { credentials: "include" });
|
|
66
|
-
if (
|
|
38
|
+
if (commandsSessionRef.current !== target)
|
|
67
39
|
return;
|
|
68
40
|
if (!res.ok) {
|
|
69
41
|
setDynamicCommandsError("动态命令加载失败");
|
|
@@ -89,27 +61,26 @@ export function useComposer(params) {
|
|
|
89
61
|
source,
|
|
90
62
|
});
|
|
91
63
|
}
|
|
92
|
-
if (
|
|
64
|
+
if (commandsSessionRef.current !== target)
|
|
93
65
|
return;
|
|
94
66
|
setDynamicCommands(items);
|
|
95
|
-
// Compact server fail-soft / partial-source warning; local Operator/Web still merge.
|
|
96
|
-
const warning = typeof body?.warning === "string" ? body.warning.trim() : "";
|
|
97
|
-
setDynamicCommandsError(warning ? warning.slice(0, 160) : null);
|
|
98
67
|
}
|
|
99
68
|
catch {
|
|
100
|
-
if (
|
|
69
|
+
if (commandsSessionRef.current !== target)
|
|
101
70
|
return;
|
|
102
71
|
setDynamicCommandsError("动态命令加载失败");
|
|
103
72
|
setDynamicCommands([]);
|
|
104
73
|
}
|
|
105
74
|
finally {
|
|
106
|
-
if (
|
|
75
|
+
if (commandsSessionRef.current === target) {
|
|
107
76
|
setDynamicCommandsLoading(false);
|
|
108
77
|
}
|
|
109
78
|
}
|
|
110
79
|
}, [origin, sessionId]);
|
|
111
80
|
// Lazy-load dynamic commands when slash palette opens or session changes.
|
|
112
81
|
useEffect(() => {
|
|
82
|
+
setDynamicCommands([]);
|
|
83
|
+
setDynamicCommandsError(null);
|
|
113
84
|
if (!sessionId)
|
|
114
85
|
return;
|
|
115
86
|
if (!shortcutOpen && commandsEpoch === 0)
|
|
@@ -201,14 +172,6 @@ export function useComposer(params) {
|
|
|
201
172
|
const filteredShortcuts = useMemo(() => input.startsWith("/")
|
|
202
173
|
? filterSlashCommands(mergedCommands, input)
|
|
203
174
|
: [], [input, mergedCommands]);
|
|
204
|
-
// Keep the active option index inside the current candidate range (UI-12
|
|
205
|
-
// AC-011): dynamic loads completing/failing empty, retries, session
|
|
206
|
-
// switches, late responses and search shrink all funnel through
|
|
207
|
-
// filteredShortcuts.length. The clamped index always matches a rendered
|
|
208
|
-
// `oc-shortcut-option-${globalIndex}` id (same source of truth).
|
|
209
|
-
useEffect(() => {
|
|
210
|
-
setShortcutIndex((index) => clampShortcutIndex(index, filteredShortcuts.length));
|
|
211
|
-
}, [filteredShortcuts.length]);
|
|
212
175
|
const onCompositionStart = useCallback(() => {
|
|
213
176
|
refs.isComposingRef.current = true;
|
|
214
177
|
}, [refs]);
|
|
@@ -17,13 +17,9 @@ export function resolveWorkspaceLayoutMode(widthPx) {
|
|
|
17
17
|
return "compact";
|
|
18
18
|
return "narrow";
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* invalidates Activity Rail's fixed ChatMain anchor.
|
|
24
|
-
*/
|
|
25
|
-
export function processUsesOverlay(_mode) {
|
|
26
|
-
return true;
|
|
20
|
+
/** Whether process should render as a right overlay (no main reflow). */
|
|
21
|
+
export function processUsesOverlay(mode) {
|
|
22
|
+
return mode === "compact" || mode === "narrow";
|
|
27
23
|
}
|
|
28
24
|
/** Whether the permanent full sidebar is allowed. */
|
|
29
25
|
export function fullSidebarAllowed(mode) {
|