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_PAGES = 60;
const TRACE_HYDRATION_MAX_ATTEMPTS = 3; const TRACE_HYDRATION_MAX_ATTEMPTS = 3;
const TRACE_HYDRATION_RETRY_DELAY_MS = 700; const TRACE_HYDRATION_RETRY_DELAY_MS = 700;
const ACTIVE_TURN_GAP_DELAY_MS = 1_000; const ACTIVE_TURN_GAP_INITIAL_DELAY_MS = 1_000;
const ACTIVE_TURN_GAP_MAX_ATTEMPTS = 12; const ACTIVE_TURN_GAP_MAX_DELAY_MS = 5_000;
const SESSION_LIST_PAGE_LIMIT = 20; const SESSION_LIST_PAGE_LIMIT = 20;
const WORKBENCH_READ_HYDRATION_CONCURRENCY = 3; const WORKBENCH_READ_HYDRATION_CONCURRENCY = 3;
@@ -726,14 +726,29 @@ export const useWorkbenchStore = defineStore("workbench", () => {
lastEventLabel: result.lastEventLabel ?? undefined, lastEventLabel: result.lastEventLabel ?? undefined,
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString()
} as NonNullable<ChatMessage["runnerTrace"]>; } as NonNullable<ChatMessage["runnerTrace"]>;
const runnerTrace = mergeRunnerTrace(message.runnerTrace, nextTrace); const resultStatus = normalizedStatusText(result.status) ?? null;
const error = isCompletedResultWithFinalResponse(result) ? null : normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error); const terminal = result.terminal === true || isTerminalMessageStatus(resultStatus);
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null; 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); const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
rememberTraceAuthority(runnerTrace); 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); 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 { function setProviderProfile(value: ProviderProfile): void {
@@ -983,13 +998,14 @@ export const useWorkbenchStore = defineStore("workbench", () => {
} }
if (activeTurnGapTimer) return; if (activeTurnGapTimer) return;
const attempts = activeTurnGapAttempts.get(traceId) ?? 0; const attempts = activeTurnGapAttempts.get(traceId) ?? 0;
if (attempts >= ACTIVE_TURN_GAP_MAX_ATTEMPTS) return; const nextAttempt = attempts + 1;
activeTurnGapAttempts.set(traceId, attempts + 1); activeTurnGapAttempts.set(traceId, nextAttempt);
if (typeof window === "undefined") return; 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 = window.setTimeout(() => {
activeTurnGapTimer = null; activeTurnGapTimer = null;
void hydrateRealtimeGap(`active-turn-gap:${reason}`); void hydrateRealtimeGap(`active-turn-gap:${reason}`);
}, ACTIVE_TURN_GAP_DELAY_MS); }, delayMs);
} }
function activeTurnGapTraceId(): string | null { 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"}`); if (ownerSessionId === activeSessionId.value) recordActivity(`trace:terminal:${firstNonEmptyString(result.lastEventLabel, result.status, "completed") ?? "completed"}`);
updateSessionMessages(ownerSessionId, (source) => source.map((message) => { updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return 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); 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 projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null; const projection = resultProjection ?? (terminal ? null : runnerTrace.projection ?? message.projection ?? null);
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message); 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); rememberTurnStatus(traceId, result);
markWorkbenchTraceProjected(traceId); markWorkbenchTraceProjected(traceId);
@@ -1087,12 +1108,17 @@ export const useWorkbenchStore = defineStore("workbench", () => {
if (!ownerSessionId) return; if (!ownerSessionId) return;
updateSessionMessages(ownerSessionId, (source) => source.map((message) => { updateSessionMessages(ownerSessionId, (source) => source.map((message) => {
if (!messageMatchesTraceAuthority(message, traceId, authoritySessionId, ownerSessionId)) return 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); 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 agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
const projection = projectionFromResult(result) ?? runnerTrace.projection ?? message.projection ?? null; const projection = resultProjection ?? (terminal ? null : 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() }; 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 hydrateTraceEvents(serverState.value.messagesBySessionId[ownerSessionId] ?? []);
void refreshSessions(ownerSessionId); void refreshSessions(ownerSessionId);
@@ -1765,14 +1791,6 @@ function projectedAgentMessageText(input: { status: unknown; finalText?: string
return firstNonEmptyString(input.errorText, text) ?? ""; 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 { function isFailureTerminalStatus(value: unknown): boolean {
return ["failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-")); return ["failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
} }