mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:50:42 +00:00
fix: surface external agent errors
This commit is contained in:
@@ -25,6 +25,9 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/replies: forward sanitized underlying agent failure details on external
|
||||
channels instead of replacing unknown failures with a generic retry message.
|
||||
Thanks @steipete.
|
||||
- Agents/TTS: preserve `[[audio_as_voice]]` directives on trusted text
|
||||
tool-result `MEDIA:` payloads so generated audio still delivers as a voice
|
||||
note. (#46535) Thanks @azade-c.
|
||||
|
||||
@@ -1662,7 +1662,9 @@ describe("runAgentTurnWithFallback", () => {
|
||||
|
||||
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).toContain("Agent failed before reply");
|
||||
expect(result.payload.text).toContain("All models failed");
|
||||
expect(result.payload.text).toContain("402 (billing)");
|
||||
expect(result.payload.text).not.toContain("Rate-limited");
|
||||
}
|
||||
});
|
||||
@@ -1877,7 +1879,7 @@ describe("runAgentTurnWithFallback", () => {
|
||||
expect(failMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns a friendly generic error on external chat channels", async () => {
|
||||
it("forwards sanitized generic errors on external chat channels", async () => {
|
||||
state.runEmbeddedPiAgentMock.mockRejectedValueOnce(
|
||||
new Error("INVALID_ARGUMENT: some other failure"),
|
||||
);
|
||||
@@ -1910,7 +1912,47 @@ describe("runAgentTurnWithFallback", () => {
|
||||
expect(result.kind).toBe("final");
|
||||
if (result.kind === "final") {
|
||||
expect(result.payload.text).toBe(
|
||||
"⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.",
|
||||
"⚠️ Agent failed before reply: INVALID_ARGUMENT: some other failure. Please try again, or use /new to start a fresh session.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("formats raw Codex API payloads before forwarding external errors", async () => {
|
||||
state.runEmbeddedPiAgentMock.mockRejectedValueOnce(
|
||||
new Error(
|
||||
'Codex error: {"type":"error","error":{"type":"server_error","message":"Something exploded"},"sequence_number":2}',
|
||||
),
|
||||
);
|
||||
|
||||
const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();
|
||||
const result = await runAgentTurnWithFallback({
|
||||
commandBody: "hello",
|
||||
followupRun: createFollowupRun(),
|
||||
sessionCtx: {
|
||||
Provider: "whatsapp",
|
||||
MessageSid: "msg",
|
||||
} as unknown as TemplateContext,
|
||||
opts: {},
|
||||
typingSignals: createMockTypingSignaler(),
|
||||
blockReplyPipeline: null,
|
||||
blockStreamingEnabled: false,
|
||||
resolvedBlockStreamingBreak: "message_end",
|
||||
applyReplyToMode: (payload) => payload,
|
||||
shouldEmitToolResult: () => true,
|
||||
shouldEmitToolOutput: () => false,
|
||||
pendingToolTasks: new Set(),
|
||||
resetSessionAfterCompactionFailure: async () => false,
|
||||
resetSessionAfterRoleOrderingConflict: async () => false,
|
||||
isHeartbeat: false,
|
||||
sessionKey: "main",
|
||||
getActiveSessionEntry: () => undefined,
|
||||
resolvedVerboseLevel: "off",
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("final");
|
||||
if (result.kind === "final") {
|
||||
expect(result.payload.text).toBe(
|
||||
"⚠️ Agent failed before reply: LLM error server_error: Something exploded. Please try again, or use /new to start a fresh session.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -352,6 +352,7 @@ function collapseRepeatedFailureDetail(message: string): string {
|
||||
}
|
||||
|
||||
const SAFE_MISSING_API_KEY_PROVIDERS = new Set(["anthropic", "google", "openai", "openai-codex"]);
|
||||
const EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS = 900;
|
||||
|
||||
function buildMissingApiKeyFailureText(message: string): string | null {
|
||||
const normalizedMessage = collapseRepeatedFailureDetail(message);
|
||||
@@ -369,6 +370,22 @@ function buildMissingApiKeyFailureText(message: string): string | null {
|
||||
return "⚠️ Missing API key for the selected provider on the gateway. Configure provider auth, then try again.";
|
||||
}
|
||||
|
||||
function formatForwardedExternalRunFailureText(message: string): string {
|
||||
const sanitized = sanitizeUserFacingText(message, { errorContext: true })
|
||||
.trim()
|
||||
.replace(/^⚠️\s*/u, "")
|
||||
.replace(/\s+/gu, " ");
|
||||
if (!sanitized) {
|
||||
return "⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.";
|
||||
}
|
||||
const detail =
|
||||
sanitized.length > EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS
|
||||
? `${sanitized.slice(0, EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS - 1).trimEnd()}…`
|
||||
: sanitized;
|
||||
const suffix = /[.!?]$/u.test(detail) ? "" : ".";
|
||||
return `⚠️ Agent failed before reply: ${detail}${suffix} Please try again, or use /new to start a fresh session.`;
|
||||
}
|
||||
|
||||
function buildExternalRunFailureText(message: string): string {
|
||||
const normalizedMessage = collapseRepeatedFailureDetail(message);
|
||||
if (isToolResultTurnMismatchError(normalizedMessage)) {
|
||||
@@ -386,7 +403,7 @@ function buildExternalRunFailureText(message: string): string {
|
||||
}
|
||||
return `⚠️ Model login failed on the gateway${oauthRefreshFailure.provider ? ` for ${oauthRefreshFailure.provider}` : ""}. Please try again. If this keeps happening, re-auth with \`${loginCommand}\`.`;
|
||||
}
|
||||
return "⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.";
|
||||
return formatForwardedExternalRunFailureText(normalizedMessage);
|
||||
}
|
||||
|
||||
function shouldApplyOpenAIGptChatGuard(params: { provider?: string; model?: string }): boolean {
|
||||
|
||||
Reference in New Issue
Block a user