diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 7c958e53..b3fc5406 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -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)); diff --git a/internal/cloud/server-agent-chat.test.ts b/internal/cloud/server-agent-chat.test.ts index 9dac02ee..3fb158a2 100644 --- a/internal/cloud/server-agent-chat.test.ts +++ b/internal/cloud/server-agent-chat.test.ts @@ -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"); diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index db4f3b1b..53b79abf 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -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).content), messageText((message as Record).message), finalResponseText((message as Record).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).content), messageText((message as Record).message)); + const finalText = firstNonEmptyString(finalResponseText((message as Record).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, "-")); }