diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b8d036b859..898f16c5a4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,7 @@ Docs: https://docs.openclaw.ai - Gateway/Sessions: abort active embedded runs and clear queued session work before `sessions.reset`, returning unavailable if the run does not stop in time. (#16576) Thanks @Grynn. - Sessions/Agents: harden transcript path resolution for mismatched agent context by preserving explicit store roots and adding safe absolute-path fallback to the correct agent sessions directory. (#16288) Thanks @robbyczgw-cla. - Agents: add a safety timeout around embedded `session.compact()` to ensure stalled compaction runs settle and release blocked session lanes. (#16331) Thanks @BinHPdev. +- Agents/Tools: make required-parameter validation errors list missing fields and instruct: "Supply correct parameters before retrying," reducing repeated invalid tool-call loops (for example `read({})`). (#14729) - Agents: keep unresolved mutating tool failures visible until the same action retry succeeds, scope mutation-error surfacing to mutating calls (including `session_status` model changes), and dedupe duplicate failure warnings in outbound replies. (#16131) Thanks @Swader. - Agents/Process/Bootstrap: preserve unbounded `process log` offset-only pagination (default tail applies only when both `offset` and `limit` are omitted) and enforce strict `bootstrapTotalMaxChars` budgeting across injected bootstrap content (including markers), skipping additional injection when remaining budget is too small. (#16539) Thanks @CharlieGreenman. - Agents/Workspace: persist bootstrap onboarding state so partially initialized workspaces recover missing `BOOTSTRAP.md` once, while completed onboarding keeps BOOTSTRAP deleted even if runtime files are later recreated. Thanks @gumadeiras. diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.e2e.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.e2e.test.ts index 6104fc16936..51ccca68c42 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.e2e.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.e2e.test.ts @@ -102,7 +102,10 @@ describe("createOpenClawCodingTools", () => { execute, }; - const wrapped = __testing.wrapToolParamNormalization(tool, [{ keys: ["path", "file_path"] }]); + const wrapped = __testing.wrapToolParamNormalization(tool, [ + { keys: ["path", "file_path"], label: "path (path or file_path)" }, + { keys: ["content"], label: "content" }, + ]); await wrapped.execute("tool-1", { file_path: "foo.txt", content: "x" }); expect(execute).toHaveBeenCalledWith( @@ -115,9 +118,21 @@ describe("createOpenClawCodingTools", () => { await expect(wrapped.execute("tool-2", { content: "x" })).rejects.toThrow( /Missing required parameter/, ); + await expect(wrapped.execute("tool-2", { content: "x" })).rejects.toThrow( + /Supply correct parameters before retrying\./, + ); await expect(wrapped.execute("tool-3", { file_path: " ", content: "x" })).rejects.toThrow( /Missing required parameter/, ); + await expect(wrapped.execute("tool-3", { file_path: " ", content: "x" })).rejects.toThrow( + /Supply correct parameters before retrying\./, + ); + await expect(wrapped.execute("tool-4", {})).rejects.toThrow( + /Missing required parameters: path \(path or file_path\), content/, + ); + await expect(wrapped.execute("tool-4", {})).rejects.toThrow( + /Supply correct parameters before retrying\./, + ); }); }); diff --git a/src/agents/pi-tools.read.ts b/src/agents/pi-tools.read.ts index 3798c6dd8b1..71e9bb72348 100644 --- a/src/agents/pi-tools.read.ts +++ b/src/agents/pi-tools.read.ts @@ -87,6 +87,12 @@ type RequiredParamGroup = { label?: string; }; +const RETRY_GUIDANCE_SUFFIX = " Supply correct parameters before retrying."; + +function parameterValidationError(message: string): Error { + return new Error(`${message}.${RETRY_GUIDANCE_SUFFIX}`); +} + export const CLAUDE_PARAM_GROUPS = { read: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }], write: [ @@ -245,9 +251,10 @@ export function assertRequiredParams( toolName: string, ): void { if (!record || typeof record !== "object") { - throw new Error(`Missing parameters for ${toolName}`); + throw parameterValidationError(`Missing parameters for ${toolName}`); } + const missingLabels: string[] = []; for (const group of groups) { const satisfied = group.keys.some((key) => { if (!(key in record)) { @@ -265,9 +272,15 @@ export function assertRequiredParams( if (!satisfied) { const label = group.label ?? group.keys.join(" or "); - throw new Error(`Missing required parameter: ${label}`); + missingLabels.push(label); } } + + if (missingLabels.length > 0) { + const joined = missingLabels.join(", "); + const noun = missingLabels.length === 1 ? "parameter" : "parameters"; + throw parameterValidationError(`Missing required ${noun}: ${joined}`); + } } // Generic wrapper to normalize parameters for any tool