@tea-agent/loop-agent 0.42.0 → 0.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -1
- package/dist/application/dag/run-dag.js +5 -0
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +1229 -584
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +5 -2
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +143 -0
- package/dist/worker/console/chat/browser-routes.js +148 -0
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime.js +131 -95
- package/dist/worker/console/chat/routes.js +180 -54
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +284 -0
- package/dist/worker/console/chat/terminal-tools.js +199 -0
- package/dist/worker/console/chat/tool-preview.js +11 -0
- package/dist/worker/console/chat/tools.js +2 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-VJWsxWhB.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
- package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
- package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
- package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
- package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-G1JR40SX.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-CCgPF0oA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +31 -18
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +39 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/relations.js +2 -0
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +260 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +50 -3
- package/dist/worker/observe/static/views/dag-inspector.js +753 -265
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +49 -11
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +189 -240
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +13 -0
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +10 -3
- package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-run.js +4 -0
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +30 -1
- package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +16 -10
- package/dist/workflows/dag/node-execution.js +38 -155
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/rerun-plan.js +7 -1
- package/dist/workflows/dag/runner.js +26 -1
- package/dist/workflows/dag/types.js +6 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/package.json +6 -3
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +9 -2
- package/skills/frontend-plan/references/decision-contract.md +18 -5
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
- package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
|
@@ -1,25 +1,35 @@
|
|
|
1
|
-
/** Insert the optimistic user bubble and assistant slot exactly once.
|
|
1
|
+
/** Insert the optimistic user bubble and assistant slot exactly once.
|
|
2
|
+
*
|
|
3
|
+
* The optimistic user row is the ONLY row carrying `pending: true`. Every
|
|
4
|
+
* rollback path deletes pending rows by clientRequestId; a durable row that
|
|
5
|
+
* already replaced it must survive (2026-09-08). */
|
|
2
6
|
export function appendPendingSubmission(messages, pending) {
|
|
3
7
|
if (messages.some((message) => message.clientRequestId === pending.clientRequestId))
|
|
4
8
|
return [...messages];
|
|
5
|
-
return [...messages, pending.user, pending.assistant];
|
|
9
|
+
return [...messages, { ...pending.user, pending: true }, pending.assistant];
|
|
6
10
|
}
|
|
7
|
-
/**
|
|
11
|
+
/** Merge a durable user message into its optimistic or snapshot row in place.
|
|
12
|
+
*
|
|
13
|
+
* The replacement drops the `pending` marker: the row is server truth from now
|
|
14
|
+
* on, so a later adopt/rejected/connect rollback for the same clientRequestId
|
|
15
|
+
* must not delete it. Matching the durable id also dedupes retained SSE replay
|
|
16
|
+
* against a session snapshot whose stored row predates clientRequestId. */
|
|
8
17
|
export function replacePendingUserMessage(messages, clientRequestId, durable) {
|
|
9
18
|
let replaced = false;
|
|
10
|
-
const next =
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
const next = [];
|
|
20
|
+
for (const message of messages) {
|
|
21
|
+
const matches = message.clientRequestId === clientRequestId || message.id === durable.id;
|
|
22
|
+
if (!matches) {
|
|
23
|
+
next.push(message);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
13
26
|
if (replaced)
|
|
14
|
-
|
|
27
|
+
continue;
|
|
15
28
|
replaced = true;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
? { ...message, ...durable, clientRequestId }
|
|
21
|
-
: message)
|
|
22
|
-
: [...next, durable];
|
|
29
|
+
const { pending: _pending, ...rest } = message;
|
|
30
|
+
next.push({ ...rest, ...durable, clientRequestId });
|
|
31
|
+
}
|
|
32
|
+
return replaced ? next : [...next, durable];
|
|
23
33
|
}
|
|
24
34
|
/** Keep an unconfirmed optimistic bubble when a recovery snapshot lacks it. */
|
|
25
35
|
export function mergePendingSubmissionOnHydration(canonical, pending) {
|
|
@@ -27,6 +37,8 @@ export function mergePendingSubmissionOnHydration(canonical, pending) {
|
|
|
27
37
|
return [...canonical];
|
|
28
38
|
return appendPendingSubmission(canonical, pending);
|
|
29
39
|
}
|
|
40
|
+
/** Drop only the unconfirmed optimistic rows for a submission. A durable row
|
|
41
|
+
* that already replaced the optimistic one is left untouched. */
|
|
30
42
|
export function removePendingSubmission(messages, clientRequestId) {
|
|
31
|
-
return messages.filter((message) => message.clientRequestId !== clientRequestId);
|
|
43
|
+
return messages.filter((message) => message.clientRequestId !== clientRequestId || message.pending !== true);
|
|
32
44
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isSubagentTool } from "../../chat/tool-preview.js";
|
|
2
|
+
/**
|
|
3
|
+
* Count the collapsed-turn disclosure buckets exactly like DSH
|
|
4
|
+
* `TurnProcessNodeView`: tool calls, process messages and subagents, in that
|
|
5
|
+
* order. Built-in subagent delegations (`Agent` tool) count as subagents, not
|
|
6
|
+
* as ordinary tool calls (2026-09-08 alignment).
|
|
7
|
+
*
|
|
8
|
+
* @param items - ordered process nodes of one turn.
|
|
9
|
+
* @returns the three bucket counts.
|
|
10
|
+
*/
|
|
11
|
+
export function countTurnProcessItems(items) {
|
|
12
|
+
let toolCalls = 0;
|
|
13
|
+
let messages = 0;
|
|
14
|
+
let subagents = 0;
|
|
15
|
+
for (const item of items) {
|
|
16
|
+
if (item.kind === "tool") {
|
|
17
|
+
if (isSubagentTool(item.tool.toolName))
|
|
18
|
+
subagents += 1;
|
|
19
|
+
else
|
|
20
|
+
toolCalls += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
messages += 1;
|
|
24
|
+
}
|
|
25
|
+
return { toolCalls, messages, subagents };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Summary label for a completed turn's process disclosure.
|
|
29
|
+
*
|
|
30
|
+
* @param counts - bucket counts from {@link countTurnProcessItems}.
|
|
31
|
+
* @returns `N 次工具调用 · M 条消息 · K 个 subagent`, or「已思考」when empty.
|
|
32
|
+
*/
|
|
33
|
+
export function buildTurnProcessLabel(counts) {
|
|
34
|
+
return ([
|
|
35
|
+
counts.toolCalls > 0 ? `${counts.toolCalls} 次工具调用` : "",
|
|
36
|
+
counts.messages > 0 ? `${counts.messages} 条消息` : "",
|
|
37
|
+
counts.subagents > 0 ? `${counts.subagents} 个 subagent` : "",
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(" · ") || "已思考");
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function sameReferences(previous, next) {
|
|
2
|
+
return (previous.length === next.length &&
|
|
3
|
+
previous.every((value, index) => value === next[index]));
|
|
4
|
+
}
|
|
5
|
+
/** A completed group's rendered projection is reusable only while every source
|
|
6
|
+
* object that feeds grouping and process-item derivation remains unchanged. */
|
|
7
|
+
export function sameTurnGroupInputs(previous, next) {
|
|
8
|
+
return (previous.key === next.key &&
|
|
9
|
+
previous.user === next.user &&
|
|
10
|
+
previous.live === next.live &&
|
|
11
|
+
sameReferences(previous.assistants, next.assistants) &&
|
|
12
|
+
sameReferences(previous.tools, next.tools));
|
|
13
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef } from "react";
|
|
2
|
+
/** Whether this browser can reveal `hidden="until-found"` content through
|
|
3
|
+
* `beforematch` (Chrome 102+, Firefox 148+, Safari: not supported at all).
|
|
4
|
+
* `content-visibility` support does NOT imply this — Safari 18+ and
|
|
5
|
+
* Firefox 125–147 have the former without the latter. */
|
|
6
|
+
function supportsUntilFound() {
|
|
7
|
+
return typeof document !== "undefined" && "onbeforematch" in document.body;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Collapse content without removing it from the document: the element gets
|
|
11
|
+
* `hidden="until-found"` so browser find-in-page can still reach the text, and
|
|
12
|
+
* a match fires `beforematch`, which reveals (expands) the disclosure again.
|
|
13
|
+
*
|
|
14
|
+
* Mirrors DSH `useSearchableHidden` (2026-09-08 alignment). Browsers without
|
|
15
|
+
* `until-found` support get a plain `hidden=""` element, which keeps the
|
|
16
|
+
* pre-alignment behavior (collapsed and not searchable).
|
|
17
|
+
*
|
|
18
|
+
* @param hidden - whether the content is collapsed.
|
|
19
|
+
* @param reveal - expand callback invoked on `beforematch` and when the
|
|
20
|
+
* collapsed subtree already owns focus.
|
|
21
|
+
* @returns ref for the collapsible container.
|
|
22
|
+
*/
|
|
23
|
+
export function useSearchableHidden(hidden, reveal) {
|
|
24
|
+
const ref = useRef(null);
|
|
25
|
+
useLayoutEffect(() => {
|
|
26
|
+
const element = ref.current;
|
|
27
|
+
if (element === null)
|
|
28
|
+
return;
|
|
29
|
+
// Focus would otherwise be lost inside a hidden subtree.
|
|
30
|
+
if (hidden && element.contains(element.ownerDocument.activeElement)) {
|
|
31
|
+
reveal();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (hidden) {
|
|
35
|
+
element.setAttribute("hidden", supportsUntilFound() ? "until-found" : "");
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
element.removeAttribute("hidden");
|
|
39
|
+
}
|
|
40
|
+
}, [hidden, reveal]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const element = ref.current;
|
|
43
|
+
if (element === null)
|
|
44
|
+
return;
|
|
45
|
+
element.addEventListener("beforematch", reveal);
|
|
46
|
+
return () => {
|
|
47
|
+
element.removeEventListener("beforematch", reveal);
|
|
48
|
+
};
|
|
49
|
+
}, [reveal]);
|
|
50
|
+
return ref;
|
|
51
|
+
}
|
|
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { runtimeSelectionFromRecord } from "../../chat/runtime-selection.js";
|
|
3
3
|
import { hydrateSessionThread } from "../../chat/turn-process.js";
|
|
4
4
|
import { forgetActiveChatSession, readActiveChatSessionId, rememberActiveChatSession, } from "../../chat/workspace-landing.js";
|
|
5
|
+
import { DEFAULT_CONSOLE_CHAT_SESSION_MODE } from "../../chat/session-mode-view.js";
|
|
5
6
|
import { confirmationToken } from "./format.js";
|
|
6
7
|
import { isLostChatSessionError } from "./runtime-snapshot-store.js";
|
|
7
8
|
import { upsertCompactionMessage } from "./compaction-message.js";
|
|
@@ -67,6 +68,8 @@ function sameSessionSummary(a, b) {
|
|
|
67
68
|
a.model?.modelId === b.model?.modelId &&
|
|
68
69
|
a.thinkingLevel === b.thinkingLevel &&
|
|
69
70
|
a.isBlank === b.isBlank &&
|
|
71
|
+
(a.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE) ===
|
|
72
|
+
(b.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE) &&
|
|
70
73
|
a.optimisticState === b.optimisticState &&
|
|
71
74
|
sameQueueSummary(a, b));
|
|
72
75
|
}
|
|
@@ -83,8 +86,18 @@ export function stabilizeSessionList(previous, next) {
|
|
|
83
86
|
});
|
|
84
87
|
return changed ? stabilized : previous;
|
|
85
88
|
}
|
|
89
|
+
/** Project a session detail/list record into the active ChatSessionInfo. */
|
|
90
|
+
export function projectActiveChatSession(session, fallbackWorkspaceId) {
|
|
91
|
+
const workspaceId = session.workspaceId?.trim() || fallbackWorkspaceId?.trim() || "";
|
|
92
|
+
return {
|
|
93
|
+
sessionId: session.sessionId,
|
|
94
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
95
|
+
sessionMode: session.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE,
|
|
96
|
+
...runtimeSelectionFromRecord(session),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
86
99
|
/** Select a server-confirmed blank session owned by the requested workspace. */
|
|
87
|
-
export function findReusableBlankSession(list, workspaceId) {
|
|
100
|
+
export function findReusableBlankSession(list, workspaceId, sessionMode = DEFAULT_CONSOLE_CHAT_SESSION_MODE) {
|
|
88
101
|
const scope = workspaceId.trim();
|
|
89
102
|
if (!scope)
|
|
90
103
|
return undefined;
|
|
@@ -93,7 +106,8 @@ export function findReusableBlankSession(list, workspaceId) {
|
|
|
93
106
|
item.isBlank === true &&
|
|
94
107
|
item.turnPhase === "idle" &&
|
|
95
108
|
!item.activeTurnId &&
|
|
96
|
-
!item.optimisticState
|
|
109
|
+
!item.optimisticState &&
|
|
110
|
+
(item.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE) === sessionMode);
|
|
97
111
|
}
|
|
98
112
|
export async function createSessionAndPrimeTitleWatcher(input) {
|
|
99
113
|
if (input.creatingRef?.current) {
|
|
@@ -127,7 +141,13 @@ export async function createSessionAndPrimeTitleWatcher(input) {
|
|
|
127
141
|
const body = (await res.json());
|
|
128
142
|
if (!body.session)
|
|
129
143
|
throw new Error("创建会话响应缺少 durable session");
|
|
130
|
-
const durable = {
|
|
144
|
+
const durable = {
|
|
145
|
+
...body.session,
|
|
146
|
+
isBlank: true,
|
|
147
|
+
sessionMode: body.session.sessionMode ??
|
|
148
|
+
input.options?.sessionMode ??
|
|
149
|
+
DEFAULT_CONSOLE_CHAT_SESSION_MODE,
|
|
150
|
+
};
|
|
131
151
|
const created = {
|
|
132
152
|
sessionId: durable.sessionId,
|
|
133
153
|
...(durable.workspaceId || input.options?.workspaceId
|
|
@@ -135,6 +155,7 @@ export async function createSessionAndPrimeTitleWatcher(input) {
|
|
|
135
155
|
workspaceId: durable.workspaceId ?? input.options?.workspaceId,
|
|
136
156
|
}
|
|
137
157
|
: {}),
|
|
158
|
+
sessionMode: durable.sessionMode,
|
|
138
159
|
...runtimeSelectionFromRecord(durable),
|
|
139
160
|
};
|
|
140
161
|
const nextList = replaceOptimisticSession(input.sessionListRef.current, input.options?.optimisticSessionId, durable);
|
|
@@ -402,11 +423,7 @@ export function useChatSessions(params) {
|
|
|
402
423
|
}
|
|
403
424
|
if (selectionVersion !== sessionSelectionVersionRef.current)
|
|
404
425
|
return false;
|
|
405
|
-
const next =
|
|
406
|
-
sessionId: body.session.sessionId,
|
|
407
|
-
...(body.session.workspaceId ? { workspaceId: body.session.workspaceId } : {}),
|
|
408
|
-
...runtimeSelectionFromRecord(body.session),
|
|
409
|
-
};
|
|
426
|
+
const next = projectActiveChatSession(body.session);
|
|
410
427
|
// AC-002: session switch (same or cross workspace) never aborts other
|
|
411
428
|
// sessions' background request controllers; the single /events owner is
|
|
412
429
|
// re-bound by useChatStream's session effect.
|
|
@@ -538,15 +555,7 @@ export function useChatSessions(params) {
|
|
|
538
555
|
selectionVersion !== sessionSelectionVersionRef.current ||
|
|
539
556
|
!body.session)
|
|
540
557
|
return;
|
|
541
|
-
const restored =
|
|
542
|
-
sessionId: body.session.sessionId,
|
|
543
|
-
...(body.session.workspaceId
|
|
544
|
-
? { workspaceId: body.session.workspaceId }
|
|
545
|
-
: workspaceId
|
|
546
|
-
? { workspaceId }
|
|
547
|
-
: {}),
|
|
548
|
-
...runtimeSelectionFromRecord(body.session),
|
|
549
|
-
};
|
|
558
|
+
const restored = projectActiveChatSession(body.session, workspaceId);
|
|
550
559
|
refs.sessionGenerationRef.current += 1;
|
|
551
560
|
refs.sessionRef.current = restored;
|
|
552
561
|
// Do not seed Last-Event-ID here: session messages are restored
|
|
@@ -619,6 +628,7 @@ export function useChatSessions(params) {
|
|
|
619
628
|
setError("创建会话需要选择一个有效的工作区(不会回退默认工作区)");
|
|
620
629
|
return Promise.reject(new Error("WORKSPACE_REQUIRED"));
|
|
621
630
|
}
|
|
631
|
+
const sessionMode = options?.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE;
|
|
622
632
|
const optimisticSessionId = `optimistic-chat-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
623
633
|
const now = new Date().toISOString();
|
|
624
634
|
const optimisticRow = {
|
|
@@ -627,6 +637,7 @@ export function useChatSessions(params) {
|
|
|
627
637
|
state: "active",
|
|
628
638
|
updatedAt: now,
|
|
629
639
|
turnPhase: "idle",
|
|
640
|
+
sessionMode,
|
|
630
641
|
optimisticState: "creating",
|
|
631
642
|
};
|
|
632
643
|
pendingCreatedRowsRef.current.set(optimisticSessionId, optimisticRow);
|
|
@@ -669,9 +680,10 @@ export function useChatSessions(params) {
|
|
|
669
680
|
}
|
|
670
681
|
// A caller-provided snapshot is an explicit new-session boundary; reusing
|
|
671
682
|
// an existing shell would silently drop that context suffix.
|
|
683
|
+
const sessionMode = options?.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE;
|
|
672
684
|
const reusable = options?.snapshot
|
|
673
685
|
? undefined
|
|
674
|
-
: findReusableBlankSession(sessionListRef.current, scope);
|
|
686
|
+
: findReusableBlankSession(sessionListRef.current, scope, sessionMode);
|
|
675
687
|
if (reusable) {
|
|
676
688
|
if (previousSession?.sessionId === reusable.sessionId)
|
|
677
689
|
return previousSession;
|
|
@@ -681,6 +693,7 @@ export function useChatSessions(params) {
|
|
|
681
693
|
return {
|
|
682
694
|
sessionId: reusable.sessionId,
|
|
683
695
|
...(reusable.workspaceId ? { workspaceId: reusable.workspaceId } : {}),
|
|
696
|
+
sessionMode: reusable.sessionMode ?? sessionMode,
|
|
684
697
|
...runtimeSelectionFromRecord(reusable),
|
|
685
698
|
};
|
|
686
699
|
}
|
|
@@ -53,7 +53,11 @@ function clearEmptyAssistant(setMessages, assistantId) {
|
|
|
53
53
|
return;
|
|
54
54
|
setMessages((messages) => messages.filter((message) => !(message.id === assistantId &&
|
|
55
55
|
message.role === "assistant" &&
|
|
56
|
-
!message.text.trim()
|
|
56
|
+
!message.text.trim() &&
|
|
57
|
+
// A row carrying only thinking/tool blocks is real content. Deleting
|
|
58
|
+
// it here dropped the turn's process nodes while the settled path in
|
|
59
|
+
// chat-sse-events deliberately keeps them (2026-09-08).
|
|
60
|
+
!(message.content && message.content.length > 0))));
|
|
57
61
|
}
|
|
58
62
|
/** One GET /events stream owns all Chat events. Sending a prompt creates a
|
|
59
63
|
* detached Turn resource only; it never opens a second inline SSE consumer. */
|
|
@@ -140,7 +144,10 @@ export function useChatStream(params) {
|
|
|
140
144
|
current.generation === generation)
|
|
141
145
|
current.ready = true;
|
|
142
146
|
replay = false;
|
|
143
|
-
|
|
147
|
+
// Fall through: the applier owns the buffered-replay flush and
|
|
148
|
+
// returns immediately for this event. `continue` here silently
|
|
149
|
+
// dropped that flush, so a reconnect that landed after the
|
|
150
|
+
// turn settled never restored the answer (2026-09-08).
|
|
144
151
|
}
|
|
145
152
|
const assistantId = refs.assistantIdRef.current ?? fallbackAssistantId;
|
|
146
153
|
const eventId = applySseBlock(block, assistantId, {
|
|
@@ -713,6 +720,15 @@ export function useChatStream(params) {
|
|
|
713
720
|
if (refs.sessionRef.current?.sessionId !== activeSession.sessionId ||
|
|
714
721
|
refs.sessionGenerationRef.current !== generation) {
|
|
715
722
|
releaseSubmitOwnership(clientRequestId);
|
|
723
|
+
if (refs.sessionRef.current?.sessionId === activeSession.sessionId) {
|
|
724
|
+
// Same session, newer generation: the activation snapshot replaced
|
|
725
|
+
// the thread, so the optimistic row is gone. Restore the draft
|
|
726
|
+
// instead of losing the user's text silently (2026-09-08).
|
|
727
|
+
setMessages((messages) => removePendingSubmission(messages, clientRequestId));
|
|
728
|
+
clearEmptyAssistant(setMessages, assistantId);
|
|
729
|
+
setInput(draftText);
|
|
730
|
+
setPendingImages(draftImages);
|
|
731
|
+
}
|
|
716
732
|
return;
|
|
717
733
|
}
|
|
718
734
|
// Attachments stay until accepted (AC/UIS-accepted).
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
2
2
|
import { buildTurnProcessGroups, hydrateSessionThread, selectRecentTurnGroups, } from "../../chat/turn-process.js";
|
|
3
|
+
import { isToolFailure } from "../../chat/tool-preview.js";
|
|
3
4
|
import { mergeOperationCardState, } from "../../chat/operation-card.js";
|
|
4
5
|
import { createChatSseEventApplier, } from "./chat-sse-events.js";
|
|
5
6
|
import { mergePendingSubmissionOnHydration } from "./pending-user-message.js";
|
|
6
7
|
import { taskContextUnchanged } from "./format.js";
|
|
7
8
|
import { upsertCompactionMessage } from "./compaction-message.js";
|
|
9
|
+
import { sameTurnGroupInputs } from "./turn-group-equality.js";
|
|
8
10
|
const EMPTY_USAGE = {
|
|
9
11
|
inputTokens: 0,
|
|
10
12
|
outputTokens: 0,
|
|
@@ -115,6 +117,12 @@ export function useChatThread(params) {
|
|
|
115
117
|
// (UI-08); the SSE applier must not open the process panel.
|
|
116
118
|
void params.setProcessOpen;
|
|
117
119
|
const [messages, setMessages] = useState([]);
|
|
120
|
+
/** Rendered-messages mirror for the replay flush: it must resolve its target
|
|
121
|
+
* row synchronously, before React commits the queued updater (2026-09-08). */
|
|
122
|
+
const messagesRef = useRef([]);
|
|
123
|
+
useLayoutEffect(() => {
|
|
124
|
+
messagesRef.current = messages;
|
|
125
|
+
}, [messages]);
|
|
118
126
|
const [toolCalls, setToolCalls] = useState([]);
|
|
119
127
|
const [operationCards, setOperationCards] = useState([]);
|
|
120
128
|
const [artifactCards, setArtifactCards] = useState([]);
|
|
@@ -187,6 +195,7 @@ export function useChatThread(params) {
|
|
|
187
195
|
onTelemetryRefresh: () => void refreshSessionTelemetry(),
|
|
188
196
|
setMessages,
|
|
189
197
|
setToolCalls,
|
|
198
|
+
getMessages: () => messagesRef.current,
|
|
190
199
|
}), [refs, onReconcile, setError, refreshSessionTelemetry]);
|
|
191
200
|
/** Parse + apply one raw SSE block (frames separated by \n\n). Returns the
|
|
192
201
|
* eventId if the block carried an `id:` line (for Last-Event-ID tracking). */
|
|
@@ -285,7 +294,7 @@ export function useChatThread(params) {
|
|
|
285
294
|
if (toolCalls.length === 0)
|
|
286
295
|
return null;
|
|
287
296
|
const running = toolCalls.filter((t) => t.status === "running");
|
|
288
|
-
const errored = toolCalls.filter((t) => t
|
|
297
|
+
const errored = toolCalls.filter((t) => isToolFailure(t));
|
|
289
298
|
const current = running[running.length - 1];
|
|
290
299
|
let statusLabel = "完成";
|
|
291
300
|
let statusKind = "ok";
|
|
@@ -348,7 +357,9 @@ export function useChatThread(params) {
|
|
|
348
357
|
const previous = new Map(completedGroupsRef.current.map((group) => [group.key, group]));
|
|
349
358
|
const all = built.map((group) => {
|
|
350
359
|
const cached = previous.get(group.key);
|
|
351
|
-
return cached && !group.live
|
|
360
|
+
return cached && !group.live && sameTurnGroupInputs(cached, group)
|
|
361
|
+
? cached
|
|
362
|
+
: group;
|
|
352
363
|
});
|
|
353
364
|
completedGroupsRef.current = all.filter((group) => !group.live);
|
|
354
365
|
previousTimelineRef.current = {
|
|
@@ -450,7 +461,31 @@ export function useChatThread(params) {
|
|
|
450
461
|
/** Adopt a freshly activated session's hydrated thread + streaming flags. */
|
|
451
462
|
const applyActivatedSession = useCallback((next) => {
|
|
452
463
|
const visuallyStopped = next.turnPhase === "stopping";
|
|
453
|
-
|
|
464
|
+
// A submission still owned for THIS session keeps its optimistic bubble
|
|
465
|
+
// across an activation snapshot (refresh / reopen); a lease owned by
|
|
466
|
+
// another session must never leak into this thread (2026-09-08).
|
|
467
|
+
const ownedPending = refs.pendingSubmissionRef.current?.sessionId ===
|
|
468
|
+
refs.sessionRef.current?.sessionId
|
|
469
|
+
? refs.pendingSubmissionRef.current
|
|
470
|
+
: undefined;
|
|
471
|
+
setMessages(mergePendingSubmissionOnHydration(next.messages, ownedPending
|
|
472
|
+
? {
|
|
473
|
+
clientRequestId: ownedPending.clientRequestId,
|
|
474
|
+
user: {
|
|
475
|
+
id: ownedPending.userMessageId,
|
|
476
|
+
role: "user",
|
|
477
|
+
text: ownedPending.text,
|
|
478
|
+
createdAt: Date.now(),
|
|
479
|
+
clientRequestId: ownedPending.clientRequestId,
|
|
480
|
+
},
|
|
481
|
+
assistant: {
|
|
482
|
+
id: ownedPending.assistantId,
|
|
483
|
+
role: "assistant",
|
|
484
|
+
text: "",
|
|
485
|
+
createdAt: Date.now(),
|
|
486
|
+
},
|
|
487
|
+
}
|
|
488
|
+
: undefined));
|
|
454
489
|
setToolCalls(next.toolCalls);
|
|
455
490
|
setOperationCards([]);
|
|
456
491
|
setHumanGateCards([]);
|
|
@@ -7,7 +7,7 @@ import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
|
|
|
7
7
|
/** Composer state: input text, @file palette, image attachments, /shortcuts,
|
|
8
8
|
* IME composition guards, auto-grow and server-side draft sync. */
|
|
9
9
|
export function useComposer(params) {
|
|
10
|
-
const { origin, sessionId, refs, setError, onUnrecoverableSession } = params;
|
|
10
|
+
const { origin, sessionId, sessionMode, refs, setError, onUnrecoverableSession, } = params;
|
|
11
11
|
const [input, setInputState] = useState("");
|
|
12
12
|
const [fileOptions, setFileOptions] = useState([]);
|
|
13
13
|
const [pendingImages, setPendingImages] = useState([]);
|
|
@@ -28,7 +28,10 @@ export function useComposer(params) {
|
|
|
28
28
|
}, []);
|
|
29
29
|
const [shortcutIndex, setShortcutIndex] = useState(0);
|
|
30
30
|
const [dynamicCommands, setDynamicCommands] = useState([]);
|
|
31
|
-
const [commandCapabilities, setCommandCapabilities] = useState({
|
|
31
|
+
const [commandCapabilities, setCommandCapabilities] = useState({
|
|
32
|
+
sddBingo: false,
|
|
33
|
+
sessionMode: undefined,
|
|
34
|
+
});
|
|
32
35
|
const [dynamicCommandsLoading, setDynamicCommandsLoading] = useState(false);
|
|
33
36
|
const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
|
|
34
37
|
const [commandsEpoch, setCommandsEpoch] = useState(0);
|
|
@@ -75,10 +78,10 @@ export function useComposer(params) {
|
|
|
75
78
|
commandsSessionRef.current = sessionId;
|
|
76
79
|
commandsGenerationRef.current += 1;
|
|
77
80
|
setDynamicCommands([]);
|
|
78
|
-
setCommandCapabilities({ sddBingo: false });
|
|
81
|
+
setCommandCapabilities({ sddBingo: false, sessionMode });
|
|
79
82
|
setDynamicCommandsError(null);
|
|
80
83
|
setDynamicCommandsLoading(false);
|
|
81
|
-
}, [sessionId]);
|
|
84
|
+
}, [sessionId, sessionMode]);
|
|
82
85
|
const loadDynamicCommands = useCallback(async () => {
|
|
83
86
|
if (!sessionId) {
|
|
84
87
|
setDynamicCommands([]);
|
|
@@ -124,7 +127,10 @@ export function useComposer(params) {
|
|
|
124
127
|
if (!isCurrent())
|
|
125
128
|
return;
|
|
126
129
|
setDynamicCommands(items);
|
|
127
|
-
setCommandCapabilities({
|
|
130
|
+
setCommandCapabilities({
|
|
131
|
+
sddBingo: body?.capabilities?.sddBingo === true,
|
|
132
|
+
sessionMode: body?.capabilities?.sessionMode ?? sessionMode,
|
|
133
|
+
});
|
|
128
134
|
// Compact server fail-soft / partial-source warning; local Operator/Web still merge.
|
|
129
135
|
const warning = typeof body?.warning === "string" ? body.warning.trim() : "";
|
|
130
136
|
setDynamicCommandsError(warning ? warning.slice(0, 160) : null);
|
|
@@ -134,14 +140,14 @@ export function useComposer(params) {
|
|
|
134
140
|
return;
|
|
135
141
|
setDynamicCommandsError("动态命令加载失败");
|
|
136
142
|
setDynamicCommands([]);
|
|
137
|
-
setCommandCapabilities({ sddBingo: false });
|
|
143
|
+
setCommandCapabilities({ sddBingo: false, sessionMode });
|
|
138
144
|
}
|
|
139
145
|
finally {
|
|
140
146
|
if (isCurrent()) {
|
|
141
147
|
setDynamicCommandsLoading(false);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
|
-
}, [origin, sessionId]);
|
|
150
|
+
}, [origin, sessionId, sessionMode]);
|
|
145
151
|
// Lazy-load dynamic commands when slash palette opens or session changes.
|
|
146
152
|
useEffect(() => {
|
|
147
153
|
if (!sessionId)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** User anchors only depend on the ordered user-message objects, not on
|
|
2
|
+
* assistant/process updates that rebuild their containing turn groups. */
|
|
3
|
+
export function sameUserMessages(previous, next) {
|
|
4
|
+
let previousIndex = 0;
|
|
5
|
+
let nextIndex = 0;
|
|
6
|
+
for (;;) {
|
|
7
|
+
while (previousIndex < previous.length && !previous[previousIndex].user) {
|
|
8
|
+
previousIndex += 1;
|
|
9
|
+
}
|
|
10
|
+
while (nextIndex < next.length && !next[nextIndex].user) {
|
|
11
|
+
nextIndex += 1;
|
|
12
|
+
}
|
|
13
|
+
if (previousIndex === previous.length || nextIndex === next.length) {
|
|
14
|
+
return previousIndex === previous.length && nextIndex === next.length;
|
|
15
|
+
}
|
|
16
|
+
if (previous[previousIndex].user !== next[nextIndex].user)
|
|
17
|
+
return false;
|
|
18
|
+
previousIndex += 1;
|
|
19
|
+
nextIndex += 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -37,6 +37,8 @@ import { UserQuestionRegistry } from "./chat/user-questions.js";
|
|
|
37
37
|
import { TodoListRegistry } from "./chat/todos.js";
|
|
38
38
|
import { GoalRegistry } from "./chat/goals.js";
|
|
39
39
|
import { GoalRoundDriver } from "./chat/goal-round-driver.js";
|
|
40
|
+
import { TerminalSessionRegistry } from "./chat/terminal-sessions.js";
|
|
41
|
+
import { ScmService } from "./chat/scm-service.js";
|
|
40
42
|
import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
|
|
41
43
|
import { createOperationEventStore, } from "./operation-sse.js";
|
|
42
44
|
import { OperationStore } from "../../infrastructure/console/operation-store.js";
|
|
@@ -254,7 +256,9 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
254
256
|
}
|
|
255
257
|
},
|
|
256
258
|
});
|
|
257
|
-
const
|
|
259
|
+
const aimaxAuth = new AimaxAuthReader();
|
|
260
|
+
await aimaxAuth.getToken();
|
|
261
|
+
const sddDataAlignment = new SddDataAlignmentService(chatStore, aimaxAuth, deps.fetchImpl);
|
|
258
262
|
const chatEventEpoch = randomBytes(12).toString("hex");
|
|
259
263
|
const chatEvents = createChatEventStore({
|
|
260
264
|
appData,
|
|
@@ -285,6 +289,12 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
285
289
|
operations,
|
|
286
290
|
operationEvents: events,
|
|
287
291
|
});
|
|
292
|
+
// Persistent PTY terminal sessions shared by the model tool (terminal) and
|
|
293
|
+
// the Console UI terminal panel (HTTP API), keyed per chat session owner.
|
|
294
|
+
const terminalRegistry = new TerminalSessionRegistry();
|
|
295
|
+
// Workspace-scoped git source control shared by the model tool (scm) and
|
|
296
|
+
// the Console UI SCM panel (HTTP API).
|
|
297
|
+
const scmService = new ScmService(repoRoot);
|
|
288
298
|
const chatRuntime = new ConsolePiRuntime({
|
|
289
299
|
cwd: repoRoot,
|
|
290
300
|
sessionDir: appData.chatSessions,
|
|
@@ -294,6 +304,8 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
294
304
|
todoRegistry,
|
|
295
305
|
goalRegistry,
|
|
296
306
|
sddDataAlignment,
|
|
307
|
+
terminalRegistry,
|
|
308
|
+
scmService,
|
|
297
309
|
});
|
|
298
310
|
let chatRouteDeps;
|
|
299
311
|
const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
|
|
@@ -308,6 +320,8 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
308
320
|
sddDataAlignment,
|
|
309
321
|
goalRoundDriver,
|
|
310
322
|
operationLinker: chatOperationLinker,
|
|
323
|
+
terminals: terminalRegistry,
|
|
324
|
+
scm: scmService,
|
|
311
325
|
appData,
|
|
312
326
|
bootToken: deps.bootToken,
|
|
313
327
|
getConsoleOrigin: deps.getConsoleOrigin,
|