mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 19:54:32 +00:00
fix(agents): mark required-param tool errors as non-retryable (#17533)
* Agents: mark missing tool params as non-retryable * Agents: include all missing required params in tool errors * Agents: change required-param errors to retry guidance * Docs: align changelog text for issue #14729 guidance wording
This commit is contained in:
@@ -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.
|
- 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.
|
- 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: 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: 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/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.
|
- 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.
|
||||||
|
|||||||
@@ -102,7 +102,10 @@ describe("createOpenClawCodingTools", () => {
|
|||||||
execute,
|
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" });
|
await wrapped.execute("tool-1", { file_path: "foo.txt", content: "x" });
|
||||||
expect(execute).toHaveBeenCalledWith(
|
expect(execute).toHaveBeenCalledWith(
|
||||||
@@ -115,9 +118,21 @@ describe("createOpenClawCodingTools", () => {
|
|||||||
await expect(wrapped.execute("tool-2", { content: "x" })).rejects.toThrow(
|
await expect(wrapped.execute("tool-2", { content: "x" })).rejects.toThrow(
|
||||||
/Missing required parameter/,
|
/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(
|
await expect(wrapped.execute("tool-3", { file_path: " ", content: "x" })).rejects.toThrow(
|
||||||
/Missing required parameter/,
|
/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\./,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,12 @@ type RequiredParamGroup = {
|
|||||||
label?: string;
|
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 = {
|
export const CLAUDE_PARAM_GROUPS = {
|
||||||
read: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }],
|
read: [{ keys: ["path", "file_path"], label: "path (path or file_path)" }],
|
||||||
write: [
|
write: [
|
||||||
@@ -245,9 +251,10 @@ export function assertRequiredParams(
|
|||||||
toolName: string,
|
toolName: string,
|
||||||
): void {
|
): void {
|
||||||
if (!record || typeof record !== "object") {
|
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) {
|
for (const group of groups) {
|
||||||
const satisfied = group.keys.some((key) => {
|
const satisfied = group.keys.some((key) => {
|
||||||
if (!(key in record)) {
|
if (!(key in record)) {
|
||||||
@@ -265,9 +272,15 @@ export function assertRequiredParams(
|
|||||||
|
|
||||||
if (!satisfied) {
|
if (!satisfied) {
|
||||||
const label = group.label ?? group.keys.join(" or ");
|
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
|
// Generic wrapper to normalize parameters for any tool
|
||||||
|
|||||||
Reference in New Issue
Block a user