mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-18 01:01:36 +00:00
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:
@@ -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.
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user