Merge pull request #2259 from pikasTech/fix/2255-terminal-trace-hydration

fix: avoid terminal trace hydration storms
This commit is contained in:
Lyon
2026-06-30 00:03:01 +08:00
committed by GitHub
+11 -24
View File
@@ -960,15 +960,10 @@ function nonBlockingProjection(projection: ProjectionDiagnostic | null): Project
}
function hydrateTerminalTraceGaps(source: ChatMessage[], reason: string): void {
for (const message of terminalTraceHydrationGaps(source)) {
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
if (traceId && activeSessionId.value) recordActivity(`${reason}:${traceId}`);
void hydrateTraceEventsForMessage(message, { force: true });
}
}
function terminalTraceHydrationGaps(source: ChatMessage[]): ChatMessage[] {
return source.filter(messageNeedsTerminalTraceGapHydration).slice(-TRACE_HYDRATION_AUTO_QUEUE_LIMIT).reverse();
// 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;
}
function scheduleForcedTraceHydrationRetry(message: ChatMessage): void {
@@ -1232,13 +1227,13 @@ function nonBlockingProjection(projection: ProjectionDiagnostic | null): Project
}
const ownerMessages = ownerSessionId ? serverState.value.messagesBySessionId[ownerSessionId] ?? [] : messages.value;
const message = latestMessageForTrace(id, ownerMessages);
if (message) await hydrateTraceEventsForMessage(message, { force: true });
const turn = turnStatusAuthority.value[id];
const terminal = turn?.terminal === true || isTerminalMessageStatus(turn?.status) || isTerminalMessageStatus(message?.status);
if (terminal) {
clearActiveTraceRestGapFill(id);
return;
}
if (message) await hydrateTraceEventsForMessage(message, { force: true });
const stillActive = currentRequest.value?.traceId === id || isTraceActiveStatus(turn?.status) || isTraceActiveStatus(message?.status);
if (stillActive) scheduleActiveTraceRestGapFill(id, "active-rest-gap:repeat", WORKBENCH_ACTIVE_TRACE_REST_GAP_FILL_REPEAT_MS);
}
@@ -1416,9 +1411,7 @@ function nonBlockingProjection(projection: ProjectionDiagnostic | null): Project
if (ownerSessionId) await refreshMessageProjectionForTrace(ownerSessionId, id, { force: true });
const ownerMessages = ownerSessionId ? serverState.value.messagesBySessionId[ownerSessionId] ?? [] : messages.value;
const message = latestMessageForTrace(id, ownerMessages);
if (message) {
await hydrateTraceEventsForMessage(message, { force: true });
} else if (ownerSessionId) {
if (!message && ownerSessionId) {
await refreshRealtimeSessionMessages(ownerSessionId, `${reason}:message-gap`, { force: true });
}
} finally {
@@ -1540,7 +1533,6 @@ function nonBlockingProjection(projection: ProjectionDiagnostic | null): Project
const ownerMessages = serverState.value.messagesBySessionId[ownerSessionId] ?? [];
const terminalMessage = latestMessageForTrace(traceId, ownerMessages);
if (options.forceRead && terminalMessage) void hydrateTraceEventsForMessage(terminalMessage, { force: true });
else void hydrateTraceEvents(ownerMessages);
if (options.forceRead) void refreshSessions(ownerSessionId, { force: true });
if (ownerSessionId === activeSessionId.value) {
chatPending.value = false;
@@ -2479,6 +2471,7 @@ function messageNeedsTraceHydration(message: ChatMessage): boolean {
if (message.role !== "agent") return false;
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
if (!traceId) return false;
if (!messageHasActiveTraceForHydration(message)) return false;
const trace = message.runnerTrace;
const events = Array.isArray(trace?.events) ? trace.events : [];
const eventCount = firstFiniteNumber(trace?.eventCount) ?? events.length;
@@ -2486,17 +2479,11 @@ function messageNeedsTraceHydration(message: ChatMessage): boolean {
return events.length === 0 || trace?.eventsCompacted === true || trace?.fullTraceLoaded !== true;
}
function messageNeedsTerminalTraceGapHydration(message: ChatMessage): boolean {
if (!messageHasCompletedFinalResponse(message)) return false;
const traceId = firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
if (!traceId) return false;
function messageHasActiveTraceForHydration(message: ChatMessage): boolean {
const trace = message.runnerTrace;
if (!trace) return true;
const events = Array.isArray(trace.events) ? trace.events : [];
const eventCount = firstFiniteNumber(trace.eventCount) ?? events.length;
if (trace.eventsCompacted === true || trace.fullTraceLoaded !== true) return true;
if (events.length === 0 && eventCount > 0) return true;
return isTraceActiveStatus(trace.status) || isTraceActiveStatus(trace.traceStatus);
return isTraceActiveStatus(message.status)
|| isTraceActiveStatus(trace?.status)
|| isTraceActiveStatus(trace?.traceStatus);
}
function traceHasCompletedFinalResponse(traceId: string | null | undefined, source: ChatMessage[]): boolean {