fix(workbench): seal final response lifecycle

This commit is contained in:
lyon
2026-06-21 07:26:22 +08:00
parent 7b5b14cc20
commit b6dc2b4126
2 changed files with 61 additions and 15 deletions
+32 -8
View File
@@ -1542,6 +1542,7 @@ function agentRunProjectionActivitySignature(result, runnerTrace) {
function agentRunProjectionObservedTerminal(result, runnerTrace) {
const agentRun = result?.agentRun && typeof result.agentRun === "object" ? result.agentRun : {};
if (codeAgentPayloadHasSealedFinalResponse(result)) return true;
if (
isTraceCommandTerminalStatus(result?.status) ||
isTraceCommandTerminalStatus(agentRun.status) ||
@@ -2021,7 +2022,10 @@ function codeAgentTurnStatusPayload({ traceId, result, snapshot, resultPollError
const lifecycle = codeAgentTurnLifecycleFields(traceId, resultObject ?? snapshotObject ?? {});
const events = Array.isArray(snapshotObject?.events) ? snapshotObject.events : Array.isArray(resultObject?.runnerTrace?.events) ? resultObject.runnerTrace.events : [];
const lastEvent = events.at(-1) ?? null;
const finalResponse = resultObject?.finalResponse ?? snapshotObject?.finalResponse ?? snapshotObject?.terminalEvidence?.finalResponse ?? codeAgentFinalResponseEvidence(resultObject ?? snapshotObject ?? {}, traceId);
const sealedFinalStatus = codeAgentPayloadHasSealedFinalResponse(resultObject ?? snapshotObject ?? {}) ? "completed" : null;
const status = normalizeTurnStatus(
sealedFinalStatus,
resultObject?.status,
resultObject?.agentRun?.terminalStatus,
resultObject?.agentRun?.commandState,
@@ -2056,7 +2060,7 @@ function codeAgentTurnStatusPayload({ traceId, result, snapshot, resultPollError
runnerTrace: runnerTrace ? compactRunnerTraceForResult(runnerTrace, resultTraceEventLimit(options)) : null,
agentRun: resultObject?.agentRun ?? snapshotObject?.agentRun ?? null,
terminalEvidence: snapshotObject?.terminalEvidence ?? null,
finalResponse: resultObject?.finalResponse ?? snapshotObject?.finalResponse ?? snapshotObject?.terminalEvidence?.finalResponse ?? null,
finalResponse,
traceSummary: resultObject?.traceSummary ?? snapshotObject?.traceSummary ?? snapshotObject?.terminalEvidence?.traceSummary ?? null,
retention: snapshotObject?.retention ?? null,
eventCount: numberOrNull(snapshotObject?.eventCount ?? runnerTrace?.eventCount ?? events.length),
@@ -2726,6 +2730,7 @@ async function recordCodeAgentSessionOwner({ payload = {}, params = {}, options
function codeAgentOwnerStatusForResult(result = {}) {
const sessionStatus = textValue(result?.session?.status ?? result?.sessionSummary?.status ?? result?.sessionLifecycleStatus);
if (sessionStatus === "thread-resume-failed") return "thread-resume-failed";
if (codeAgentPayloadHasSealedFinalResponse(result)) return "completed";
if (result?.status === "completed") return "completed";
if (result?.status === "canceled" || result?.status === "cancelled") return "canceled";
return result?.status ?? "active";
@@ -2782,7 +2787,7 @@ function codeAgentTraceResultEvidence(payload = {}, params = {}, traceId = null,
if (!agentRun && !finalResponse && !traceSummary) return null;
return {
traceId: resolvedTraceId,
status: payload.status ?? agentRun?.terminalStatus ?? agentRun?.commandState ?? null,
status: codeAgentPayloadHasSealedFinalResponse(payload) ? "completed" : payload.status ?? agentRun?.terminalStatus ?? agentRun?.commandState ?? null,
conversationId,
sessionId,
threadId,
@@ -3036,12 +3041,27 @@ function safeMessageId(value) {
function codeAgentConversationAgentMessageStatus(payload = {}) {
const status = textValue(payload.status ?? payload.agentRun?.terminalStatus ?? payload.commandState ?? payload.runStatus).toLowerCase();
if (codeAgentPayloadHasSealedFinalResponse(payload)) return "completed";
if (["completed", "done", "success", "active"].includes(status)) return "completed";
if (["failed", "blocked", "error", "timeout", "canceled", "cancelled"].includes(status)) return "failed";
if (payload.error || payload.blocker) return "failed";
return "running";
}
function codeAgentPayloadHasSealedFinalResponse(payload = {}) {
if (!payload || typeof payload !== "object") return false;
if (payload.error || payload.blocker) return false;
if (!codeAgentFinalResponseText(payload)) return false;
const projectionStatus = textValue(payload.projectionStatus ?? payload.projection?.projectionStatus).toLowerCase().replace(/_/gu, "-");
if (projectionStatus === "caught-up") return true;
const agentRun = payload.agentRun && typeof payload.agentRun === "object" ? payload.agentRun : {};
return isTraceCommandTerminalStatus(payload.status) ||
isTraceCommandTerminalStatus(agentRun.status) ||
isTraceCommandTerminalStatus(agentRun.runStatus) ||
isTraceCommandTerminalStatus(agentRun.commandState) ||
isTraceCommandTerminalStatus(agentRun.terminalStatus);
}
function codeAgentConversationRunnerTraceEvidence(runnerTrace = {}, traceId = null) {
const events = Array.isArray(runnerTrace.events) ? runnerTrace.events : [];
const lastEvent = runnerTrace.lastEvent && typeof runnerTrace.lastEvent === "object" ? runnerTrace.lastEvent : events.at(-1) ?? null;
@@ -3069,22 +3089,26 @@ function codeAgentConversationRunnerTraceEvidence(runnerTrace = {}, traceId = nu
}
function codeAgentFinalResponseEvidence(payload = {}, traceId = null) {
const text = conversationText(payload.reply?.content ?? payload.message?.content ?? payload.assistantText);
const text = codeAgentFinalResponseText(payload);
if (!text) return null;
return {
text,
textChars: text.length,
messageId: payload.reply?.messageId ?? payload.messageId ?? null,
messageId: payload.finalResponse?.messageId ?? payload.reply?.messageId ?? payload.messageId ?? null,
role: payload.reply?.role ?? payload.message?.role ?? "assistant",
status: payload.status ?? null,
status: codeAgentPayloadHasSealedFinalResponse(payload) ? "completed" : payload.finalResponse?.status ?? payload.status ?? null,
traceId,
createdAt: payload.reply?.createdAt ?? payload.createdAt ?? null,
updatedAt: payload.updatedAt ?? null,
source: "code-agent-result",
createdAt: payload.finalResponse?.createdAt ?? payload.reply?.createdAt ?? payload.createdAt ?? null,
updatedAt: payload.finalResponse?.updatedAt ?? payload.updatedAt ?? null,
source: payload.finalResponse?.source ?? "code-agent-result",
valuesPrinted: false
};
}
function codeAgentFinalResponseText(payload = {}) {
return conversationText(payload?.finalResponse?.text ?? payload?.finalResponse?.content ?? payload?.reply?.content ?? payload?.message?.content ?? payload?.assistantText);
}
function codeAgentTraceSummaryEvidence(payload = {}, traceId = null, finalResponse = null) {
const runnerTrace = payload.runnerTrace && typeof payload.runnerTrace === "object" ? payload.runnerTrace : null;
const events = Array.isArray(runnerTrace?.events) ? runnerTrace.events : [];
+29 -7
View File
@@ -244,12 +244,15 @@ function buildWorkbenchProjectionFacts({ traceId = null, ownerUserId = null, own
const projection = createWorkbenchTurnProjection({ traceId: safeId, result: payload, session: { id: resolvedSessionId, status: normalizedStatus, session }, trace: payload?.runnerTrace ?? null });
const timing = projectionTimingForStatus(projection.timing, terminal);
const diagnostic = projectionDiagnostics({ traceId: safeId, result: payload, trace: payload?.runnerTrace ?? null, projection });
const finalText = finalResponseTextValue(projection.finalResponse, payload?.finalResponse, session?.finalResponse, payload?.assistantText);
const messages = normalizeMessages(session?.messages, {
traceId: safeId,
sessionId: resolvedSessionId,
conversationId: resolvedConversationId,
threadId: resolvedThreadId,
terminal,
terminalStatus: projection.status ?? normalizedStatus,
finalText,
timestamp,
timing
});
@@ -340,22 +343,28 @@ function buildWorkbenchProjectionFacts({ traceId = null, ownerUserId = null, own
}
function normalizeMessages(messages, context) {
if (!Array.isArray(messages)) return [];
return messages.map((message, index) => normalizeMessageFact(message, index, context)).filter(Boolean);
const normalized = Array.isArray(messages) ? messages.map((message, index) => normalizeMessageFact(message, index, context)).filter(Boolean) : [];
if (context?.terminal && context?.finalText && context?.traceId && !normalized.some((message) => message.role !== "user" && message.traceId === context.traceId)) {
const projected = normalizeMessageFact({ role: "agent", status: context.terminalStatus ?? "completed", text: context.finalText, traceId: context.traceId, source: "workbench-terminal-projection" }, normalized.length, context);
if (projected) normalized.push(projected);
}
return normalized;
}
function normalizeMessageFact(message = {}, index, { traceId, sessionId, conversationId, threadId, terminal, timestamp, timing: contextTiming }) {
function normalizeMessageFact(message = {}, index, { traceId, sessionId, conversationId, threadId, terminal, terminalStatus = "completed", finalText = null, timestamp, timing: contextTiming }) {
const messageId = textValue(message.messageId ?? message.id) || stableFactId("msg", { traceId, index, role: message.role });
if (!sessionId || !messageId) return null;
const role = textValue(message.role) || "agent";
const status = normalizeWorkbenchStatus(message.status ?? (terminal && role !== "user" ? "completed" : role === "user" ? "sent" : "running"));
const messageTerminal = role !== "user" && TERMINAL_STATUSES.has(status);
const messageTraceId = safeTraceId(message.traceId ?? message.turnId);
const appliesToContextTrace = role !== "user" && Boolean(traceId && messageTraceId && messageTraceId === traceId);
const appliesToContextTrace = role !== "user" && Boolean(traceId && (!messageTraceId || messageTraceId === traceId));
const contextTerminalStatus = normalizeWorkbenchStatus(terminalStatus) || "completed";
const status = appliesToContextTrace && terminal ? contextTerminalStatus : normalizeWorkbenchStatus(message.status ?? (terminal && role !== "user" ? contextTerminalStatus : role === "user" ? "sent" : "running"));
const messageTerminal = role !== "user" && TERMINAL_STATUSES.has(status);
const messageTiming = normalizeTimingProjection(message.timing) ?? normalizeTimingProjection(message);
const contextTimingProjection = appliesToContextTrace ? normalizeTimingProjection(contextTiming) : null;
const timing = role === "user" ? messageTiming ?? emptyTimingProjection() : contextTimingProjection ?? messageTiming ?? emptyTimingProjection();
const terminalCompleted = messageTerminal && status === "completed";
const text = appliesToContextTrace && terminal && finalText ? finalText : textValue(message.text ?? message.content ?? message.message);
return {
...message,
messageId,
@@ -371,7 +380,7 @@ function normalizeMessageFact(message = {}, index, { traceId, sessionId, convers
sourceEventId: textValue(message.sourceEventId) || `${messageId}:0`,
terminal: messageTerminal,
sealed: messageTerminal,
text: textValue(message.text ?? message.content ?? message.message),
text,
timing,
startedAt: timing.startedAt,
lastEventAt: timing.lastEventAt,
@@ -384,6 +393,19 @@ function normalizeMessageFact(message = {}, index, { traceId, sessionId, convers
};
}
function finalResponseTextValue(...values) {
for (const value of values) {
if (value && typeof value === "object") {
const nested = finalResponseTextValue(value.text, value.content, value.message, value.summary, value.preview);
if (nested) return nested;
continue;
}
const text = textValue(value);
if (text) return text;
}
return null;
}
async function latestWorkbenchCheckpoint(runtimeStore, traceId) {
if (typeof runtimeStore?.queryWorkbenchFacts !== "function") return null;
const result = await runtimeStore.queryWorkbenchFacts({ traceId, families: ["checkpoints"], limit: 1 });