@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.3
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 +178 -175
- package/dist/build-stamp.json +3 -3
- package/dist/executors/pi-sdk-executor.js +29 -1
- package/dist/shared/wizard-local-telemetry.js +50 -0
- package/dist/worker/console/chat/chat-event-store.js +3 -0
- package/dist/worker/console/chat/db-connections.js +790 -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 +627 -0
- package/dist/worker/console/chat/pi-runtime.js +66 -6
- package/dist/worker/console/chat/routes.js +154 -3
- package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
- 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 +43 -39
- 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 -2
- package/dist/worker/console/server.js +5 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-COQlLTI7.js} +1 -1
- package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-zN3QkNaV.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-Pyoyj5f2.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-BNppA5WW.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-lyxL8nTx.js} +1 -1
- package/dist/worker/console/static/assets/channel-CgLPkTw2.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-0hgjcdK4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-D63N9d4i.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-CUw2AyB5.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-Q_kwZT30.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-C7vMiKcw.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-T9GbV5Hz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-D8nUSUJI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-BCH2ewdp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-BTdDuDBN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-Cqm2RcKF.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuSD1Mhb.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuSD1Mhb.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-pjwfpVu4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-BvalptKA.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BU4qJ6d7.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-DVTpq8J9.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-B8s1W6Fc.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DPrFk9yH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-CnQIQsHC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-op0DEpCv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-DXQamm4T.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-b1ad2C0r.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-B4cD-ZFJ.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-CrgGjlQ1.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-DovmXQNV.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-DpFyu2Jn.js} +1 -1
- package/dist/worker/console/static/assets/index-DHBWEwz_.css +1 -0
- package/dist/worker/console/static/assets/index-JRfTUgP7.js +478 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-D3MoQZIl.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-Ci6C1yjS.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-BWMo2lIP.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdthIAg3.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DuFAlSOj.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-Dqz3Slj9.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-DNVTrD09.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-CgfZPfBh.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-ClM9P7yo.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-DwnEak2J.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-rK4hxt9P.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-Cjqa6vo3.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-D3Fa250Z.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-2PdrNajj.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-BBcuGeZn.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CSJX9X6s.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cqskj0zt.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-pOUU1bic.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CM0ZIhNP.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-CAeqfQn0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-DYPLOMVO.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-Dy2V0Cha.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-Dj9o7jrn.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +33 -1
- 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/pending-user-message.js +17 -3
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +15 -7
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +47 -5
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +24 -2
- package/dist/worker/console/workspace-context.js +2 -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-evidence.js +60 -2
- package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
- package/package.json +3 -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, 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";
|
|
@@ -179,10 +179,11 @@ function mergeCompactionMessages(messages, compactions) {
|
|
|
179
179
|
* compact, plus browser-refresh recovery from the durable session store. */
|
|
180
180
|
export function useChatSessions(params) {
|
|
181
181
|
const { origin, refs, setSession, thread, composer, browser, setError, setNotice, onCompactionChange, setProcessOpen, setAutoScroll, } = params;
|
|
182
|
-
const { applyActivatedSession, resetForNewSession, setMessages } = thread;
|
|
182
|
+
const { applyActivatedSession, resetForNewSession, setMessages, refreshSessionTelemetry, } = thread;
|
|
183
183
|
const { seedSession, releaseSession } = composer;
|
|
184
184
|
const { setPreviews, setActivePreviewPath } = browser;
|
|
185
185
|
const { workspaceId, blocked } = params;
|
|
186
|
+
const visibleWorkspaceIds = useMemo(() => new Set(params.visibleWorkspaceIds), [params.visibleWorkspaceIds]);
|
|
186
187
|
const workspaceQuery = workspaceId
|
|
187
188
|
? `?workspaceId=${encodeURIComponent(workspaceId)}`
|
|
188
189
|
: "";
|
|
@@ -276,10 +277,13 @@ export function useChatSessions(params) {
|
|
|
276
277
|
return;
|
|
277
278
|
}
|
|
278
279
|
const sessions = body.sessions ?? [];
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
const belongsToVisibleWorkspace = (item) => !item.workspaceId || visibleWorkspaceIds.has(item.workspaceId);
|
|
281
|
+
const durable = sessions.filter((item) => belongsToVisibleWorkspace(item) &&
|
|
282
|
+
!pendingDeletedIdsRef.current.has(item.sessionId));
|
|
283
|
+
const optimistic = [...pendingCreatedRowsRef.current.values()].filter((item) => belongsToVisibleWorkspace(item) &&
|
|
284
|
+
!durable.some((session) => session.sessionId === item.sessionId));
|
|
285
|
+
// Preserve summaries for visible workspaces. ChatWorkspaceSidebar applies
|
|
286
|
+
// the recent-item limit independently inside each workspace group.
|
|
283
287
|
const next = stabilizeSessionList(sessionListRef.current, [...optimistic, ...durable]);
|
|
284
288
|
sessionListRef.current = next;
|
|
285
289
|
setSessionList(next);
|
|
@@ -292,7 +296,7 @@ export function useChatSessions(params) {
|
|
|
292
296
|
return;
|
|
293
297
|
setError(`会话列表加载失败:${error instanceof Error ? error.message : String(error)}`);
|
|
294
298
|
}
|
|
295
|
-
}, [origin, blocked, setError]);
|
|
299
|
+
}, [origin, blocked, setError, visibleWorkspaceIds]);
|
|
296
300
|
const deleteSession = useCallback(async (sessionId) => {
|
|
297
301
|
if (pendingDeletedIdsRef.current.has(sessionId))
|
|
298
302
|
return false;
|
|
@@ -500,6 +504,7 @@ export function useChatSessions(params) {
|
|
|
500
504
|
sessionStats: body.sessionStats,
|
|
501
505
|
usage: body.usage,
|
|
502
506
|
turnTelemetry: body.turnTelemetry,
|
|
507
|
+
sddSessionIdentity: body.sddDataAlignment ?? null,
|
|
503
508
|
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
504
509
|
turnPhase: body.session.turnPhase ??
|
|
505
510
|
(body.session.activeTurnId ? "running" : "idle"),
|
|
@@ -634,6 +639,7 @@ export function useChatSessions(params) {
|
|
|
634
639
|
sessionStats: body.sessionStats,
|
|
635
640
|
usage: body.usage,
|
|
636
641
|
turnTelemetry: body.turnTelemetry,
|
|
642
|
+
sddSessionIdentity: body.sddDataAlignment ?? null,
|
|
637
643
|
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
638
644
|
turnPhase: body.session.turnPhase ??
|
|
639
645
|
(body.session.activeTurnId ? "running" : "idle"),
|
|
@@ -760,6 +766,7 @@ export function useChatSessions(params) {
|
|
|
760
766
|
setSelectedSession(created);
|
|
761
767
|
rememberActiveChatSession(window.localStorage, created.workspaceId ?? options?.workspaceId ?? workspaceId, created.sessionId);
|
|
762
768
|
setSession(created);
|
|
769
|
+
void refreshSessionTelemetry();
|
|
763
770
|
return created;
|
|
764
771
|
}
|
|
765
772
|
catch (e) {
|
|
@@ -783,6 +790,7 @@ export function useChatSessions(params) {
|
|
|
783
790
|
activateSession,
|
|
784
791
|
blocked,
|
|
785
792
|
workspaceId,
|
|
793
|
+
refreshSessionTelemetry,
|
|
786
794
|
]);
|
|
787
795
|
// Phase-1: fork / in-session branch / mainline switch UI deferred; API routes remain.
|
|
788
796
|
/** Single compact helper for toolbar + `/compact <instructions>` (AC-1). */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect } from "react";
|
|
2
2
|
import { formatOperatorUserError } from "../../operator-user-error.js";
|
|
3
|
+
import { inspectChatSseContinuity, readChatStreamReadyCursor, selectChatSseRepairBlocks, selectLaterChatEventId, } from "./chat-stream-continuity.js";
|
|
3
4
|
import { confirmationToken, requestId, sleep } from "./format.js";
|
|
4
5
|
import { classifyCreateTurnNetworkFailure, classifyCreateTurnResponse, createPendingSubmission, decideSubmissionRoute, isRuntimeBusyUnion, isSubmissionCurrent, isTerminalTurnState, keepAmbiguousAfterRetry, reconcileDeferredSubmissionFromState, reconcilePendingSubmissionFromState, resolveAmbiguousFromState, } from "./turn-submission.js";
|
|
5
6
|
import { applyAbortDecision, applyPendingReconcileDecision, classifyAbortResponse, markStopRequested, operatorChatAbortUrl, operatorChatEventsUrl, operatorChatStateUrl, operatorChatTurnsUrl, releaseOwnershipOnTerminalEvidence, releaseSubmitOwnership as releaseOwnershipBag, requestStopOnOwnership, settleCreateTurnOutcome, turnPhaseFromOwnership, visualTurnPhaseFromOwnership, } from "./turn-stream-controller.js";
|
|
@@ -104,6 +105,7 @@ export function useChatStream(params) {
|
|
|
104
105
|
* means replay cannot render duplicate messages while live text remains
|
|
105
106
|
* smooth (M4.1/M4.3). */
|
|
106
107
|
const openEventsStream = useCallback(async (sessionId, generation, signal) => {
|
|
108
|
+
const gapReplayBuffer = [];
|
|
107
109
|
while (!signal.aborted) {
|
|
108
110
|
const state = refs.eventsStreamStateRef.current;
|
|
109
111
|
if (!state ||
|
|
@@ -129,21 +131,38 @@ export function useChatStream(params) {
|
|
|
129
131
|
const decoder = new TextDecoder();
|
|
130
132
|
let buffer = "";
|
|
131
133
|
let replay = true;
|
|
134
|
+
let replayRepairNeeded = false;
|
|
135
|
+
let liveContinuityCursor = null;
|
|
132
136
|
const fallbackAssistantId = requestId("a");
|
|
133
|
-
for (;;) {
|
|
137
|
+
readLoop: for (;;) {
|
|
134
138
|
const { done, value } = await reader.read();
|
|
135
139
|
if (done)
|
|
136
140
|
break;
|
|
137
141
|
buffer += decoder.decode(value, { stream: true });
|
|
138
142
|
const blocks = buffer.split("\n\n");
|
|
139
143
|
buffer = blocks.pop() ?? "";
|
|
140
|
-
for (
|
|
144
|
+
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex += 1) {
|
|
145
|
+
const block = blocks[blockIndex];
|
|
146
|
+
if (block === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
if (!replay) {
|
|
149
|
+
const continuity = inspectChatSseContinuity(block, liveContinuityCursor);
|
|
150
|
+
if (continuity.action === "reconnect") {
|
|
151
|
+
// Buffer this block and the rest of the decoded chunk before closing
|
|
152
|
+
// the reader. Retained replay repairs structure; the buffer preserves
|
|
153
|
+
// cumulative text frames that persistence may have coalesced away.
|
|
154
|
+
gapReplayBuffer.push(...blocks.slice(blockIndex));
|
|
155
|
+
replayRepairNeeded = true;
|
|
156
|
+
break readLoop;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
141
159
|
if (sseEventName(block) === "stream_ready") {
|
|
142
160
|
const current = refs.eventsStreamStateRef.current;
|
|
143
161
|
if (current?.sessionId === sessionId &&
|
|
144
162
|
current.generation === generation)
|
|
145
163
|
current.ready = true;
|
|
146
164
|
replay = false;
|
|
165
|
+
liveContinuityCursor = readChatStreamReadyCursor(block);
|
|
147
166
|
// Fall through: the applier owns the buffered-replay flush and
|
|
148
167
|
// returns immediately for this event. `continue` here silently
|
|
149
168
|
// dropped that flush, so a reconnect that landed after the
|
|
@@ -155,15 +174,38 @@ export function useChatStream(params) {
|
|
|
155
174
|
targetSessionId: sessionId,
|
|
156
175
|
generation,
|
|
157
176
|
});
|
|
158
|
-
if (eventId)
|
|
159
|
-
refs.lastEventIdRef.current = eventId;
|
|
177
|
+
if (eventId) {
|
|
178
|
+
refs.lastEventIdRef.current = selectLaterChatEventId(refs.lastEventIdRef.current, eventId);
|
|
179
|
+
if (!replay)
|
|
180
|
+
liveContinuityCursor = selectLaterChatEventId(liveContinuityCursor, eventId);
|
|
181
|
+
}
|
|
182
|
+
if (sseEventName(block) === "stream_ready" &&
|
|
183
|
+
gapReplayBuffer.length > 0) {
|
|
184
|
+
const repairedBlocks = selectChatSseRepairBlocks(gapReplayBuffer.splice(0), liveContinuityCursor);
|
|
185
|
+
for (const repairedBlock of repairedBlocks) {
|
|
186
|
+
const repairedEventId = applySseBlock(repairedBlock, refs.assistantIdRef.current ?? fallbackAssistantId, {
|
|
187
|
+
replay: false,
|
|
188
|
+
targetSessionId: sessionId,
|
|
189
|
+
generation,
|
|
190
|
+
});
|
|
191
|
+
if (!repairedEventId)
|
|
192
|
+
continue;
|
|
193
|
+
refs.lastEventIdRef.current = selectLaterChatEventId(refs.lastEventIdRef.current, repairedEventId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
160
196
|
}
|
|
161
197
|
}
|
|
198
|
+
if (replayRepairNeeded) {
|
|
199
|
+
await reader.cancel().catch(() => undefined);
|
|
200
|
+
}
|
|
162
201
|
const current = refs.eventsStreamStateRef.current;
|
|
163
202
|
if (current?.sessionId === sessionId &&
|
|
164
203
|
current.generation === generation)
|
|
165
204
|
current.ready = false;
|
|
166
|
-
|
|
205
|
+
// A detected hole is expected to be immediately recoverable from the
|
|
206
|
+
// durable ring. Other EOFs retain the normal reconnect backoff.
|
|
207
|
+
if (!replayRepairNeeded)
|
|
208
|
+
await sleep(1_000, signal);
|
|
167
209
|
}
|
|
168
210
|
catch {
|
|
169
211
|
if (signal.aborted)
|
|
@@ -140,6 +140,8 @@ export function useChatThread(params) {
|
|
|
140
140
|
const [usage, setUsage] = useState(EMPTY_USAGE);
|
|
141
141
|
const [sessionStats, setSessionStats] = useState(EMPTY_SESSION_STATS);
|
|
142
142
|
const [turnTelemetry, setTurnTelemetry] = useState([]);
|
|
143
|
+
const [operatorActions, setOperatorActions] = useState([]);
|
|
144
|
+
const [sddSessionIdentity, setSddSessionIdentity] = useState(null);
|
|
143
145
|
const [streaming, setStreaming] = useState(false);
|
|
144
146
|
const [turnPhase, setTurnPhase] = useState("idle");
|
|
145
147
|
const [deferredTurn, setDeferredTurn] = useState(null);
|
|
@@ -167,6 +169,7 @@ export function useChatThread(params) {
|
|
|
167
169
|
if (body.usage)
|
|
168
170
|
setUsage(body.usage);
|
|
169
171
|
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
172
|
+
setSddSessionIdentity(body.sddDataAlignment ?? null);
|
|
170
173
|
}
|
|
171
174
|
catch {
|
|
172
175
|
// Telemetry is supplementary; the normal event stream remains authoritative.
|
|
@@ -188,6 +191,8 @@ export function useChatThread(params) {
|
|
|
188
191
|
setPendingQuestion,
|
|
189
192
|
setTodos,
|
|
190
193
|
setGoal,
|
|
194
|
+
setOperatorActions,
|
|
195
|
+
setSddSessionIdentity,
|
|
191
196
|
setStreamingAssistantId,
|
|
192
197
|
setUnread,
|
|
193
198
|
setOperationCards,
|
|
@@ -421,6 +426,8 @@ export function useChatThread(params) {
|
|
|
421
426
|
setUsage(EMPTY_USAGE);
|
|
422
427
|
setSessionStats(EMPTY_SESSION_STATS);
|
|
423
428
|
setTurnTelemetry([]);
|
|
429
|
+
setOperatorActions([]);
|
|
430
|
+
setSddSessionIdentity(null);
|
|
424
431
|
setStreaming(false);
|
|
425
432
|
setTurnPhase("idle");
|
|
426
433
|
setDeferredTurn(null);
|
|
@@ -477,6 +484,8 @@ export function useChatThread(params) {
|
|
|
477
484
|
setUsage(next.usage ?? EMPTY_USAGE);
|
|
478
485
|
setSessionStats(next.sessionStats ?? EMPTY_SESSION_STATS);
|
|
479
486
|
setTurnTelemetry(next.turnTelemetry ?? []);
|
|
487
|
+
setOperatorActions([]);
|
|
488
|
+
setSddSessionIdentity(next.sddSessionIdentity ?? null);
|
|
480
489
|
refs.usageResponseKeysRef.current = new Set();
|
|
481
490
|
setStreaming(visuallyStopped ? false : next.streaming);
|
|
482
491
|
setTurnPhase(visuallyStopped
|
|
@@ -500,13 +509,15 @@ export function useChatThread(params) {
|
|
|
500
509
|
* operation/human-gate/artifact/interview cards, which the reconnected tail
|
|
501
510
|
* stream will not replay (they live in the ring, already consumed). */
|
|
502
511
|
const recoverSession = useCallback(async (targetSessionId) => {
|
|
512
|
+
const targetGeneration = refs.sessionGenerationRef.current;
|
|
503
513
|
try {
|
|
504
514
|
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(targetSessionId)}`, { credentials: "include" });
|
|
505
515
|
if (!res.ok)
|
|
506
516
|
return false;
|
|
507
517
|
const body = (await res.json());
|
|
508
518
|
if (!body.session ||
|
|
509
|
-
refs.sessionRef.current?.sessionId !== targetSessionId
|
|
519
|
+
refs.sessionRef.current?.sessionId !== targetSessionId ||
|
|
520
|
+
refs.sessionGenerationRef.current !== targetGeneration)
|
|
510
521
|
return false;
|
|
511
522
|
const activeTurnId = body.session.activeTurnId ?? null;
|
|
512
523
|
const visuallyStopped = body.session.turnPhase === "stopping";
|
|
@@ -526,6 +537,7 @@ export function useChatThread(params) {
|
|
|
526
537
|
if (body.usage)
|
|
527
538
|
setUsage(body.usage);
|
|
528
539
|
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
540
|
+
setSddSessionIdentity(body.sddDataAlignment ?? null);
|
|
529
541
|
// Resume the events stream from the snapshot tail so the next
|
|
530
542
|
// reconnect never immediately re-expires (AC-003). The refs below
|
|
531
543
|
// are reset so any follow-up tail replay re-applies cleanly.
|
|
@@ -534,7 +546,13 @@ export function useChatThread(params) {
|
|
|
534
546
|
refs.turnAssistantIdsRef.current = new Map();
|
|
535
547
|
refs.sealedAssistantIdsRef.current = new Set();
|
|
536
548
|
const hydrated = hydrateSessionThread(body.session.messages ?? []);
|
|
537
|
-
|
|
549
|
+
// Recovery is addressed to targetSessionId, so an optimistic
|
|
550
|
+
// submission from another session must never be projected into
|
|
551
|
+
// this snapshot (the activation path enforces the same owner gate).
|
|
552
|
+
const pending = refs.pendingSubmissionRef.current?.sessionId === targetSessionId &&
|
|
553
|
+
refs.pendingSubmissionRef.current.sessionGeneration === targetGeneration
|
|
554
|
+
? refs.pendingSubmissionRef.current
|
|
555
|
+
: undefined;
|
|
538
556
|
const recovered = mergePendingSubmissionOnHydration(hydrated.messages, pending
|
|
539
557
|
? {
|
|
540
558
|
clientRequestId: pending.clientRequestId,
|
|
@@ -599,6 +617,9 @@ export function useChatThread(params) {
|
|
|
599
617
|
usage,
|
|
600
618
|
sessionStats,
|
|
601
619
|
turnTelemetry,
|
|
620
|
+
operatorActions,
|
|
621
|
+
sddSessionIdentity,
|
|
622
|
+
setSddSessionIdentity,
|
|
602
623
|
streaming,
|
|
603
624
|
setStreaming,
|
|
604
625
|
turnPhase,
|
|
@@ -621,6 +642,7 @@ export function useChatThread(params) {
|
|
|
621
642
|
applySseBlock,
|
|
622
643
|
loadTaskContext,
|
|
623
644
|
applyOperationLinkMutation,
|
|
645
|
+
refreshSessionTelemetry,
|
|
624
646
|
resetForNewSession,
|
|
625
647
|
applyActivatedSession,
|
|
626
648
|
recoverSession,
|
|
@@ -265,6 +265,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
265
265
|
const aimaxAuth = new AimaxAuthReader();
|
|
266
266
|
await aimaxAuth.getToken();
|
|
267
267
|
const sddDataAlignment = new SddDataAlignmentService(chatStore, aimaxAuth, deps.fetchImpl);
|
|
268
|
+
actionContext.sddDataAlignment = sddDataAlignment;
|
|
268
269
|
const chatEventEpoch = randomBytes(12).toString("hex");
|
|
269
270
|
const chatEvents = createChatEventStore({
|
|
270
271
|
appData,
|
|
@@ -318,6 +319,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
318
319
|
terminalRegistry,
|
|
319
320
|
scmService,
|
|
320
321
|
browserAutomation,
|
|
322
|
+
globalAppDataRoot: appData.root,
|
|
321
323
|
});
|
|
322
324
|
let chatRouteDeps;
|
|
323
325
|
const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/* Opt-in palettes. Repeat tokens at the Inspect boundary: it owns local defaults
|
|
2
|
+
* and is loaded after the Console stylesheet. Specificity keeps load order safe. */
|
|
3
|
+
:root[data-console-theme="midnight"],
|
|
4
|
+
:root[data-console-theme="midnight"] body[data-shell="observe-console"],
|
|
5
|
+
:root[data-console-theme="midnight"] .inspect-workspace-root {
|
|
6
|
+
color-scheme: dark;
|
|
7
|
+
--canvas: #1c222a;
|
|
8
|
+
--canvas-soft: #202730;
|
|
9
|
+
--surface: #252d37;
|
|
10
|
+
--surface-muted: #2d3641;
|
|
11
|
+
--ink: #dce2e9;
|
|
12
|
+
--surface-ink: #dce2e9;
|
|
13
|
+
--body: #bcc5d0;
|
|
14
|
+
--muted: #a1acba;
|
|
15
|
+
--muted-soft: #909eaf;
|
|
16
|
+
--muted-ink: #a1acba;
|
|
17
|
+
--hairline: #353f4b;
|
|
18
|
+
--hairline-strong: #526171;
|
|
19
|
+
--orange: #a0bcd3;
|
|
20
|
+
--orange-active: #b8cfe0;
|
|
21
|
+
--blue: #a0bcd3;
|
|
22
|
+
--brand: #b2aacb;
|
|
23
|
+
--theme-action: #4d6b86;
|
|
24
|
+
--theme-action-hover: #557590;
|
|
25
|
+
--theme-on-action: #ffffff;
|
|
26
|
+
--green: #99c5b0;
|
|
27
|
+
--amber: #d3b783;
|
|
28
|
+
--warn: #d3b783;
|
|
29
|
+
--red: #dea4ac;
|
|
30
|
+
--peach: #303e4d;
|
|
31
|
+
--mint: #2d403b;
|
|
32
|
+
--lavender: #393746;
|
|
33
|
+
--gold: #c3ac80;
|
|
34
|
+
--shadow-sm: 0 1px 2px rgb(0 0 0 / 12%);
|
|
35
|
+
--shadow-panel: 0 1px 2px rgb(0 0 0 / 12%), 0 6px 18px rgb(0 0 0 / 10%);
|
|
36
|
+
--shadow-soft: 0 8px 24px rgb(0 0 0 / 18%);
|
|
37
|
+
--shadow-lg: 0 16px 40px rgb(0 0 0 / 24%);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:root[data-console-theme="daylight"],
|
|
41
|
+
:root[data-console-theme="daylight"] body[data-shell="observe-console"],
|
|
42
|
+
:root[data-console-theme="daylight"] .inspect-workspace-root {
|
|
43
|
+
color-scheme: light;
|
|
44
|
+
--canvas: #eef0f2;
|
|
45
|
+
--canvas-soft: #f3f4f5;
|
|
46
|
+
--surface: #f9fafb;
|
|
47
|
+
--surface-muted: #e5e9ed;
|
|
48
|
+
--ink: #303b47;
|
|
49
|
+
--surface-ink: #303b47;
|
|
50
|
+
--body: #4b5867;
|
|
51
|
+
--muted: #5e6a78;
|
|
52
|
+
--muted-soft: #697583;
|
|
53
|
+
--muted-ink: #5e6a78;
|
|
54
|
+
--hairline: #dce1e6;
|
|
55
|
+
--hairline-strong: #b5bfc9;
|
|
56
|
+
--orange: #466581;
|
|
57
|
+
--orange-active: #365570;
|
|
58
|
+
--blue: #466581;
|
|
59
|
+
--brand: #706387;
|
|
60
|
+
--theme-action: #536f88;
|
|
61
|
+
--theme-action-hover: #466079;
|
|
62
|
+
--theme-on-action: #ffffff;
|
|
63
|
+
--green: #3e735d;
|
|
64
|
+
--amber: #85632d;
|
|
65
|
+
--warn: #85632d;
|
|
66
|
+
--red: #a04f5c;
|
|
67
|
+
--peach: #dfe7ee;
|
|
68
|
+
--mint: #e0eae4;
|
|
69
|
+
--lavender: #e7e3ed;
|
|
70
|
+
--gold: #927644;
|
|
71
|
+
--shadow-sm: 0 1px 2px rgb(35 45 58 / 4%);
|
|
72
|
+
--shadow-panel: 0 1px 2px rgb(35 45 58 / 3%), 0 6px 18px rgb(35 45 58 / 3%);
|
|
73
|
+
--shadow-soft: 0 8px 24px rgb(35 45 58 / 6%);
|
|
74
|
+
--shadow-lg: 0 16px 40px rgb(35 45 58 / 9%);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Only the two added themes override component literals; original warm paper
|
|
78
|
+
* remains byte-for-byte defined by its existing stylesheets. */
|
|
79
|
+
:root:is([data-console-theme="midnight"], [data-console-theme="daylight"]) {
|
|
80
|
+
--orange-soft: color-mix(in srgb, var(--orange) 10%, var(--surface));
|
|
81
|
+
background: var(--canvas);
|
|
82
|
+
|
|
83
|
+
body[data-shell="observe-console"], .inspect-workspace-root {
|
|
84
|
+
--orange-soft: color-mix(in srgb, var(--orange) 10%, var(--surface));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
::selection { background: color-mix(in srgb, var(--blue) 32%, var(--surface)); }
|
|
88
|
+
|
|
89
|
+
.oc-composer-shell, .console-update-channel, .console-update-button,
|
|
90
|
+
.panel-output { background: var(--surface); color: var(--ink); }
|
|
91
|
+
.console-update-channel, .console-update-button { border-color: var(--hairline-strong); }
|
|
92
|
+
.console-update-channel-label { color: var(--body); }
|
|
93
|
+
.console-update-error, .console-update-result-error { color: var(--red); }
|
|
94
|
+
.console-update-badge { background: var(--surface-muted); color: var(--amber); }
|
|
95
|
+
|
|
96
|
+
.oc-bubble-user { background: color-mix(in srgb, var(--blue) 9%, var(--surface)); }
|
|
97
|
+
.oc-composer-shell { box-shadow: var(--shadow-panel); }
|
|
98
|
+
.oc-bubble-assistant .oc-codeblock { background: var(--canvas-soft); }
|
|
99
|
+
.oc-session-scroll, .oc-tree-scroll { background: var(--canvas-soft); }
|
|
100
|
+
.oc-sidebar-new-session:is(:hover, :focus-visible),
|
|
101
|
+
.oc-hero-chip:is(:hover, :focus-visible, [aria-expanded="true"]) { background: var(--surface-muted); }
|
|
102
|
+
|
|
103
|
+
.night-health-pill, .models-status-pill[data-state="ready"],
|
|
104
|
+
.models-probe-item.is-ok .models-probe-badge {
|
|
105
|
+
background: color-mix(in srgb, var(--green) 14%, var(--surface));
|
|
106
|
+
}
|
|
107
|
+
.night-health-pill.is-bad, .models-status-pill[data-state="setup-required"],
|
|
108
|
+
.models-probe-item.is-bad .models-probe-badge, .dag-error-node {
|
|
109
|
+
background: color-mix(in srgb, var(--red) 12%, var(--surface));
|
|
110
|
+
}
|
|
111
|
+
.models-status-pill[data-state="degraded"], .models-degraded-note,
|
|
112
|
+
.dag-active-node { background: var(--orange-soft); }
|
|
113
|
+
.liveness-timeout-risk { background: color-mix(in srgb, var(--amber) 12%, var(--surface)); }
|
|
114
|
+
.night-strip-icon { color: var(--brand); }
|
|
115
|
+
|
|
116
|
+
/* Quiet selected surfaces keep navigation secondary to the content. */
|
|
117
|
+
.och-mode-thumb { background: var(--surface); box-shadow: var(--shadow-sm); }
|
|
118
|
+
.och-mode-btn.och-on { color: var(--ink); }
|
|
119
|
+
.dag-inspector-tab[aria-selected="true"],
|
|
120
|
+
.dag-graph-mode-tab[aria-selected="true"],
|
|
121
|
+
#dag-inspector-panel-prompt-restart button.prompt-restart-mode-option.is-selected {
|
|
122
|
+
background: var(--surface-muted);
|
|
123
|
+
color: var(--ink);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Accent text needs a different luminance from a white-labelled action. */
|
|
127
|
+
.btn-primary, button.btn-primary, .oc-send, .oc-question-button.is-primary,
|
|
128
|
+
.oc-scm-commitbtn, .skip-link, .dags-page-num.is-current,
|
|
129
|
+
.dag-action-primary, .context-export-submit,
|
|
130
|
+
#dag-inspector-panel-prompt-restart button.prompt-restart-submit:not(.is-secondary) {
|
|
131
|
+
background: var(--theme-action);
|
|
132
|
+
border-color: var(--theme-action);
|
|
133
|
+
color: var(--theme-on-action);
|
|
134
|
+
box-shadow: var(--shadow-sm);
|
|
135
|
+
}
|
|
136
|
+
.btn-primary:hover:not(:disabled), button.btn-primary:hover:not(:disabled),
|
|
137
|
+
.oc-send:hover:not(:disabled), .oc-question-button.is-primary:hover:not(:disabled),
|
|
138
|
+
.oc-scm-commitbtn:hover:not(:disabled), .dag-action-primary:hover:not(:disabled),
|
|
139
|
+
.context-export-submit:hover:not(:disabled),
|
|
140
|
+
#dag-inspector-panel-prompt-restart button.prompt-restart-submit:not(.is-secondary):hover {
|
|
141
|
+
background: var(--theme-action-hover);
|
|
142
|
+
border-color: var(--theme-action-hover);
|
|
143
|
+
}
|
|
144
|
+
.och-context-return, .process-timeline-autoscroll::after {
|
|
145
|
+
background: var(--surface-muted);
|
|
146
|
+
color: var(--ink);
|
|
147
|
+
}
|
|
148
|
+
.markdown-output pre, .output-block { background: var(--canvas-soft); color: var(--ink); }
|
|
149
|
+
.context-export-ticket { background: var(--surface-muted); color: var(--ink); }
|
|
150
|
+
.context-export-ticket-safety { color: var(--green); }
|
|
151
|
+
.context-export-ticket-safety.is-risk { color: var(--red); }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Syntax highlighting is independently hardcoded to a light palette. */
|
|
155
|
+
:root[data-console-theme="midnight"] {
|
|
156
|
+
/* The SVG brand uses dark strokes; provide a subtle light backing, not a filter. */
|
|
157
|
+
.och-brand-mark, .oc-hero img { background: #aebac7; border-radius: 6px; }
|
|
158
|
+
.hljs { color: var(--ink); background: var(--canvas-soft); }
|
|
159
|
+
.hljs-doctag, .hljs-keyword, .hljs-meta .hljs-keyword,
|
|
160
|
+
.hljs-template-tag, .hljs-template-variable, .hljs-type,
|
|
161
|
+
.hljs-variable.language_ { color: #c7afcf; }
|
|
162
|
+
.hljs-title, .hljs-title.class_, .hljs-title.class_.inherited__,
|
|
163
|
+
.hljs-title.function_ { color: #b9b3d1; }
|
|
164
|
+
.hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number,
|
|
165
|
+
.hljs-operator, .hljs-selector-attr, .hljs-selector-class,
|
|
166
|
+
.hljs-selector-id, .hljs-variable, .hljs-section { color: var(--blue); }
|
|
167
|
+
.hljs-meta .hljs-string, .hljs-regexp, .hljs-string,
|
|
168
|
+
.hljs-name, .hljs-quote, .hljs-selector-pseudo, .hljs-selector-tag { color: var(--green); }
|
|
169
|
+
.hljs-built_in, .hljs-symbol, .hljs-bullet { color: var(--amber); }
|
|
170
|
+
.hljs-code, .hljs-comment, .hljs-formula { color: var(--muted); }
|
|
171
|
+
.hljs-subst, .hljs-emphasis, .hljs-strong { color: var(--ink); }
|
|
172
|
+
.hljs-addition { color: var(--green); background: var(--mint); }
|
|
173
|
+
.hljs-deletion { color: var(--red); background: color-mix(in srgb, var(--red) 12%, var(--surface)); }
|
|
174
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Shared Console theme identities; the default leaves the original palette intact. */
|
|
2
|
+
export const CONSOLE_THEMES = Object.freeze([
|
|
3
|
+
{ id: "default", label: "默认" },
|
|
4
|
+
{ id: "midnight", label: "深色" },
|
|
5
|
+
{ id: "daylight", label: "浅色" },
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEY = "loop-console.theme.v1";
|
|
9
|
+
|
|
10
|
+
function normalizeTheme(value) {
|
|
11
|
+
return CONSOLE_THEMES.some((theme) => theme.id === value) ? value : "default";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function applyStoredConsoleTheme() {
|
|
15
|
+
let value;
|
|
16
|
+
try {
|
|
17
|
+
value = window.localStorage.getItem(STORAGE_KEY);
|
|
18
|
+
} catch {
|
|
19
|
+
// Storage can be disabled, including the localStorage getter itself.
|
|
20
|
+
}
|
|
21
|
+
const theme = normalizeTheme(value);
|
|
22
|
+
document.documentElement.dataset.consoleTheme = theme;
|
|
23
|
+
return theme;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function setConsoleTheme(value) {
|
|
27
|
+
const theme = normalizeTheme(value);
|
|
28
|
+
// Apply before persistence: a storage failure must not disable the picker.
|
|
29
|
+
document.documentElement.dataset.consoleTheme = theme;
|
|
30
|
+
try {
|
|
31
|
+
window.localStorage.setItem(STORAGE_KEY, theme);
|
|
32
|
+
} catch {
|
|
33
|
+
// The document attribute remains the in-memory preference.
|
|
34
|
+
}
|
|
35
|
+
return theme;
|
|
36
|
+
}
|
|
@@ -8,6 +8,84 @@
|
|
|
8
8
|
* 的暖白 token 保持一致;宿主样式表已定义同名变量时以宿主为准。
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
@import "./console-theme.css";
|
|
12
|
+
|
|
13
|
+
.och-theme {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 6px;
|
|
17
|
+
flex: none;
|
|
18
|
+
color: var(--och-muted);
|
|
19
|
+
font-size: 12px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.och-theme-trigger {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
width: 26px;
|
|
27
|
+
min-height: 26px;
|
|
28
|
+
padding: 0;
|
|
29
|
+
border: 1px solid transparent;
|
|
30
|
+
border-radius: 6px;
|
|
31
|
+
background: transparent;
|
|
32
|
+
color: var(--och-body);
|
|
33
|
+
font: inherit;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.och-theme-trigger:hover,
|
|
38
|
+
.och-theme-trigger[aria-expanded="true"] {
|
|
39
|
+
background: var(--och-surface-muted);
|
|
40
|
+
color: var(--och-ink);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.och-theme-trigger:focus-visible,
|
|
44
|
+
.och-theme-option:focus-visible {
|
|
45
|
+
outline: 2px solid var(--och-orange);
|
|
46
|
+
outline-offset: 2px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Native popover renders in the top layer, outside the narrow header's clip. */
|
|
50
|
+
.och-theme-menu {
|
|
51
|
+
position: fixed;
|
|
52
|
+
inset: auto;
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
width: 112px;
|
|
55
|
+
margin: 0;
|
|
56
|
+
padding: 4px;
|
|
57
|
+
border: 1px solid var(--och-hairline);
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
background: var(--och-surface);
|
|
60
|
+
color: var(--och-ink);
|
|
61
|
+
box-shadow: var(--shadow-soft);
|
|
62
|
+
font: inherit;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.och-theme-option {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: space-between;
|
|
69
|
+
width: 100%;
|
|
70
|
+
min-height: 28px;
|
|
71
|
+
padding: 4px 8px;
|
|
72
|
+
border: 0;
|
|
73
|
+
border-radius: 4px;
|
|
74
|
+
background: transparent;
|
|
75
|
+
color: inherit;
|
|
76
|
+
font: inherit;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.och-theme-option:hover,
|
|
81
|
+
.och-theme-option[aria-pressed="true"] {
|
|
82
|
+
background: var(--och-surface-muted);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.och-theme-option[aria-pressed="true"]::after {
|
|
86
|
+
content: "✓";
|
|
87
|
+
}
|
|
88
|
+
|
|
11
89
|
.och {
|
|
12
90
|
--och-canvas: var(--canvas, #f3f4f6);
|
|
13
91
|
--och-surface: var(--surface, #fbfcfd);
|
|
@@ -622,7 +700,14 @@
|
|
|
622
700
|
.och-inner {
|
|
623
701
|
padding-inline: 12px;
|
|
624
702
|
}
|
|
703
|
+
.och-theme {
|
|
704
|
+
position: absolute;
|
|
705
|
+
top: 46px;
|
|
706
|
+
right: 0;
|
|
707
|
+
}
|
|
625
708
|
.och-global {
|
|
709
|
+
height: 84px;
|
|
710
|
+
padding-bottom: 40px;
|
|
626
711
|
gap: 6px;
|
|
627
712
|
overflow-x: auto;
|
|
628
713
|
scrollbar-width: none;
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { withWorkspaceQuery } from "./router.js";
|
|
15
|
+
import { CONSOLE_THEMES, applyStoredConsoleTheme, setConsoleTheme } from "./console-theme.js";
|
|
16
|
+
export { CONSOLE_THEMES } from "./console-theme.js";
|
|
15
17
|
|
|
16
18
|
export const OPERATE_TABS = Object.freeze([
|
|
17
19
|
{ id: "tasks", label: "任务", href: "/#/operate/tasks" },
|
|
@@ -296,6 +298,13 @@ export function buildOperatorChromeModel(config) {
|
|
|
296
298
|
|
|
297
299
|
const SVG_NS = "http:" + "//www.w3.org/2000/svg";
|
|
298
300
|
const ICONS = Object.freeze({
|
|
301
|
+
theme: {
|
|
302
|
+
size: 16,
|
|
303
|
+
parts: [
|
|
304
|
+
["circle", { cx: 12, cy: 12, r: 9 }],
|
|
305
|
+
["path", { d: "M12 3a9 9 0 0 1 0 18Z", fill: "currentColor", stroke: "none" }],
|
|
306
|
+
],
|
|
307
|
+
},
|
|
299
308
|
radar: {
|
|
300
309
|
size: 17,
|
|
301
310
|
parts: [
|
|
@@ -426,6 +435,7 @@ function readWorkspaceCollapsedPref() {
|
|
|
426
435
|
export function mountOperatorChrome(root, config = {}) {
|
|
427
436
|
if (!root) throw new Error("mountOperatorChrome: root element is required");
|
|
428
437
|
root.classList.add("och-host");
|
|
438
|
+
if (!document.documentElement.dataset.consoleTheme) applyStoredConsoleTheme();
|
|
429
439
|
|
|
430
440
|
let currentConfig = { ...config };
|
|
431
441
|
let popoverOpen = false;
|
|
@@ -701,6 +711,47 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
701
711
|
|
|
702
712
|
global.appendChild(el("span", "och-spacer"));
|
|
703
713
|
|
|
714
|
+
const themePicker = el("div", "och-theme");
|
|
715
|
+
const trigger = el("button", "och-theme-trigger");
|
|
716
|
+
trigger.type = "button";
|
|
717
|
+
trigger.title = "主题";
|
|
718
|
+
trigger.setAttribute("aria-label", "主题");
|
|
719
|
+
trigger.appendChild(makeIcon("theme"));
|
|
720
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
721
|
+
const menu = el("div", "och-theme-menu");
|
|
722
|
+
menu.id = `${popoverId}-theme`;
|
|
723
|
+
menu.popover = "auto";
|
|
724
|
+
menu.setAttribute("role", "group");
|
|
725
|
+
menu.setAttribute("aria-label", "选择主题");
|
|
726
|
+
trigger.setAttribute("aria-controls", menu.id);
|
|
727
|
+
trigger.popoverTargetElement = menu;
|
|
728
|
+
menu.addEventListener("beforetoggle", (event) => {
|
|
729
|
+
if (event.newState !== "open") return;
|
|
730
|
+
const rect = trigger.getBoundingClientRect();
|
|
731
|
+
menu.style.top = `${rect.bottom + 6}px`;
|
|
732
|
+
menu.style.left = `${Math.max(8, Math.min(rect.right - 112, window.innerWidth - 120))}px`;
|
|
733
|
+
});
|
|
734
|
+
menu.addEventListener("toggle", (event) => {
|
|
735
|
+
trigger.setAttribute("aria-expanded", String(event.newState === "open"));
|
|
736
|
+
});
|
|
737
|
+
for (const theme of CONSOLE_THEMES) {
|
|
738
|
+
const option = el("button", "och-theme-option", theme.label);
|
|
739
|
+
option.type = "button";
|
|
740
|
+
option.dataset.theme = theme.id;
|
|
741
|
+
option.setAttribute("aria-pressed", String(theme.id === (document.documentElement.dataset.consoleTheme ?? "default")));
|
|
742
|
+
option.addEventListener("click", () => {
|
|
743
|
+
setConsoleTheme(theme.id);
|
|
744
|
+
for (const item of menu.children) {
|
|
745
|
+
item.setAttribute("aria-pressed", String(item.dataset.theme === theme.id));
|
|
746
|
+
}
|
|
747
|
+
menu.hidePopover();
|
|
748
|
+
trigger.focus();
|
|
749
|
+
});
|
|
750
|
+
menu.appendChild(option);
|
|
751
|
+
}
|
|
752
|
+
themePicker.append(trigger, menu);
|
|
753
|
+
global.appendChild(themePicker);
|
|
754
|
+
|
|
704
755
|
if (model.statusVisible && model.freshness) {
|
|
705
756
|
const fresh = el("span", "och-fresh");
|
|
706
757
|
fresh.setAttribute("aria-label", "数据新鲜度");
|