Merge pull request #1784 from pikasTech/fix/workbench-terminal-live-1770

fix(workbench): active turn 持续收敛到终态
This commit is contained in:
Lyon
2026-06-21 05:38:12 +08:00
committed by GitHub
+43 -25
View File
@@ -19,8 +19,8 @@ const TRACE_HYDRATION_PAGE_LIMIT = 100;
const TRACE_HYDRATION_MAX_PAGES = 60;
const TRACE_HYDRATION_MAX_ATTEMPTS = 3;
const TRACE_HYDRATION_RETRY_DELAY_MS = 700;
const ACTIVE_TURN_GAP_DELAY_MS = 1_000;
const ACTIVE_TURN_GAP_MAX_ATTEMPTS = 12;
const ACTIVE_TURN_GAP_INITIAL_DELAY_MS = 1_000;
const ACTIVE_TURN_GAP_MAX_DELAY_MS = 5_000;
const SESSION_LIST_PAGE_LIMIT = 20;
const WORKBENCH_READ_HYDRATION_CONCURRENCY = 3;
@@ -726,14 +726,29 @@ export const useWorkbenchStore = defineStore("workbench", () => {
lastEventLabel: result.lastEventLabel ?? undefined,
updatedAt: new Date().toISOString()
} as NonNullable<ChatMessage["runnerTrace"]>;
const runnerTrace = mergeRunnerTrace(message.runnerTrace, nextTrace);
const error = isCompletedResultWithFinalResponse(result) ? null : normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null;
const resultStatus = normalizedStatusText(result.status) ?? null;
const terminal = result.terminal === true || isTerminalMessageStatus(resultStatus);
const resultError = normalizeAgentError(result.error ?? null);
const resultProjection = projectionFromResult(result);
const mergedRunnerTrace = mergeRunnerTrace(message.runnerTrace, nextTrace);
const runnerTrace = terminal && !resultError && !resultProjection ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
const error = resultError ?? (terminal ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
const projection = resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
rememberTraceAuthority(runnerTrace);
return { ...message, ...messageTimingPatch(result), runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
return { ...message, ...messageTimingPatch(result), runnerTrace, error, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
}));
markWorkbenchTraceProjected(traceId);
if (result.terminal === true || isTerminalMessageStatus(result.status)) {
rememberTurnStatus(traceId, result);
clearActiveTurnGapRefresh(traceId);
if (ownerSessionId === activeSessionId.value) {
chatPending.value = false;
currentRequest.value = null;
void clearActiveTrace(traceId, "trace-hydration-terminal");
restartRealtime("trace-hydration-terminal");
}
}
}
function setProviderProfile(value: ProviderProfile): void {
@@ -983,13 +998,14 @@ export const useWorkbenchStore = defineStore("workbench", () => {
}
if (activeTurnGapTimer) return;
const attempts = activeTurnGapAttempts.get(traceId) ?? 0;
if (attempts >= ACTIVE_TURN_GAP_MAX_ATTEMPTS) return;
activeTurnGapAttempts.set(traceId, attempts + 1);
const nextAttempt = attempts + 1;
activeTurnGapAttempts.set(traceId, nextAttempt);
if (typeof window === "undefined") return;
const delayMs = Math.min(ACTIVE_TURN_GAP_MAX_DELAY_MS, ACTIVE_TURN_GAP_INITIAL_DELAY_MS * Math.max(1, nextAttempt));
activeTurnGapTimer = window.setTimeout(() => {
activeTurnGapTimer = null;
void hydrateRealtimeGap(`active-turn-gap:${reason}`);
}, ACTIVE_TURN_GAP_DELAY_MS);
}, delayMs);
}
function activeTurnGapTraceId(): string | null {
@@ -1048,12 +1064,17 @@ export const useWorkbenchStore = defineStore("workbench", () => {
if (ownerSessionId === activeSessionId.value) recordActivity(`trace:terminal:${firstNonEmptyString(result.lastEventLabel, result.status, "completed") ?? "completed"}`);
updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
const resultStatus = normalizedStatusText(result.status) ?? null;
const terminal = result.terminal === true || isTerminalMessageStatus(resultStatus);
const resultError = normalizeAgentError(result.error ?? null);
const resultProjection = projectionFromResult(result);
const mergedRunnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
const runnerTrace = terminal && !resultError && !resultProjection ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
rememberTraceAuthority(runnerTrace);
const error = isCompletedResultWithFinalResponse(result) ? null : normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null;
const error = resultError ?? (terminal ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
const projection = resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
return { ...message, ...messageTimingPatch(result), runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
return { ...message, ...messageTimingPatch(result), runnerTrace, error, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
}));
rememberTurnStatus(traceId, result);
markWorkbenchTraceProjected(traceId);
@@ -1087,12 +1108,17 @@ export const useWorkbenchStore = defineStore("workbench", () => {
if (!ownerSessionId) return;
updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return message;
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
const resultStatus = normalizedStatusText(result.status) ?? null;
const terminal = result.terminal === true || isTerminalMessageStatus(resultStatus);
const resultError = normalizeAgentError(result.error ?? null);
const resultProjection = projectionFromResult(result);
const mergedRunnerTrace = mergeTerminalResultTrace(message.runnerTrace, result);
const runnerTrace = terminal && !resultError && !resultProjection ? clearRunnerTraceTransientDiagnostics(mergedRunnerTrace) : mergedRunnerTrace;
rememberTraceAuthority(runnerTrace);
const error = isCompletedResultWithFinalResponse(result) ? null : normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
const error = resultError ?? (terminal ? null : normalizeAgentError(runnerTrace?.error ?? message.error));
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null;
return { ...message, ...messageTimingPatch(result), title: normalizeWorkbenchMessageTitle(message.role, message.title), runnerTrace, error: error ?? message.error ?? null, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
const projection = resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
return { ...message, ...messageTimingPatch(result), title: normalizeWorkbenchMessageTitle(message.role, message.title), runnerTrace, error, projection, projectionStatus: projection?.projectionStatus ?? null, projectionHealth: projection?.projectionHealth ?? null, blocker: projection?.blocker ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
}));
void hydrateTraceEvents(serverState.value.messagesBySessionId[ownerSessionId] ?? []);
void refreshSessions(ownerSessionId);
@@ -1765,14 +1791,6 @@ function projectedAgentMessageText(input: { status: unknown; finalText?: string
return firstNonEmptyString(input.errorText, text) ?? "";
}
function isCompletedResultWithFinalResponse(value: unknown): boolean {
const record = recordValue(value);
if (!record) return false;
const status = normalizedStatusText(record.status);
if (status !== "completed") return false;
return Boolean(finalResponseText(record.finalResponse) ?? messageText(record.assistantText) ?? messageText(record.text) ?? messageText(record.summary));
}
function isFailureTerminalStatus(value: unknown): boolean {
return ["failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}