fix: 清理 Workbench 无效投影残余
This commit is contained in:
@@ -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"
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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> {
|
||||
void source;
|
||||
}
|
||||
|
||||
function fetchWorkbenchTraceEvents(traceId: string, afterProjectedSeq: number, useActivityTimeout = shouldUseActivityTimeoutForTrace(traceId), options: { force?: boolean } = {}): Promise<ApiResult<AgentChatResultResponse>> {
|
||||
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<void> {
|
||||
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<ChatMessage[]> {
|
||||
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;
|
||||
|
||||
-1
@@ -54,7 +54,6 @@ declare global {
|
||||
workbenchSessionDetailMinRefreshMs?: number;
|
||||
workbenchSessionMessagesMinRefreshMs?: number;
|
||||
workbenchSessionMessagesWindowLimit?: number;
|
||||
workbenchTraceMessagesWindowLimit?: number;
|
||||
workbenchRealtimeSessionMessagesMinRefreshMs?: number;
|
||||
workbenchTraceEventsTimeoutMs?: number;
|
||||
sessionListMinRefreshIntervalMs?: number;
|
||||
|
||||
Reference in New Issue
Block a user