

























@@ -44,11 +44,54 @@ const RECOVERABLE_TOOL_ERROR_KEYWORDS = [
4444"requires",
4545] as const;
464647+const MUTATING_FAILURE_ACTION_PATTERN =
48+"(?:write|edit|update|save|create|delete|remove|modify|change|apply|patch|move|rename|send|reply|message|tool|action|operation)";
49+50+const MUTATING_FAILURE_INABILITY_PATTERN = new RegExp(
51+`\\b(?:couldn't|could not|can't|cannot|unable to|am unable to|wasn't able to|was not able to|were unable to)\\b.{0,100}\\b${MUTATING_FAILURE_ACTION_PATTERN}\\b`,
52+"u",
53+);
54+const MUTATING_FAILURE_ACTION_THEN_FAILURE_PATTERN = new RegExp(
55+`\\b${MUTATING_FAILURE_ACTION_PATTERN}\\b.{0,100}\\b(?:failed|failure|errored)\\b`,
56+"u",
57+);
58+const MUTATING_FAILURE_FAILURE_THEN_ACTION_PATTERN = new RegExp(
59+`\\b(?:failed|failure)\\b.{0,100}\\b${MUTATING_FAILURE_ACTION_PATTERN}\\b`,
60+"u",
61+);
62+const MUTATING_FAILURE_ERROR_WHILE_ACTION_PATTERN = new RegExp(
63+`\\b(?:hit|encountered|ran into)\\b.{0,60}\\berror\\b.{0,100}\\b(?:while|trying to|when)\\b.{0,100}\\b${MUTATING_FAILURE_ACTION_PATTERN}\\b`,
64+"u",
65+);
66+const DID_NOT_FAIL_PATTERN = /\b(?:did not|didn't)\s+fail\b/u;
67+const NEGATED_FAILURE_PATTERN = /\b(?:no|not|without)\s+(?:failures?|errors?)\b/u;
68+4769function isRecoverableToolError(error: string | undefined): boolean {
4870const errorLower = normalizeOptionalLowercaseString(error) ?? "";
4971return RECOVERABLE_TOOL_ERROR_KEYWORDS.some((keyword) => errorLower.includes(keyword));
5072}
517374+function hasExplicitMutatingToolFailureAcknowledgement(text: string): boolean {
75+const normalizedText = normalizeTextForComparison(text);
76+if (!normalizedText) {
77+return false;
78+}
79+if (DID_NOT_FAIL_PATTERN.test(normalizedText)) {
80+return false;
81+}
82+if (MUTATING_FAILURE_INABILITY_PATTERN.test(normalizedText)) {
83+return true;
84+}
85+if (NEGATED_FAILURE_PATTERN.test(normalizedText)) {
86+return false;
87+}
88+return (
89+MUTATING_FAILURE_ACTION_THEN_FAILURE_PATTERN.test(normalizedText) ||
90+MUTATING_FAILURE_FAILURE_THEN_ACTION_PATTERN.test(normalizedText) ||
91+MUTATING_FAILURE_ERROR_WHILE_ACTION_PATTERN.test(normalizedText)
92+);
93+}
94+5295function isVerboseToolDetailEnabled(level?: VerboseLevel): boolean {
5396return level === "on" || level === "full";
5497}
@@ -84,6 +127,7 @@ function shouldIncludeToolErrorDetails(params: {
84127function resolveToolErrorWarningPolicy(params: {
85128lastToolError: ToolErrorSummary;
86129hasUserFacingReply: boolean;
130+hasUserFacingFailureAcknowledgement: boolean;
87131suppressToolErrors: boolean;
88132suppressToolErrorWarnings?: boolean;
89133isCronTrigger?: boolean;
@@ -107,7 +151,10 @@ function resolveToolErrorWarningPolicy(params: {
107151const isMutatingToolError =
108152params.lastToolError.mutatingAction ?? isLikelyMutatingToolName(params.lastToolError.toolName);
109153if (isMutatingToolError) {
110-return { showWarning: true, includeDetails };
154+return {
155+showWarning: !params.hasUserFacingFailureAcknowledgement,
156+ includeDetails,
157+};
111158}
112159if (params.suppressToolErrors) {
113160return { showWarning: false, includeDetails };
@@ -316,6 +363,7 @@ export function buildEmbeddedRunPayloads(params: {
316363).filter((text) => !shouldSuppressRawErrorText(text));
317364318365let hasUserFacingAssistantReply = false;
366+let hasUserFacingFailureAcknowledgement = false;
319367for (const text of answerTexts) {
320368const {
321369text: cleanedText,
@@ -337,20 +385,24 @@ export function buildEmbeddedRunPayloads(params: {
337385 replyToCurrent,
338386});
339387hasUserFacingAssistantReply = true;
388+if (cleanedText && hasExplicitMutatingToolFailureAcknowledgement(cleanedText)) {
389+hasUserFacingFailureAcknowledgement = true;
390+}
340391}
341392342393if (params.lastToolError) {
343394const warningPolicy = resolveToolErrorWarningPolicy({
344395lastToolError: params.lastToolError,
345396hasUserFacingReply: hasUserFacingAssistantReply,
397+ hasUserFacingFailureAcknowledgement,
346398suppressToolErrors: Boolean(params.config?.messages?.suppressToolErrors),
347399suppressToolErrorWarnings: params.suppressToolErrorWarnings,
348400isCronTrigger: params.isCronTrigger,
349401sessionKey: params.sessionKey,
350402verboseLevel: params.verboseLevel,
351403});
352404353-// Always surface mutating tool failures so we do not silently confirm actions that did not happen.
405+// Surface mutating failures unless the assistant explicitly acknowledged the failed action.
354406// Otherwise, keep the previous behavior and only surface non-recoverable failures when no reply exists.
355407if (warningPolicy.showWarning) {
356408const toolSummary = formatToolAggregate(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。