fix: honor wildcard tool denylists in factory planning (#76773) (thanks @dorukardahan)

This commit is contained in:
Peter Steinberger
2026-05-03 17:05:06 +01:00
parent 0739cb19b7
commit fb9030ff67
3 changed files with 45 additions and 22 deletions

View File

@@ -10,7 +10,7 @@ Docs: https://docs.openclaw.ai
### Changes
- 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.
- 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.
- 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.
- 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.
- 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`.

View File

@@ -276,6 +276,45 @@ describe("optional media tool factory planning", () => {
});
});
it("applies wildcard deny patterns to optional factory planning", () => {
const config: OpenClawConfig = {};
installSnapshot(config, [
createPlugin({
id: "image-owner",
contracts: { imageGenerationProviders: ["image-owner"] },
setupProviders: [{ id: "image-owner", envVars: ["IMAGE_OWNER_API_KEY"] }],
}),
createPlugin({
id: "video-owner",
contracts: { videoGenerationProviders: ["video-owner"] },
setupProviders: [{ id: "video-owner", envVars: ["VIDEO_OWNER_API_KEY"] }],
}),
createPlugin({
id: "music-owner",
contracts: { musicGenerationProviders: ["music-owner"] },
setupProviders: [{ id: "music-owner", envVars: ["MUSIC_OWNER_API_KEY"] }],
}),
createPlugin({
id: "media-owner",
contracts: { mediaUnderstandingProviders: ["anthropic"] },
setupProviders: [{ id: "anthropic", envVars: ["ANTHROPIC_API_KEY"] }],
}),
]);
expect(
__testing.resolveOptionalMediaToolFactoryPlan({
config,
authStore: createAuthStore(["image-owner", "video-owner", "music-owner", "anthropic"]),
toolDenylist: ["*_generate", "p*"],
}),
).toEqual({
imageGenerate: false,
videoGenerate: false,
musicGenerate: false,
pdf: false,
});
});
it("keeps auth-backed providers on the factory path", () => {
const config: OpenClawConfig = {};
installSnapshot(config, [

View File

@@ -22,7 +22,7 @@ import {
import type { SandboxFsBridge } from "./sandbox/fs-bridge.js";
import type { SpawnedToolContext } from "./spawned-context.js";
import type { ToolFsPolicy } from "./tool-fs-policy.js";
import { expandToolGroups, normalizeToolName } from "./tool-policy.js";
import { isToolAllowedByPolicyName } from "./tool-policy-match.js";
import { createAgentsListTool } from "./tools/agents-list-tool.js";
import { createCanvasTool } from "./tools/canvas-tool.js";
import type { AnyAgentTool } from "./tools/common.js";
@@ -83,31 +83,15 @@ function hasExplicitImageModelConfig(config: OpenClawConfig | undefined): boolea
return hasToolModelConfig(coerceImageModelConfig(config));
}
function isToolAllowedByFactoryAllowlist(toolName: string, allowlist?: string[]): boolean {
if (!allowlist || allowlist.length === 0) {
return true;
}
const expanded = new Set(expandToolGroups(allowlist));
return expanded.has("*") || expanded.has(normalizeToolName(toolName));
}
function isToolDeniedByFactoryDenylist(toolName: string, denylist?: string[]): boolean {
if (!denylist || denylist.length === 0) {
return false;
}
const expanded = new Set(expandToolGroups(denylist));
return expanded.has("*") || expanded.has(normalizeToolName(toolName));
}
function isToolAllowedByFactoryPolicy(params: {
toolName: string;
allowlist?: string[];
denylist?: string[];
}): boolean {
if (isToolDeniedByFactoryDenylist(params.toolName, params.denylist)) {
return false;
}
return isToolAllowedByFactoryAllowlist(params.toolName, params.allowlist);
return isToolAllowedByPolicyName(params.toolName, {
allow: params.allowlist,
deny: params.denylist,
});
}
function resolveImageToolFactoryAvailable(params: {