惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
罗磊的独立博客
The GitHub Blog
The GitHub Blog
V
V2EX
Last Week in AI
Last Week in AI
博客园 - 聂微东
MyScale Blog
MyScale Blog
美团技术团队
L
LangChain Blog
博客园 - Franky
腾讯CDC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园_首页
S
SegmentFault 最新的问题
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Stack Overflow Blog
Stack Overflow Blog
量子位
小众软件
小众软件
宝玉的分享
宝玉的分享
J
Java Code Geeks
Google DeepMind News
Google DeepMind News
D
Docker
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix: validate firecrawl numeric options · openclaw/opencl...
steipete · 2026-05-29 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -15,7 +15,7 @@ const FirecrawlScrapeToolSchema = Type.Object(

1515

description: 'Extraction mode ("markdown" or "text"). Default: markdown.',

1616

}),

1717

maxChars: Type.Optional(

18-

Type.Number({

18+

Type.Integer({

1919

description: "Maximum characters to return.",

2020

minimum: 100,

2121

}),

@@ -26,7 +26,7 @@ const FirecrawlScrapeToolSchema = Type.Object(

2626

}),

2727

),

2828

maxAgeMs: Type.Optional(

29-

Type.Number({

29+

Type.Integer({

3030

description: "Maximum Firecrawl cache age in milliseconds.",

3131

minimum: 0,

3232

}),

@@ -40,7 +40,7 @@ const FirecrawlScrapeToolSchema = Type.Object(

4040

}),

4141

),

4242

timeoutSeconds: Type.Optional(

43-

Type.Number({

43+

Type.Integer({

4444

description: "Timeout in seconds for the Firecrawl scrape request.",

4545

minimum: 1,

4646

}),

@@ -60,10 +60,10 @@ export function createFirecrawlScrapeTool(api: OpenClawPluginApi) {

6060

const url = readStringParam(rawParams, "url", { required: true });

6161

const extractMode =

6262

readStringParam(rawParams, "extractMode") === "text" ? "text" : "markdown";

63-

const maxChars = readNumberParam(rawParams, "maxChars", { integer: true });

64-

const maxAgeMs = readNumberParam(rawParams, "maxAgeMs", { integer: true });

63+

const maxChars = readNumberParam(rawParams, "maxChars", { positiveInteger: true });

64+

const maxAgeMs = readNumberParam(rawParams, "maxAgeMs", { nonNegativeInteger: true });

6565

const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {

66-

integer: true,

66+

positiveInteger: true,

6767

});

6868

const proxyRaw = readStringParam(rawParams, "proxy");

6969

const proxy =

Original file line numberDiff line numberDiff line change

@@ -12,7 +12,7 @@ const FirecrawlSearchToolSchema = Type.Object(

1212

{

1313

query: Type.String({ description: "Search query string." }),

1414

count: Type.Optional(

15-

Type.Number({

15+

Type.Integer({

1616

description: "Number of results to return (1-10).",

1717

minimum: 1,

1818

maximum: 10,

@@ -34,7 +34,7 @@ const FirecrawlSearchToolSchema = Type.Object(

3434

}),

3535

),

3636

timeoutSeconds: Type.Optional(

37-

Type.Number({

37+

Type.Integer({

3838

description: "Timeout in seconds for the Firecrawl Search request.",

3939

minimum: 1,

4040

}),

@@ -52,9 +52,9 @@ export function createFirecrawlSearchTool(api: OpenClawPluginApi) {

5252

parameters: FirecrawlSearchToolSchema,

5353

execute: async (_toolCallId: string, rawParams: Record<string, unknown>) => {

5454

const query = readStringParam(rawParams, "query", { required: true });

55-

const count = readNumberParam(rawParams, "count", { integer: true });

55+

const count = readNumberParam(rawParams, "count", { positiveInteger: true });

5656

const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {

57-

integer: true,

57+

positiveInteger: true,

5858

});

5959

const sources = readStringArrayParam(rawParams, "sources");

6060

const categories = readStringArrayParam(rawParams, "categories");

Original file line numberDiff line numberDiff line change

@@ -489,6 +489,49 @@ describe("firecrawl tools", () => {

489489

});

490490

});

491491
492+

it("drops malformed numeric Firecrawl tool options", async () => {

493+

const searchTool = createFirecrawlSearchTool({

494+

config: { env: "test" },

495+

} as never);

496+

await searchTool.execute("call-search", {

497+

query: "web search",

498+

count: 6.5,

499+

timeoutSeconds: Number.POSITIVE_INFINITY,

500+

});

501+
502+

expect(runFirecrawlSearch).toHaveBeenLastCalledWith({

503+

cfg: { env: "test" },

504+

query: "web search",

505+

count: undefined,

506+

timeoutSeconds: undefined,

507+

sources: undefined,

508+

categories: undefined,

509+

scrapeResults: false,

510+

});

511+
512+

const scrapeTool = createFirecrawlScrapeTool({

513+

config: { env: "test" },

514+

} as never);

515+

await scrapeTool.execute("call-scrape", {

516+

url: "https://docs.openclaw.ai",

517+

maxChars: 1500.5,

518+

maxAgeMs: -1,

519+

timeoutSeconds: 22.5,

520+

});

521+
522+

expect(runFirecrawlScrape).toHaveBeenLastCalledWith({

523+

cfg: { env: "test" },

524+

url: "https://docs.openclaw.ai",

525+

extractMode: "markdown",

526+

maxChars: undefined,

527+

onlyMainContent: undefined,

528+

maxAgeMs: undefined,

529+

proxy: undefined,

530+

storeInCache: undefined,

531+

timeoutSeconds: undefined,

532+

});

533+

});

534+
492535

it("passes text mode through and ignores invalid proxy values", async () => {

493536

const tool = createFirecrawlScrapeTool({

494537

config: { env: "test" },

Original file line numberDiff line numberDiff line change

@@ -74,6 +74,22 @@ describe("readNumberParam", () => {

7474

}),

7575

).toBeUndefined();

7676

});

77+
78+

it("accepts only nonnegative safe integers when nonNegativeInteger is true", () => {

79+

expect(readNumberParam({ cacheAge: 0 }, "cacheAge", { nonNegativeInteger: true })).toBe(0);

80+

expect(readNumberParam({ cacheAge: "42" }, "cacheAge", { nonNegativeInteger: true })).toBe(42);

81+

expect(

82+

readNumberParam({ cacheAge: "42.9" }, "cacheAge", { nonNegativeInteger: true }),

83+

).toBeUndefined();

84+

expect(

85+

readNumberParam({ cacheAge: -1 }, "cacheAge", { nonNegativeInteger: true }),

86+

).toBeUndefined();

87+

expect(

88+

readNumberParam({ cacheAge: Number.POSITIVE_INFINITY }, "cacheAge", {

89+

nonNegativeInteger: true,

90+

}),

91+

).toBeUndefined();

92+

});

7793

});

7894
7995

describe("snake_case aliases", () => {

Original file line numberDiff line numberDiff line change

@@ -163,6 +163,7 @@ export function readNumberParam(

163163

integer?: boolean;

164164

strict?: boolean;

165165

positiveInteger?: boolean;

166+

nonNegativeInteger?: boolean;

166167

} = {},

167168

): number | undefined {

168169

const {

@@ -171,6 +172,7 @@ export function readNumberParam(

171172

integer = false,

172173

strict = false,

173174

positiveInteger = false,

175+

nonNegativeInteger = false,

174176

} = options;

175177

const raw = readParamRaw(params, key);

176178

let value: number | undefined;

@@ -194,6 +196,9 @@ export function readNumberParam(

194196

if (positiveInteger) {

195197

return Number.isSafeInteger(value) && value > 0 ? value : undefined;

196198

}

199+

if (nonNegativeInteger) {

200+

return Number.isSafeInteger(value) && value >= 0 ? value : undefined;

201+

}

197202

return integer ? Math.trunc(value) : value;

198203

}

199204