diff --git a/src/auto-reply/reply/agent-runner-error-handler.ts b/src/auto-reply/reply/agent-runner-error-handler.ts index 95c7bae27100..91541346feec 100644 --- a/src/auto-reply/reply/agent-runner-error-handler.ts +++ b/src/auto-reply/reply/agent-runner-error-handler.ts @@ -12,6 +12,7 @@ import { isRateLimitErrorMessage, isTransientHttpError, } from "../../agents/embedded-agent-helpers.js"; +import { sanitizeUserFacingText } from "../../agents/embedded-agent-helpers/sanitize-user-facing-text.js"; import { isFailoverError } from "../../agents/failover-error.js"; import { LiveSessionModelSwitchError } from "../../agents/live-model-switch-error.js"; import { isFallbackSummaryError } from "../../agents/model-fallback.js"; @@ -437,19 +438,20 @@ export async function handleAgentExecutionError(params: { failoverReason === "overloaded" ? "overloaded" : message, ) : undefined; + const trimmedMessage = ( + isTransientHttp ? sanitizeUserFacingText(message, { errorContext: true }) : message + ).replace(/\.\s*$/, ""); const externalRunFailureReply = !isBilling && !(isRateLimit && !isOverloaded) && !rateLimitOrOverloadedCopy && - !isContextOverflow + !isContextOverflow && + !params.shouldSurfaceToControlUi ? buildExternalRunFailureReply( { message, error: err }, { - includeAuthProfileId: - !params.shouldSurfaceToControlUi && !isNonDirectConversationContext(turn.sessionCtx), - includeDetails: - !params.shouldSurfaceToControlUi && - isVerboseFailureDetailEnabled(turn.resolvedVerboseLevel), + includeAuthProfileId: !isNonDirectConversationContext(turn.sessionCtx), + includeDetails: isVerboseFailureDetailEnabled(turn.resolvedVerboseLevel), isHeartbeat: turn.isHeartbeat, replayPrevented: params.overloadRetryState.unsafeToReplay, }, @@ -463,10 +465,12 @@ export async function handleAgentExecutionError(params: { ? rateLimitOrOverloadedCopy : isContextOverflow ? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model." - : (externalRunFailureReply?.text ?? - (turn.isHeartbeat - ? HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT - : GENERIC_EXTERNAL_RUN_FAILURE_TEXT)); + : params.shouldSurfaceToControlUi + ? `⚠️ Agent failed before reply: ${trimmedMessage}.\nLogs: openclaw logs --follow` + : (externalRunFailureReply?.text ?? + (turn.isHeartbeat + ? HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT + : GENERIC_EXTERNAL_RUN_FAILURE_TEXT)); const userVisibleFallbackText = resolveExternalRunFailureTextForConversation({ text: fallbackText, sessionCtx: turn.sessionCtx, diff --git a/src/auto-reply/reply/agent-runner-execution-conversation-failures.test.ts b/src/auto-reply/reply/agent-runner-execution-conversation-failures.test.ts index 23ee93ae81e4..09404789e059 100644 --- a/src/auto-reply/reply/agent-runner-execution-conversation-failures.test.ts +++ b/src/auto-reply/reply/agent-runner-execution-conversation-failures.test.ts @@ -118,7 +118,7 @@ describe("runAgentTurnWithFallback: conversation failures", () => { } }); - it("keeps raw failure details out of the internal control surface in verbose mode", async () => { + it("keeps raw generic errors on internal control surfaces", async () => { state.isInternalMessageChannelMock.mockReturnValue(true); state.runEmbeddedAgentMock.mockRejectedValueOnce( new Error("INVALID_ARGUMENT: some other failure"), @@ -146,14 +146,14 @@ describe("runAgentTurnWithFallback: conversation failures", () => { isHeartbeat: false, sessionKey: "main", getActiveSessionEntry: () => undefined, - resolvedVerboseLevel: "full", + resolvedVerboseLevel: "off", }); expect(result.kind).toBe("final"); if (result.kind === "final") { - expect(result.payload.text).toContain("Something went wrong while processing your request"); - expect(result.payload.text).not.toContain("INVALID_ARGUMENT: some other failure"); - expect(result.payload).not.toHaveProperty("errorDetails"); + expect(result.payload.text).toContain("Agent failed before reply"); + expect(result.payload.text).toContain("INVALID_ARGUMENT: some other failure"); + expect(result.payload.text).toContain("Logs: openclaw logs --follow"); } }); }); diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index ae5b22c7f26a..9636984e4a8a 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -3712,9 +3712,6 @@ export const en: TranslationMap = { switchedSession: "Switched to {session}", actions: { dismissError: "Dismiss error", - showErrorDetails: "Show details", - hideErrorDetails: "Hide details", - errorLogsCommand: "Logs: openclaw logs --follow", exitFocusMode: "Exit focus mode", scrollToLatest: "Scroll to latest", },