Files
openclaw/src/agents/tool-error-summary.ts
2026-08-08 16:10:30 -06:00

87 lines
2.6 KiB
TypeScript

/**
* Compact tool error summary types.
*
* Stores failure metadata used by transcripts, retry behavior, and mutation recovery logic.
*/
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
import type { FileTarget } from "./tool-mutation.js";
export type ProcessTerminalDiagnostic = {
kind: "process";
sessionId: string;
reason:
| { kind: "exit"; exitCode: number }
| { kind: "signal"; signal: string | number }
| {
kind: "timeout";
timeoutKind?: "overall-timeout" | "no-output-timeout";
};
};
export type ToolErrorSummary = {
toolName: string;
meta?: string;
errorCode?: string;
error?: string;
validationErrorSummary?: string;
timedOut?: boolean;
middlewareError?: boolean;
mutatingAction?: boolean;
actionFingerprint?: string;
fileTarget?: FileTarget;
terminalDiagnostic?: ProcessTerminalDiagnostic;
};
const EXEC_LIKE_TOOL_NAMES = new Set(["exec", "bash"]);
/** Detects shell-execution tools that share retry and mutation semantics. */
export function isExecLikeToolName(toolName: string): boolean {
return EXEC_LIKE_TOOL_NAMES.has(normalizeOptionalLowercaseString(toolName) ?? "");
}
const MAX_ABORT_SUMMARY_LENGTH = 160;
function hasUnsafeSummaryCharacter(value: string): boolean {
for (const char of value) {
const code = char.charCodeAt(0);
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) {
return true;
}
}
return false;
}
/** Accepts only the compact single-line diagnostic produced below. */
export function readToolValidationErrorSummary(value: unknown): string | undefined {
if (typeof value !== "string") {
return undefined;
}
const summary = value.trim();
if (!summary || summary.length > MAX_ABORT_SUMMARY_LENGTH || hasUnsafeSummaryCharacter(summary)) {
return undefined;
}
return summary;
}
/** Builds a static diagnostic from typed pre-execution validation provenance. */
export function createToolValidationErrorSummary(toolName: string): string | undefined {
if (hasUnsafeSummaryCharacter(toolName)) {
return undefined;
}
const normalizedToolName = toolName.replace(/\s+/g, " ").trim();
if (!normalizedToolName) {
return undefined;
}
return readToolValidationErrorSummary(
`${normalizedToolName} tool validation failed: invalid arguments`,
);
}
/**
* Returns only a boundary-prepared validation summary. Raw validator messages
* stay private because paths and custom messages can contain model input.
*/
export function summarizeToolValidationError(summary: ToolErrorSummary): string | undefined {
return readToolValidationErrorSummary(summary.validationErrorSummary);
}