From 2603b00caafcc27affb223f45cbde117f8647a25 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 14 Jul 2026 08:21:58 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B8=85=E7=90=86=20Workbench=20?= =?UTF-8?q?=E6=97=A0=E6=95=88=E6=8A=95=E5=BD=B1=E6=AE=8B=E4=BD=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...h-fixed-transactional-architecture.test.ts | 7 ++++ .../workbench-realtime-runtime.test.ts | 2 - .../src/config/workbench-runtime-policy.ts | 3 -- ...rkbench-message-projection-runtime.test.ts | 10 ++--- web/hwlab-cloud-web/src/stores/workbench.ts | 39 +------------------ web/hwlab-cloud-web/src/types/global.d.ts | 1 - 6 files changed, 11 insertions(+), 51 deletions(-) diff --git a/web/hwlab-cloud-web/scripts/workbench-fixed-transactional-architecture.test.ts b/web/hwlab-cloud-web/scripts/workbench-fixed-transactional-architecture.test.ts index 50b05742..a46acec0 100644 --- a/web/hwlab-cloud-web/scripts/workbench-fixed-transactional-architecture.test.ts +++ b/web/hwlab-cloud-web/scripts/workbench-fixed-transactional-architecture.test.ts @@ -11,6 +11,8 @@ const productionFiles = [ "internal/cloud/server-code-agent-admission-http.ts", "internal/cloud/server.ts", "web/hwlab-cloud-web/src/config/runtime.ts", + "web/hwlab-cloud-web/src/config/workbench-runtime-policy.ts", + "web/hwlab-cloud-web/src/types/global.d.ts", "web/hwlab-cloud-web/src/api/workbench-events.ts", "web/hwlab-cloud-web/src/utils/workbench-stream-transport.ts", "web/hwlab-cloud-web/src/utils/workbench-realtime-runtime.ts", @@ -48,6 +50,11 @@ const forbiddenArchitectureIdentifiers = [ "sessionDetailAutoReadDecision", "projectionMergeCommitSummary", "forceRead", + "sealRestoredActiveTurnMessages", + "messageNeedsRestoredTurnSeal", + "refreshSessionStatusAuthority", + "readTerminalTraceDetailGaps", + "workbenchTraceMessagesWindowLimit", "handleWorkbenchSyncHttp", "/v1/workbench/sync" ]; diff --git a/web/hwlab-cloud-web/scripts/workbench-realtime-runtime.test.ts b/web/hwlab-cloud-web/scripts/workbench-realtime-runtime.test.ts index 19e74333..32dbd148 100644 --- a/web/hwlab-cloud-web/scripts/workbench-realtime-runtime.test.ts +++ b/web/hwlab-cloud-web/scripts/workbench-realtime-runtime.test.ts @@ -39,7 +39,6 @@ test("Workbench runtime policy reads injected config while preserving defaults", traceDetailMaxPages: 2, workbenchSessionDetailMinRefreshMs: 1234, workbenchSessionMessagesWindowLimit: 9, - workbenchTraceMessagesWindowLimit: 4, workbenchRealtimeErrorSyncReplayMinMs: 0, workbenchRealtimeFlushMaxItemsPerChunk: 2, workbenchRealtimeFlushMaxChunkMs: 6, @@ -51,7 +50,6 @@ test("Workbench runtime policy reads injected config while preserving defaults", assert.equal(policy.traceDetailMaxPages, 2); assert.equal(policy.workbenchSessionDetailMinRefreshMs, 1234); assert.equal(policy.workbenchSessionMessagesWindowLimit, 9); - assert.equal(policy.workbenchTraceMessagesWindowLimit, 4); assert.equal(policy.workbenchRealtimeErrorSyncReplayMinMs, 0); assert.equal(policy.workbenchRealtimeFlushMaxItemsPerChunk, 2); assert.equal(policy.workbenchRealtimeFlushMaxChunkMs, 6); diff --git a/web/hwlab-cloud-web/src/config/workbench-runtime-policy.ts b/web/hwlab-cloud-web/src/config/workbench-runtime-policy.ts index ec8b778b..b9eaf661 100644 --- a/web/hwlab-cloud-web/src/config/workbench-runtime-policy.ts +++ b/web/hwlab-cloud-web/src/config/workbench-runtime-policy.ts @@ -18,7 +18,6 @@ export interface WorkbenchRuntimePolicy { workbenchSessionDetailMinRefreshMs: number; workbenchSessionMessagesMinRefreshMs: number; workbenchSessionMessagesWindowLimit: number; - workbenchTraceMessagesWindowLimit: number; workbenchRealtimeSessionMessagesMinRefreshMs: number; workbenchTraceEventsTimeoutMs: number; sessionListMinRefreshIntervalMs: number; @@ -45,7 +44,6 @@ const DEFAULT_WORKBENCH_RUNTIME_POLICY: WorkbenchRuntimePolicy = Object.freeze({ workbenchSessionDetailMinRefreshMs: 5_000, workbenchSessionMessagesMinRefreshMs: 5_000, workbenchSessionMessagesWindowLimit: 20, - workbenchTraceMessagesWindowLimit: 8, workbenchRealtimeSessionMessagesMinRefreshMs: 1_000, workbenchTraceEventsTimeoutMs: 5_000, sessionListMinRefreshIntervalMs: 15_000, @@ -74,7 +72,6 @@ export function workbenchRuntimePolicy(input: unknown = runtimePolicyConfig()): workbenchSessionDetailMinRefreshMs: nonNegativeNumber(source.workbenchSessionDetailMinRefreshMs, DEFAULT_WORKBENCH_RUNTIME_POLICY.workbenchSessionDetailMinRefreshMs), workbenchSessionMessagesMinRefreshMs: nonNegativeNumber(source.workbenchSessionMessagesMinRefreshMs, DEFAULT_WORKBENCH_RUNTIME_POLICY.workbenchSessionMessagesMinRefreshMs), workbenchSessionMessagesWindowLimit: positiveInteger(source.workbenchSessionMessagesWindowLimit, DEFAULT_WORKBENCH_RUNTIME_POLICY.workbenchSessionMessagesWindowLimit), - workbenchTraceMessagesWindowLimit: positiveInteger(source.workbenchTraceMessagesWindowLimit, DEFAULT_WORKBENCH_RUNTIME_POLICY.workbenchTraceMessagesWindowLimit), workbenchRealtimeSessionMessagesMinRefreshMs: nonNegativeNumber(source.workbenchRealtimeSessionMessagesMinRefreshMs, DEFAULT_WORKBENCH_RUNTIME_POLICY.workbenchRealtimeSessionMessagesMinRefreshMs), workbenchTraceEventsTimeoutMs: positiveNumber(source.workbenchTraceEventsTimeoutMs, DEFAULT_WORKBENCH_RUNTIME_POLICY.workbenchTraceEventsTimeoutMs), sessionListMinRefreshIntervalMs: nonNegativeNumber(source.sessionListMinRefreshIntervalMs, DEFAULT_WORKBENCH_RUNTIME_POLICY.sessionListMinRefreshIntervalMs), diff --git a/web/hwlab-cloud-web/src/stores/workbench-message-projection-runtime.test.ts b/web/hwlab-cloud-web/src/stores/workbench-message-projection-runtime.test.ts index aa5498c2..2dfb8f36 100644 --- a/web/hwlab-cloud-web/src/stores/workbench-message-projection-runtime.test.ts +++ b/web/hwlab-cloud-web/src/stores/workbench-message-projection-runtime.test.ts @@ -142,8 +142,7 @@ test("workbench active terminal paths seal final response from turn authority", const crossTabSyncBlock = source.slice(source.indexOf("async function refreshCrossTabSessionFromSyncReplay"), source.indexOf("function completeTrace")); const sessionDetailReadBlock = source.slice(source.indexOf("function fetchSessionDetailPage"), source.indexOf("function sessionMessageProjectionWindowLimit")); const traceDetailReadBlock = source.slice(source.indexOf("async function readTraceEventsForExplicitDetailPages"), source.indexOf("async function fetchTraceDetailEventsPage")); - const loadBlock = source.slice(source.indexOf("async function loadWorkbenchSession"), source.indexOf("async function sealRestoredActiveTurnMessages")); - const restoreSealBlock = source.slice(source.indexOf("async function sealRestoredActiveTurnMessages"), source.indexOf("function reattachRestoredActiveTrace")); + const loadBlock = source.slice(source.indexOf("async function loadWorkbenchSession"), source.indexOf("function reattachRestoredActiveTrace")); assert.match(projectBlock, /terminalAuthorityMessageFromTurnResult\(result\)/u); assert.match(projectBlock, /type:\s*"message\.upsert"/u); @@ -174,9 +173,7 @@ test("workbench active terminal paths seal final response from turn authority", assert.match(loadBlock, /fetchSessionMessagesPage\(normalizedRequestId, \{ limit: messageLimit, reason: "load-session:messages", force: true \}\)/u); assert.doesNotMatch(loadBlock, /messages:\s*\[\]/u); assert.doesNotMatch(loadBlock, /limit:\s*100/u); - assert.match(restoreSealBlock, /workbench_restored_turn_auto_read_disabled/u); - assert.doesNotMatch(restoreSealBlock, /fetchWorkbenchTurnStatus|Promise\.all/u); - assert.doesNotMatch(restoreSealBlock, /force:\s*true/u); + assert.doesNotMatch(source, /sealRestoredActiveTurnMessages|messageNeedsRestoredTurnSeal|refreshSessionStatusAuthority|readTerminalTraceDetailGaps/u); }); test("workbench active SSE path only recovers by reconnecting the events cursor", () => { @@ -185,8 +182,7 @@ test("workbench active SSE path only recovers by reconnecting the events cursor" source.slice(source.indexOf("async function submitMessage"), source.indexOf("async function cancelAgentMessage")), source.slice(source.indexOf("function reattachTrace"), source.indexOf("function restartRealtime")), source.slice(source.indexOf("function flushRealtimeTurnProjection"), source.indexOf("function installRealtimeVisibilityHandler")), - source.slice(source.indexOf("function completeTrace"), source.indexOf("function applyTerminalResultDiagnostics")), - source.slice(source.indexOf("async function sealRestoredActiveTurnMessages"), source.indexOf("function reattachRestoredActiveTrace")) + source.slice(source.indexOf("function completeTrace"), source.indexOf("function applyTerminalResultDiagnostics")) ].join("\n"); assert.doesNotMatch(activeBlocks, /refreshWorkbenchSyncReplay|scheduleActiveTraceSyncReplay|setTimeout|setInterval/u); diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index 49edf41e..1c7f909d 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -24,7 +24,7 @@ import { cleanupDroppedWorkbenchSessionCaches, trimWorkbenchSessionCache } from import { reduceWorkbenchRealtimeEvent, type WorkbenchRealtimeAction } from "./workbench-event-reducer"; import { projectRejectedWorkbenchAdmission } from "./workbench-admission-failure"; import { messageHasSealedTerminalResult, messageIsSealedTerminal, traceAuthorityIsSealed } from "./workbench-terminal-authority"; -import { boundedProjectionMessageLimit, traceProjectionIsTerminalSealed } from "./workbench-message-projection-budget"; +import { boundedProjectionMessageLimit } from "./workbench-message-projection-budget"; import { agentErrorDisplayText, agentErrorFromApiFailure, @@ -228,7 +228,6 @@ export const useWorkbenchStore = defineStore("workbench", () => { const nextSessions = stableSessionList(sessions.value, listedSessions, stableSelected?.sessionId ?? selected?.sessionId ?? routeSessionId ?? includeSessionId, stableSelected); rememberSessionList(nextSessions); sessionsReady.value = sessionsResult.ok || nextSessions.length > 0; - void refreshSessionStatusAuthority(nextSessions); clearSessionDetailLoading(targetSessionId); loading.value = false; recordWorkbenchLoadingState({ scope: "session_detail", active: false, reason: "hydrate", sessionId: targetSessionId }); @@ -499,10 +498,6 @@ export const useWorkbenchStore = defineStore("workbench", () => { return projectedActiveSession.value ?? null; } - async function refreshSessionStatusAuthority(source: WorkbenchSessionRecord[] = sessions.value): Promise { - void source; - } - function fetchWorkbenchTraceEvents(traceId: string, afterProjectedSeq: number, useActivityTimeout = shouldUseActivityTimeoutForTrace(traceId), options: { force?: boolean } = {}): Promise> { const activitySource = useActivityTimeout ? () => activityRef.value : null; return workbenchColadaQueries.fetchTraceEvents(traceId, { timeoutMs: runtimePolicy.workbenchTraceEventsTimeoutMs, activityRef: activitySource, afterProjectedSeq, limit: runtimePolicy.traceDetailPageLimit, minIntervalMs: runtimePolicy.workbenchTraceEventsMinRefreshMs, force: options.force }); @@ -807,13 +802,6 @@ export const useWorkbenchStore = defineStore("workbench", () => { return fetchWorkbenchTraceEvents(traceId, afterProjectedSeq, shouldUseActivityTimeoutForTrace(traceId), { force: options.force }); } - function readTerminalTraceDetailGaps(source: ChatMessage[], reason: string): void { - // Terminal trace rows are loaded on explicit force/detail paths. Auto-filling - // historical terminal traces makes multi-turn Workbench sessions main-thread bound. - void source; - void reason; - } - async function readTraceEventsForMessages(source: ChatMessage[] = messages.value, options: { force?: boolean } = {}): Promise { const candidates = traceDetailReadCandidates(source); if (options.force !== true) { @@ -1571,23 +1559,6 @@ export const useWorkbenchStore = defineStore("workbench", () => { return { ...base, sessionId: id, messages: hydratedMessages, messageCount: base.messageCount ?? hydratedMessages.length }; } - async function sealRestoredActiveTurnMessages(source: ChatMessage[]): Promise { - const targets = source.filter(messageNeedsRestoredTurnSeal).slice(-1); - if (targets.length === 0) return source; - for (const message of targets) { - const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId); - if (!traceId || traceProjectionIsTerminalSealed(traceId, source)) continue; - recordWorkbenchRuntimeDiagnostic({ - module: "workbench-turn-status", - sessionId: message.sessionId ?? message.runnerTrace?.sessionId ?? null, - traceId, - outcome: "ok", - diagnostic: { code: "workbench_restored_turn_auto_read_disabled", reason: "load-session:restored-turn", source: "projection-sse-authority", valuesRedacted: true } - }); - } - return source; - } - function reattachRestoredActiveTrace(): void { const traceId = activeTraceIdFromMessages(messages.value, turnStatusAuthority.value); if (traceId) reattachTrace(traceId); @@ -1665,14 +1636,6 @@ function routeSelectedSessionRecord(sessionId: string | null | undefined, loadin } as WorkbenchSessionRecord; } -function messageNeedsRestoredTurnSeal(message: ChatMessage): boolean { - if (message.role !== "agent") return false; - if (messageHasTerminalResponse(message)) return false; - const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId); - if (!traceId) return false; - return isTraceActiveStatus(message.status) || isTerminalMessageStatus(message.status) || message.traceAutoLifecycle === "running"; -} - function providerThreadIdForRequest(threadId: string | null | undefined): string | null { const value = firstNonEmptyString(threadId) ?? null; if (!value) return null; diff --git a/web/hwlab-cloud-web/src/types/global.d.ts b/web/hwlab-cloud-web/src/types/global.d.ts index 88fad71f..203fbdcc 100644 --- a/web/hwlab-cloud-web/src/types/global.d.ts +++ b/web/hwlab-cloud-web/src/types/global.d.ts @@ -54,7 +54,6 @@ declare global { workbenchSessionDetailMinRefreshMs?: number; workbenchSessionMessagesMinRefreshMs?: number; workbenchSessionMessagesWindowLimit?: number; - workbenchTraceMessagesWindowLimit?: number; workbenchRealtimeSessionMessagesMinRefreshMs?: number; workbenchTraceEventsTimeoutMs?: number; sessionListMinRefreshIntervalMs?: number;