Auto-reply: improve user-facing error messages
This commit is contained in:
parent
546e4d940a
commit
aed4d330f4
@ -2192,6 +2192,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/Exec: block grep safe-bin positional operand bypass by setting grep positional budget to zero, so `-e/--regexp` cannot smuggle bare filename reads (for example `.env`) via ambiguous positionals; safe-bin grep patterns must come from `-e/--regexp`. Thanks @athuljayaram for reporting.
|
||||
- Security/Gateway/Agents: remove implicit admin scopes from agent tool gateway calls by classifying methods to least-privilege operator scopes, and enforce owner-only tooling (`cron`, `gateway`, `whatsapp_login`) through centralized tool-policy wrappers plus tool metadata to prevent non-owner DM privilege escalation. Ships in the next npm release. Thanks @Adam55A-code for reporting.
|
||||
- Security/Gateway: centralize gateway method-scope authorization and default non-CLI gateway callers to least-privilege method scopes, with explicit CLI scope handling, full core-handler scope classification coverage, and regression guards to prevent scope drift.
|
||||
- Auto-reply: show user-friendly error messages based on error type (rate limit, auth, billing, timeout) instead of exposing technical details.
|
||||
- Security/Net: block SSRF bypass via NAT64 (`64:ff9b::/96`, `64:ff9b:1::/48`), 6to4 (`2002::/16`), and Teredo (`2001:0000::/32`) IPv6 transition addresses, and fail closed on IPv6 parse errors. Thanks @jackhax.
|
||||
- Security/OTEL: sanitize OTLP endpoint URL resolution. (#13791) Thanks @vincentkoc.
|
||||
- Security: patch Dependabot security issues in pnpm lock. (#20832) Thanks @vincentkoc.
|
||||
|
||||
@ -154,13 +154,31 @@ describe("trigger handling", () => {
|
||||
{
|
||||
error: "sandbox is not defined.",
|
||||
expected:
|
||||
"⚠️ Agent failed before reply: sandbox is not defined.\nLogs: openclaw logs --follow",
|
||||
"Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment.",
|
||||
},
|
||||
{
|
||||
error: "Context window exceeded",
|
||||
expected:
|
||||
"⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model.",
|
||||
},
|
||||
{
|
||||
error: "rate_limit_exceeded: API rate limit exceeded",
|
||||
expected: "The AI service is busy. Please wait a moment and try again.",
|
||||
},
|
||||
{
|
||||
error: "401 Unauthorized: Invalid API key",
|
||||
expected:
|
||||
"I couldn't connect to the AI service. Please verify your API key is configured correctly.",
|
||||
},
|
||||
{
|
||||
error: "402 Payment Required: billing limit exceeded",
|
||||
expected:
|
||||
"I've reached my limit with the AI service. Please check your account balance and try again.",
|
||||
},
|
||||
{
|
||||
error: "408 Request Timeout: connection timed out",
|
||||
expected: "The request timed out. Please try again, or start a fresh session with /new.",
|
||||
},
|
||||
] as const;
|
||||
for (const testCase of errorCases) {
|
||||
runEmbeddedPiAgentMock.mockClear();
|
||||
|
||||
@ -6,13 +6,15 @@ import { getCliSessionId } from "../../agents/cli-session.js";
|
||||
import { runWithModelFallback } from "../../agents/model-fallback.js";
|
||||
import { isCliProvider } from "../../agents/model-selection.js";
|
||||
import {
|
||||
BILLING_ERROR_USER_MESSAGE,
|
||||
isAuthErrorMessage,
|
||||
isBillingErrorMessage,
|
||||
isCompactionFailureError,
|
||||
isContextOverflowError,
|
||||
isBillingErrorMessage,
|
||||
isLikelyContextOverflowError,
|
||||
isOverloadedErrorMessage,
|
||||
isRateLimitErrorMessage,
|
||||
isTimeoutErrorMessage,
|
||||
isTransientHttpError,
|
||||
sanitizeUserFacingText,
|
||||
} from "../../agents/pi-embedded-helpers.js";
|
||||
import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
|
||||
import {
|
||||
@ -534,6 +536,12 @@ export async function runAgentTurnWithFallback(params: {
|
||||
const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message);
|
||||
const isTransientHttp = isTransientHttpError(message);
|
||||
|
||||
const isRateLimit = isRateLimitErrorMessage(message);
|
||||
const isAuthError = isAuthErrorMessage(message);
|
||||
const isBillingError = isBillingErrorMessage(message);
|
||||
const isTimeoutError = isTimeoutErrorMessage(message);
|
||||
const isOverloaded = isOverloadedErrorMessage(message);
|
||||
|
||||
if (
|
||||
isCompactionFailure &&
|
||||
!didResetAfterCompactionFailure &&
|
||||
@ -620,17 +628,30 @@ export async function runAgentTurnWithFallback(params: {
|
||||
}
|
||||
|
||||
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
|
||||
const safeMessage = isTransientHttp
|
||||
? sanitizeUserFacingText(message, { errorContext: true })
|
||||
: message;
|
||||
const trimmedMessage = safeMessage.replace(/\.\s*$/, "");
|
||||
const fallbackText = isBilling
|
||||
? BILLING_ERROR_USER_MESSAGE
|
||||
: isContextOverflow
|
||||
? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."
|
||||
: isRoleOrderingError
|
||||
? "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session."
|
||||
: `⚠️ Agent failed before reply: ${trimmedMessage}.\nLogs: openclaw logs --follow`;
|
||||
|
||||
let fallbackText: string;
|
||||
|
||||
if (isContextOverflow) {
|
||||
fallbackText =
|
||||
"⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model.";
|
||||
} else if (isRoleOrderingError) {
|
||||
fallbackText =
|
||||
"⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session.";
|
||||
} else if (isRateLimit || isOverloaded) {
|
||||
fallbackText = "The AI service is busy. Please wait a moment and try again.";
|
||||
} else if (isAuthError) {
|
||||
fallbackText =
|
||||
"I couldn't connect to the AI service. Please verify your API key is configured correctly.";
|
||||
} else if (isBillingError) {
|
||||
fallbackText =
|
||||
"I've reached my limit with the AI service. Please check your account balance and try again.";
|
||||
} else if (isTimeoutError) {
|
||||
fallbackText =
|
||||
"The request timed out. Please try again, or start a fresh session with /new.";
|
||||
} else {
|
||||
fallbackText =
|
||||
"Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment.";
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "final",
|
||||
|
||||
@ -1485,7 +1485,7 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
||||
const res = await run();
|
||||
|
||||
expect(res).toMatchObject({
|
||||
text: expect.stringContaining("Agent failed before reply"),
|
||||
text: "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment.",
|
||||
});
|
||||
expect(sessionStore.main).toBeDefined();
|
||||
await expect(fs.access(transcriptPath)).resolves.toBeUndefined();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user