fix: surface agentrun terminal errors in final response

This commit is contained in:
lyon
2026-06-17 00:17:10 +08:00
parent 576ce64f23
commit a67f29ace4
3 changed files with 72 additions and 4 deletions
@@ -1447,6 +1447,14 @@ function agentRunResultToCodeAgentPayload({ base, result, traceStore, traceId, a
const code = canceled ? "agentrun_canceled" : result?.failureKind ?? (terminalStatus === "blocked" ? "agentrun_blocked" : "agentrun_failed");
const message = result?.failureMessage ?? result?.blocker?.message ?? (canceled ? "AgentRun command was canceled" : "AgentRun command failed");
const attribution = agentRunFailureAttribution({ code, message, canceled });
const finalResponse = agentRunTerminalFailureFinalResponse({
base,
traceId,
now,
status: canceled ? "canceled" : "failed",
text: firstNonEmpty(message, attribution.summary, attribution.userMessage)
});
const traceSummary = agentRunTerminalFailureTraceSummary({ base, runnerTrace, finalResponse, traceId, terminalStatus: canceled ? "canceled" : terminalStatus || "failed" });
if (appendResultEvent) {
traceStore.append(traceId, {
type: "result",
@@ -1477,6 +1485,8 @@ function agentRunResultToCodeAgentPayload({ base, result, traceStore, traceId, a
toolCalls: agentRunToolCalls(result, canceled ? "canceled" : "failed"),
skills: { status: "delegated", provider: ADAPTER_ID, count: 0, items: [], valuesPrinted: false },
providerTrace,
finalResponse,
traceSummary,
error: {
code,
layer: "agentrun",
@@ -1520,6 +1530,47 @@ function agentRunCompletedFinalResponse({ base, result, traceId, now }) {
};
}
function agentRunTerminalFailureFinalResponse({ base, traceId, now, status, text }) {
const textValue = String(text ?? "").trim();
return {
text: textValue || "AgentRun command failed.",
textChars: (textValue || "AgentRun command failed.").length,
role: "assistant",
status,
traceId,
messageId: base.messageId ?? base.reply?.messageId ?? `msg_${traceId.slice(4)}`,
createdAt: base.reply?.createdAt ?? base.createdAt ?? now,
updatedAt: now,
valuesPrinted: false
};
}
function agentRunTerminalFailureTraceSummary({ base, runnerTrace, finalResponse, traceId, terminalStatus }) {
const events = Array.isArray(runnerTrace?.events) ? runnerTrace.events : [];
const lastSeq = Math.max(0, ...events.map((event) => Number(event?.seq ?? 0)).filter(Number.isFinite));
return {
traceId,
source: "agentrun-command-result",
sourceEventCount: Number(runnerTrace?.eventCount ?? events.length ?? 0),
terminalStatus,
finalAssistantRow: {
role: finalResponse.role,
status: finalResponse.status,
textChars: finalResponse.textChars,
textPreview: finalResponse.text.slice(0, 240),
messageId: finalResponse.messageId,
valuesPrinted: false
},
agentRun: {
runId: base.agentRun?.runId ?? null,
commandId: base.agentRun?.commandId ?? null,
lastSeq,
valuesPrinted: false
},
valuesPrinted: false
};
}
function agentRunCompletedTraceSummary({ base, runnerTrace, finalResponse, traceId }) {
const events = Array.isArray(runnerTrace?.events) ? runnerTrace.events : [];
const lastSeq = Math.max(0, ...events.map((event) => Number(event?.seq ?? 0)).filter(Number.isFinite));
+4
View File
@@ -1071,6 +1071,10 @@ test("cloud api AgentRun adapter exposes invalid tool-call attribution in result
assert.match(payload.error.userMessage, / tool-call arguments JSON/u);
assert.match(payload.error.userMessage, /AgentRun\/provider/u);
assert.match(payload.error.userMessage, / HWPOD/u);
assert.equal(payload.finalResponse.status, "failed");
assert.match(payload.finalResponse.text, /invalid function arguments json string/u);
assert.equal(payload.traceSummary.terminalStatus, "failed");
assert.match(payload.traceSummary.finalAssistantRow.textPreview, /invalid function arguments json string/u);
assert.equal(payload.blocker.category, "provider_invalid_tool_call");
assert.match(payload.blocker.summary, /invalid function arguments json string/u);
assert.equal(payload.session.status, "failed");
+17 -4
View File
@@ -295,10 +295,11 @@ export const useWorkbenchStore = defineStore("workbench", () => {
if (message.traceId !== traceId || message.role !== "agent") return message;
const runnerTrace = mergeTerminalResultTrace(message.runnerTrace, result as AgentChatResultResponse);
const error = normalizeAgentError((result as AgentChatResultResponse).error ?? runnerTrace?.error ?? message.error);
const errorText = agentErrorDisplayText(error);
const agentRun = agentRunFromResult(result as AgentChatResultResponse, runnerTrace) ?? agentRunFromMessage(message);
const replyText = agentReplyText((result as AgentChatResultResponse).reply);
const text = terminal
? firstNonEmptyString((result as AgentChatResultResponse).assistantText, finalResponseText((result as AgentChatResultResponse).finalResponse), replyText, (result as AgentChatResultResponse).text, (result as AgentChatResultResponse).summary, message.text, "Code Agent 已完成,但没有返回可展示的 final response。") ?? message.text
? firstNonEmptyString((result as AgentChatResultResponse).assistantText, finalResponseText((result as AgentChatResultResponse).finalResponse), replyText, errorText, (result as AgentChatResultResponse).text, (result as AgentChatResultResponse).summary, message.text, "Code Agent 已完成,但没有返回可展示的 final response。") ?? message.text
: message.text;
return { ...message, status, text, runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
});
@@ -448,8 +449,9 @@ export const useWorkbenchStore = defineStore("workbench", () => {
const explicitStatus = normalizedStatusText(result.status);
const status = explicitStatus ? statusFromResult(result.status) : message.status;
const terminal = result.terminal === true || isTerminalMessageStatus(status);
const text = terminal ? firstNonEmptyString(result.assistantText, finalResponseText(result.finalResponse), agentReplyText(result.reply), result.text, result.summary, message.text) ?? message.text : message.text;
const error = normalizeAgentError(result.error ?? runnerTrace?.error ?? message.error);
const errorText = agentErrorDisplayText(error);
const text = terminal ? firstNonEmptyString(result.assistantText, finalResponseText(result.finalResponse), agentReplyText(result.reply), errorText, result.text, result.summary, message.text) ?? message.text : message.text;
const agentRun = agentRunFromResult(result, runnerTrace) ?? agentRunFromMessage(message);
return { ...message, status, text, runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined, updatedAt: new Date().toISOString() };
});
@@ -525,7 +527,7 @@ export const useWorkbenchStore = defineStore("workbench", () => {
}
function completeTrace(traceId: string, result: AgentChatResultResponse): void {
const text = firstNonEmptyString(result.assistantText, finalResponseText(result.finalResponse), typeof result.reply === "string" ? result.reply : result.reply?.content, result.text, result.summary) ?? "Code Agent 已完成,但没有返回可展示的 final response。";
const text = firstNonEmptyString(result.assistantText, finalResponseText(result.finalResponse), typeof result.reply === "string" ? result.reply : result.reply?.content, agentErrorDisplayText(result.error), result.text, result.summary) ?? "Code Agent 已完成,但没有返回可展示的 final response。";
const terminalStatus = result.status === "completed" ? "completed" : statusFromResult(result.status);
messages.value = messages.value.map((message) => {
if (message.traceId !== traceId || message.role !== "agent") return message;
@@ -732,11 +734,16 @@ function selectedConversationFromWorkspace(workspace: WorkspaceRecord | null): C
}
function normalizeChatMessage(message: ChatMessage): ChatMessage {
const text = firstNonEmptyString(message.text, messageText((message as Record<string, unknown>).content), messageText((message as Record<string, unknown>).message), finalResponseText((message as Record<string, unknown>).finalResponse)) ?? "";
const runnerTrace = normalizeMessageRunnerTrace(message);
const error = normalizeAgentError(message.error ?? runnerTrace?.error);
const agentRun = agentRunFromMessage(message) ?? asAgentRun(runnerTrace?.agentRun);
const status = normalizeChatMessageStatus(message.status);
const baseText = firstNonEmptyString(message.text, messageText((message as Record<string, unknown>).content), messageText((message as Record<string, unknown>).message));
const finalText = firstNonEmptyString(finalResponseText((message as Record<string, unknown>).finalResponse), finalResponseText(runnerTrace?.finalResponse));
const errorText = agentErrorDisplayText(error);
const text = message.role === "agent" && isTerminalMessageStatus(status)
? firstNonEmptyString(finalText, errorText, baseText) ?? ""
: firstNonEmptyString(baseText, finalText, errorText) ?? "";
return { ...message, text, id: message.id ?? nextProtocolId("msg"), title: normalizeWorkbenchMessageTitle(message.role, message.title), createdAt: message.createdAt ?? new Date().toISOString(), status, runnerTrace, error: error ?? message.error ?? null, agentRun: agentRun ?? undefined };
}
@@ -847,6 +854,12 @@ function normalizeAgentError(value: unknown): ChatMessage["error"] | null {
return { message: String(value) };
}
function agentErrorDisplayText(value: unknown): string | null {
const error = normalizeAgentError(value);
if (!error) return null;
return firstNonEmptyString(error.message, typeof error.userMessage === "string" ? error.userMessage : null, error.code ? `Code Agent 请求失败:${error.code}` : null);
}
function isTerminalMessageStatus(value: unknown): boolean {
return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"));
}