fix(skill-workshop): reject invalid proposal list limits (#103496)

* fix(skill-workshop): reject invalid proposal list limits

* refactor(skill-workshop): validate list params before storage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
qingminlong
2026-07-11 09:26:12 +08:00
committed by GitHub
parent b58029f4fd
commit eccd4f02f8
2 changed files with 52 additions and 11 deletions

View File

@@ -83,6 +83,50 @@ describe("skill_workshop tool", () => {
expect(tools.some((tool) => tool.name === "skill_workshop")).toBe(false);
});
it.each([0, 1.5, "1.5", "25items", "many"])(
"rejects invalid list limit %s before touching proposal state",
async (limit) => {
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
const tool = createSkillWorkshopTool({
workspaceDir,
config: {},
agentId: "main",
});
await expect(tool.execute("call-list-limit", { action: "list", limit })).rejects.toThrow(
"limit must be a positive integer",
);
await expect(fs.access(path.join(stateDir, "skill-workshop"))).rejects.toThrow();
},
);
it("preserves list limits through 50 and clamps larger requests", async () => {
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
const tool = createSkillWorkshopTool({
workspaceDir,
config: { skills: { workshop: { maxPending: 200 } } },
agentId: "main",
});
for (let index = 0; index < 51; index += 1) {
await tool.execute(`call-create-${index}`, {
action: "create",
name: `Limit Proposal ${index}`,
description: `Proposal ${index}`,
proposal_content: `# Limit Proposal ${index}\n`,
});
}
for (const [limit, expectedCount] of [
[49, 49],
[50, 50],
[51, 50],
] as const) {
const result = await tool.execute(`call-list-${limit}`, { action: "list", limit });
expect((result.details as { proposals: unknown[] }).proposals).toHaveLength(expectedCount);
}
});
it("creates pending skill proposals without applying them", async () => {
// Creation writes reviewable proposal artifacts under state, not live skill
// files in the workspace.

View File

@@ -27,7 +27,7 @@ import type {
import { stringEnum } from "../schema/typebox.js";
import {
asToolParamsRecord,
readNumberParam,
readPositiveIntegerParam,
readStringParam,
ToolInputError,
type AnyAgentTool,
@@ -145,11 +145,14 @@ export function createSkillWorkshopTool(options: SkillWorkshopToolOptions): AnyA
const action = readStringParam(params, "action", { required: true });
if (action === "list") {
const status = readProposalStatusParam(params);
const query = readStringParam(params, "query");
const limit = readListLimitParam(params);
const proposals = listProposalEntries({
proposals: (await listSkillProposals({ workspaceDir: options.workspaceDir })).proposals,
status: readProposalStatusParam(params),
query: readStringParam(params, "query"),
limit: readListLimitParam(params),
status,
query,
limit,
});
return {
content: [{ type: "text", text: formatProposalList(proposals) }],
@@ -366,13 +369,7 @@ function readProposalStatusParam(params: Record<string, unknown>): SkillProposal
}
function readListLimitParam(params: Record<string, unknown>): number {
return (
readNumberParam(params, "limit", {
integer: true,
positiveInteger: true,
label: "limit",
}) ?? 20
);
return readPositiveIntegerParam(params, "limit") ?? 20;
}
function listProposalEntries(params: {