mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-19 01:44:47 +00:00
fix(acp): preserve RequestError details
This commit is contained in:
committed by
Shakker
parent
207fb9951d
commit
c5071a8061
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatAcpRuntimeErrorText } from "./error-text.js";
|
||||
import { AcpRuntimeError } from "./errors.js";
|
||||
import { formatAcpRuntimeErrorText, toAcpRuntimeErrorText } from "./error-text.js";
|
||||
import { AcpRuntimeError, toAcpRuntimeError } from "./errors.js";
|
||||
|
||||
describe("formatAcpRuntimeErrorText", () => {
|
||||
it("adds actionable next steps for known ACP runtime error codes", () => {
|
||||
@@ -18,4 +18,49 @@ describe("formatAcpRuntimeErrorText", () => {
|
||||
"ACP error (ACP_TURN_FAILED): turn failed\nnext: Retry, or use `/acp cancel` and send the message again.",
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces redacted numeric RequestError details in runtime failure text", () => {
|
||||
const token = "sk-abcdefghijklmnopqrstuvwxyz123456";
|
||||
const requestError = Object.assign(new Error("Internal error"), {
|
||||
name: "RequestError",
|
||||
code: -32603,
|
||||
data: {
|
||||
details: `Unknown config option: timeout; token=${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const text = formatAcpRuntimeErrorText(
|
||||
toAcpRuntimeError({
|
||||
error: requestError,
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "fallback",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(text).toContain(
|
||||
"ACP error (ACP_TURN_FAILED): Internal error: Unknown config option: timeout",
|
||||
);
|
||||
expect(text).toContain("next: Retry");
|
||||
expect(text).not.toContain(token);
|
||||
});
|
||||
|
||||
it("applies the same RequestError details normalization through text conversion", () => {
|
||||
const requestError = Object.assign(new Error("Internal error"), {
|
||||
name: "RequestError",
|
||||
code: -32603,
|
||||
data: {
|
||||
details: "Unknown config option: timeout",
|
||||
},
|
||||
});
|
||||
|
||||
const text = toAcpRuntimeErrorText({
|
||||
error: requestError,
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "fallback",
|
||||
});
|
||||
|
||||
expect(text).toContain(
|
||||
"ACP error (ACP_TURN_FAILED): Internal error: Unknown config option: timeout",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
AcpRuntimeError,
|
||||
formatAcpErrorChain,
|
||||
isAcpRuntimeError,
|
||||
toAcpRuntimeError,
|
||||
withAcpRuntimeErrorBoundary,
|
||||
} from "./errors.js";
|
||||
|
||||
@@ -72,6 +73,65 @@ describe("withAcpRuntimeErrorBoundary", () => {
|
||||
expect(error.cause).toBe(foreignError);
|
||||
expect(isAcpRuntimeError(foreignError)).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves redacted RequestError details from numeric ACP errors", () => {
|
||||
const token = "sk-abcdefghijklmnopqrstuvwxyz123456";
|
||||
const requestError = Object.assign(new Error("Internal error"), {
|
||||
name: "RequestError",
|
||||
code: -32603,
|
||||
data: {
|
||||
details: `unknown config option: timeout; token=${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const error = toAcpRuntimeError({
|
||||
error: requestError,
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "fallback",
|
||||
});
|
||||
|
||||
expect(error.code).toBe("ACP_TURN_FAILED");
|
||||
expect(error.message).toContain("Internal error: unknown config option: timeout");
|
||||
expect(error.message).not.toContain(token);
|
||||
expect(error.cause).toBe(requestError);
|
||||
});
|
||||
|
||||
it("keeps foreign OpenClaw ACP string code behavior unchanged", () => {
|
||||
const foreignError = Object.assign(new Error("backend missing"), {
|
||||
code: "ACP_BACKEND_MISSING",
|
||||
data: {
|
||||
details: "extra backend diagnostic",
|
||||
},
|
||||
});
|
||||
|
||||
const error = toAcpRuntimeError({
|
||||
error: foreignError,
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "fallback",
|
||||
});
|
||||
|
||||
expect(error.code).toBe("ACP_BACKEND_MISSING");
|
||||
expect(error.message).toBe("backend missing");
|
||||
expect(error.cause).toBe(foreignError);
|
||||
});
|
||||
|
||||
it("keeps generic non-RequestError messages unchanged", () => {
|
||||
const sourceError = Object.assign(new Error("boom"), {
|
||||
data: {
|
||||
details: "extra diagnostic",
|
||||
},
|
||||
});
|
||||
|
||||
const error = toAcpRuntimeError({
|
||||
error: sourceError,
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "fallback",
|
||||
});
|
||||
|
||||
expect(error.code).toBe("ACP_TURN_FAILED");
|
||||
expect(error.message).toBe("boom");
|
||||
expect(error.cause).toBe(sourceError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatAcpErrorChain redaction", () => {
|
||||
|
||||
@@ -43,6 +43,31 @@ function getForeignAcpRuntimeError(value: unknown): {
|
||||
};
|
||||
}
|
||||
|
||||
function readAcpRequestErrorDetails(value: Error): string | undefined {
|
||||
const code = (value as { code?: unknown }).code;
|
||||
if (typeof code !== "number") {
|
||||
return undefined;
|
||||
}
|
||||
const data = (value as { data?: unknown }).data;
|
||||
if (!data || typeof data !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const details = (data as { details?: unknown }).details;
|
||||
if (details === undefined || details === null) {
|
||||
return undefined;
|
||||
}
|
||||
const rendered = redactSensitiveText(stringifyNonErrorCause(details)).trim();
|
||||
return rendered.length > 0 ? rendered : undefined;
|
||||
}
|
||||
|
||||
function messageWithAcpRequestErrorDetails(error: Error): string {
|
||||
const details = readAcpRequestErrorDetails(error);
|
||||
if (!details || error.message.includes(details)) {
|
||||
return error.message;
|
||||
}
|
||||
return `${error.message}: ${details}`;
|
||||
}
|
||||
|
||||
export function isAcpRuntimeError(value: unknown): value is AcpRuntimeError {
|
||||
return value instanceof AcpRuntimeError || getForeignAcpRuntimeError(value) !== null;
|
||||
}
|
||||
@@ -62,9 +87,13 @@ export function toAcpRuntimeError(params: {
|
||||
});
|
||||
}
|
||||
if (params.error instanceof Error) {
|
||||
return new AcpRuntimeError(params.fallbackCode, params.error.message, {
|
||||
cause: params.error,
|
||||
});
|
||||
return new AcpRuntimeError(
|
||||
params.fallbackCode,
|
||||
messageWithAcpRequestErrorDetails(params.error),
|
||||
{
|
||||
cause: params.error,
|
||||
},
|
||||
);
|
||||
}
|
||||
return new AcpRuntimeError(params.fallbackCode, params.fallbackMessage, {
|
||||
cause: params.error,
|
||||
|
||||
Reference in New Issue
Block a user