Skip to content

Commit 0e43d97

Browse files
committed
fix: honor wildcard tool denylists in factory planning (#76773) (thanks @dorukardahan)
1 parent e15ef5f commit 0e43d97

3 files changed

Lines changed: 45 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Docs: https://docs.openclaw.ai
1010

1111
### Changes
1212

13-
- Agents/tools: skip optional media and PDF tool factories when the effective tool denylist already blocks them, avoiding unnecessary hot-path setup for tools that will be filtered out before model use.
13+
- Agents/tools: skip optional media and PDF tool factories when the effective tool denylist already blocks them, avoiding unnecessary hot-path setup for tools that will be filtered out before model use. (#76773) Thanks @dorukardahan.
1414
- Gateway/performance: lazy-load early runtime discovery and shutdown-hook helpers, defer maintenance timers until after readiness, and trim duplicate plugin auto-enable work during Gateway startup.
1515
- QA/Mantis: add a `pnpm openclaw qa mantis discord-smoke` runner and manual GitHub workflow that verify the Mantis Discord bot can see the configured guild/channel, post a smoke message, add a reaction, and upload artifacts.
1616
- Gateway/performance: lazy-load the heavy cron runtime after the rest of Gateway startup, defer restart-sentinel refresh after readiness, and let the Gateway startup benchmark write per-run V8 CPU profiles with `--cpu-prof-dir`.

src/agents/openclaw-tools.media-factory-plan.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,45 @@ describe("optional media tool factory planning", () => {
276276
});
277277
});
278278

279+
it("applies wildcard deny patterns to optional factory planning", () => {
280+
const config: OpenClawConfig = {};
281+
installSnapshot(config, [
282+
createPlugin({
283+
id: "image-owner",
284+
contracts: { imageGenerationProviders: ["image-owner"] },
285+
setupProviders: [{ id: "image-owner", envVars: ["IMAGE_OWNER_API_KEY"] }],
286+
}),
287+
createPlugin({
288+
id: "video-owner",
289+
contracts: { videoGenerationProviders: ["video-owner"] },
290+
setupProviders: [{ id: "video-owner", envVars: ["VIDEO_OWNER_API_KEY"] }],
291+
}),
292+
createPlugin({
293+
id: "music-owner",
294+
contracts: { musicGenerationProviders: ["music-owner"] },
295+
setupProviders: [{ id: "music-owner", envVars: ["MUSIC_OWNER_API_KEY"] }],
296+
}),
297+
createPlugin({
298+
id: "media-owner",
299+
contracts: { mediaUnderstandingProviders: ["anthropic"] },
300+
setupProviders: [{ id: "anthropic", envVars: ["ANTHROPIC_API_KEY"] }],
301+
}),
302+
]);
303+
304+
expect(
305+
__testing.resolveOptionalMediaToolFactoryPlan({
306+
config,
307+
authStore: createAuthStore(["image-owner", "video-owner", "music-owner", "anthropic"]),
308+
toolDenylist: ["*_generate", "p*"],
309+
}),
310+
).toEqual({
311+
imageGenerate: false,
312+
videoGenerate: false,
313+
musicGenerate: false,
314+
pdf: false,
315+
});
316+
});
317+
279318
it("keeps auth-backed providers on the factory path", () => {
280319
const config: OpenClawConfig = {};
281320
installSnapshot(config, [

src/agents/openclaw-tools.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
import type { SandboxFsBridge } from "./sandbox/fs-bridge.js";
2323
import type { SpawnedToolContext } from "./spawned-context.js";
2424
import type { ToolFsPolicy } from "./tool-fs-policy.js";
25-
import { expandToolGroups, normalizeToolName } from "./tool-policy.js";
25+
import { isToolAllowedByPolicyName } from "./tool-policy-match.js";
2626
import { createAgentsListTool } from "./tools/agents-list-tool.js";
2727
import { createCanvasTool } from "./tools/canvas-tool.js";
2828
import type { AnyAgentTool } from "./tools/common.js";
@@ -83,31 +83,15 @@ function hasExplicitImageModelConfig(config: OpenClawConfig | undefined): boolea
8383
return hasToolModelConfig(coerceImageModelConfig(config));
8484
}
8585

86-
function isToolAllowedByFactoryAllowlist(toolName: string, allowlist?: string[]): boolean {
87-
if (!allowlist || allowlist.length === 0) {
88-
return true;
89-
}
90-
const expanded = new Set(expandToolGroups(allowlist));
91-
return expanded.has("*") || expanded.has(normalizeToolName(toolName));
92-
}
93-
94-
function isToolDeniedByFactoryDenylist(toolName: string, denylist?: string[]): boolean {
95-
if (!denylist || denylist.length === 0) {
96-
return false;
97-
}
98-
const expanded = new Set(expandToolGroups(denylist));
99-
return expanded.has("*") || expanded.has(normalizeToolName(toolName));
100-
}
101-
10286
function isToolAllowedByFactoryPolicy(params: {
10387
toolName: string;
10488
allowlist?: string[];
10589
denylist?: string[];
10690
}): boolean {
107-
if (isToolDeniedByFactoryDenylist(params.toolName, params.denylist)) {
108-
return false;
109-
}
110-
return isToolAllowedByFactoryAllowlist(params.toolName, params.allowlist);
91+
return isToolAllowedByPolicyName(params.toolName, {
92+
allow: params.allowlist,
93+
deny: params.denylist,
94+
});
11195
}
11296

11397
function resolveImageToolFactoryAvailable(params: {

0 commit comments

Comments
 (0)