diff --git a/src/agents/tools/skill-workshop-tool.test.ts b/src/agents/tools/skill-workshop-tool.test.ts index 25e7f1135d65..0532e62cf44d 100644 --- a/src/agents/tools/skill-workshop-tool.test.ts +++ b/src/agents/tools/skill-workshop-tool.test.ts @@ -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. diff --git a/src/agents/tools/skill-workshop-tool.ts b/src/agents/tools/skill-workshop-tool.ts index 7c0730f3bca0..a7f35b7d4ad2 100644 --- a/src/agents/tools/skill-workshop-tool.ts +++ b/src/agents/tools/skill-workshop-tool.ts @@ -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): SkillProposal } function readListLimitParam(params: Record): number { - return ( - readNumberParam(params, "limit", { - integer: true, - positiveInteger: true, - label: "limit", - }) ?? 20 - ); + return readPositiveIntegerParam(params, "limit") ?? 20; } function listProposalEntries(params: {