




















@@ -1,5 +1,4 @@
11import crypto from "node:crypto";
2-import fs from "node:fs";
32import {
43hasOutboundReplyContent,
54resolveSendableOutboundReplyParts,
@@ -48,7 +47,6 @@ import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
4847import { buildAgentRuntimeOutcomePlan } from "../../agents/runtime-plan/build.js";
4948import {
5049resolveGroupSessionKey,
51-resolveSessionTranscriptPath,
5250type SessionEntry,
5351updateSessionStore,
5452} from "../../config/sessions.js";
@@ -98,6 +96,10 @@ import {
9896} from "./agent-runner-utils.js";
9997import { type BlockReplyPipeline } from "./block-reply-pipeline.js";
10098import { resolveOriginMessageProvider } from "./origin-routing.js";
99+import {
100+classifyProviderRequestError,
101+PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE,
102+} from "./provider-request-error-classifier.js";
101103import type { FollowupRun } from "./queue.js";
102104import { createBlockReplyDeliveryHandler } from "./reply-delivery.js";
103105import type { ReplyMediaContext } from "./reply-media-paths.js";
@@ -456,16 +458,6 @@ function isPureBillingSummary(err: unknown): boolean {
456458);
457459}
458460459-function isToolResultTurnMismatchError(message: string): boolean {
460-const lower = normalizeLowercaseStringOrEmpty(message);
461-return (
462-lower.includes("toolresult") &&
463-lower.includes("tooluse") &&
464-lower.includes("exceeds the number") &&
465-lower.includes("previous turn")
466-);
467-}
468-469461function collapseRepeatedFailureDetail(message: string): string {
470462const parts = message
471463.split(/\s+\|\s+/u)
@@ -582,6 +574,13 @@ function buildExternalRunFailureReply(
582574options?: { includeDetails?: boolean; isHeartbeat?: boolean },
583575): ExternalRunFailureReply {
584576const normalizedMessage = collapseRepeatedFailureDetail(message);
577+const providerRequestError = classifyProviderRequestError(normalizedMessage);
578+if (providerRequestError) {
579+return {
580+text: providerRequestError.userMessage,
581+isGenericRunnerFailure: false,
582+};
583+}
585584const missingApiKeyFailure = buildMissingApiKeyFailureText(normalizedMessage);
586585if (missingApiKeyFailure) {
587586return { text: missingApiKeyFailure, isGenericRunnerFailure: false };
@@ -603,12 +602,6 @@ function buildExternalRunFailureReply(
603602if (options?.isHeartbeat) {
604603return { text: HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT, isGenericRunnerFailure: false };
605604}
606-if (isToolResultTurnMismatchError(normalizedMessage)) {
607-return {
608-text: "⚠️ Session history got out of sync. Please try again, or use /new to start a fresh session.",
609-isGenericRunnerFailure: false,
610-};
611-}
612605const cliBackendTimeoutFailure = buildCliBackendTimeoutFailureText(normalizedMessage);
613606if (cliBackendTimeoutFailure) {
614607return { text: cliBackendTimeoutFailure, isGenericRunnerFailure: false };
@@ -2254,16 +2247,18 @@ export async function runAgentTurnWithFallback(params: {
22542247};
22552248}
22562249if (embeddedError?.kind === "role_ordering") {
2257-const didReset = await params.resetSessionAfterRoleOrderingConflict(embeddedError.message);
2258-if (didReset) {
2259-params.replyOperation?.fail("run_failed", embeddedError);
2260-return {
2261-kind: "final",
2262-payload: markAgentRunFailureReplyPayload({
2263-text: "⚠️ Message ordering conflict. I've reset the conversation - please try again.",
2264-}),
2265-};
2266-}
2250+const providerRequestError = classifyProviderRequestError(embeddedError);
2251+params.replyOperation?.fail("run_failed", embeddedError);
2252+const embeddedErrorText = formatErrorMessage(embeddedError).replace(/\.\s*$/, "");
2253+return {
2254+kind: "final",
2255+payload: markAgentRunFailureReplyPayload({
2256+text: shouldSurfaceToControlUi
2257+ ? `⚠️ Agent failed before reply: ${embeddedErrorText}.\nLogs: openclaw logs --follow`
2258+ : (providerRequestError?.userMessage ??
2259+PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE),
2260+}),
2261+};
22672262}
2268226322692264break;
@@ -2318,8 +2313,8 @@ export async function runAgentTurnWithFallback(params: {
23182313 : isBillingErrorMessage(message);
23192314const isContextOverflow = !isBilling && isLikelyContextOverflowError(message);
23202315const isCompactionFailure = !isBilling && isCompactionFailureError(message);
2321-const isSessionCorruption = /function call turn comes immediately after/i.test(message);
2322-const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message);
2316+const providerRequestError =
2317+ !isBilling && !shouldSurfaceToControlUi ? classifyProviderRequestError(err) : undefined;
23232318const isTransientHttp = isTransientHttpError(message);
2324231923252320if (isReplyOperationRestartAbort(params.replyOperation)) {
@@ -2382,61 +2377,12 @@ export async function runAgentTurnWithFallback(params: {
23822377}),
23832378};
23842379}
2385-if (isRoleOrderingError) {
2386-const didReset = await params.resetSessionAfterRoleOrderingConflict(message);
2387-if (didReset) {
2388-params.replyOperation?.fail("run_failed", err);
2389-return {
2390-kind: "final",
2391-payload: markAgentRunFailureReplyPayload({
2392-text: "⚠️ Message ordering conflict. I've reset the conversation - please try again.",
2393-}),
2394-};
2395-}
2396-}
2397-2398-// Auto-recover from Gemini session corruption by resetting the session
2399-if (
2400-isSessionCorruption &&
2401-params.sessionKey &&
2402-params.activeSessionStore &&
2403-params.storePath
2404-) {
2405-const sessionKey = params.sessionKey;
2406-const corruptedSessionId = params.getActiveSessionEntry()?.sessionId;
2407-defaultRuntime.error(
2408-`Session history corrupted (Gemini function call ordering). Resetting session: ${params.sessionKey}`,
2409-);
2410-2411-try {
2412-// Delete transcript file if it exists
2413-if (corruptedSessionId) {
2414-const transcriptPath = resolveSessionTranscriptPath(corruptedSessionId);
2415-try {
2416-fs.unlinkSync(transcriptPath);
2417-} catch {
2418-// Ignore if file doesn't exist
2419-}
2420-}
2421-2422-// Keep the in-memory snapshot consistent with the on-disk store reset.
2423-delete params.activeSessionStore[sessionKey];
2424-2425-// Remove session entry from store using a fresh, locked snapshot.
2426-await updateSessionStore(params.storePath, (store) => {
2427-delete store[sessionKey];
2428-});
2429-} catch (cleanupErr) {
2430-defaultRuntime.error(
2431-`Failed to reset corrupted session ${params.sessionKey}: ${String(cleanupErr)}`,
2432-);
2433-}
2434-2435-params.replyOperation?.fail("session_corruption_reset", err);
2380+if (providerRequestError) {
2381+params.replyOperation?.fail("run_failed", err);
24362382return {
24372383kind: "final",
24382384payload: markAgentRunFailureReplyPayload({
2439-text: "⚠️ Session history was corrupted. I've reset the conversation - please try again!",
2385+text: providerRequestError.userMessage,
24402386}),
24412387};
24422388}
@@ -2481,7 +2427,6 @@ export async function runAgentTurnWithFallback(params: {
24812427!(isRateLimit && !isOverloadedErrorMessage(message)) &&
24822428!rateLimitOrOverloadedCopy &&
24832429!isContextOverflow &&
2484-!isRoleOrderingError &&
24852430!shouldSurfaceToControlUi
24862431 ? buildExternalRunFailureReply(message, {
24872432includeDetails: isVerboseFailureDetailEnabled(params.resolvedVerboseLevel),
@@ -2499,11 +2444,9 @@ export async function runAgentTurnWithFallback(params: {
24992444 ? rateLimitOrOverloadedCopy
25002445 : isContextOverflow
25012446 ? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."
2502- : isRoleOrderingError
2503- ? "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session."
2504- : shouldSurfaceToControlUi
2505- ? `⚠️ Agent failed before reply: ${trimmedMessage}.\nLogs: openclaw logs --follow`
2506- : (externalRunFailureReply?.text ?? genericFallbackText);
2447+ : shouldSurfaceToControlUi
2448+ ? `⚠️ Agent failed before reply: ${trimmedMessage}.\nLogs: openclaw logs --follow`
2449+ : (externalRunFailureReply?.text ?? genericFallbackText);
25072450const userVisibleFallbackText = resolveExternalRunFailureTextForConversation({
25082451text: fallbackText,
25092452sessionCtx: params.sessionCtx,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。