mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:20:44 +00:00
fix(tools): skip denied optional media factories
This commit is contained in:
committed by
Peter Steinberger
parent
7f6798094c
commit
0739cb19b7
@@ -10,6 +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.
|
||||
- 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`.
|
||||
|
||||
@@ -247,6 +247,35 @@ describe("optional media tool factory planning", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("skips tools that the resolved denylist blocks", () => {
|
||||
const config: OpenClawConfig = {};
|
||||
installSnapshot(config, [
|
||||
createPlugin({
|
||||
id: "image-owner",
|
||||
contracts: { imageGenerationProviders: ["image-owner"] },
|
||||
setupProviders: [{ id: "image-owner", envVars: ["IMAGE_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", "anthropic"]),
|
||||
toolDenylist: ["image_generate", "pdf"],
|
||||
}),
|
||||
).toEqual({
|
||||
imageGenerate: false,
|
||||
videoGenerate: false,
|
||||
musicGenerate: false,
|
||||
pdf: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps auth-backed providers on the factory path", () => {
|
||||
const config: OpenClawConfig = {};
|
||||
installSnapshot(config, [
|
||||
|
||||
@@ -91,6 +91,25 @@ function isToolAllowedByFactoryAllowlist(toolName: string, allowlist?: string[])
|
||||
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);
|
||||
}
|
||||
|
||||
function resolveImageToolFactoryAvailable(params: {
|
||||
config?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
@@ -159,21 +178,29 @@ function resolveOptionalMediaToolFactoryPlan(params: {
|
||||
workspaceDir?: string;
|
||||
authStore?: AuthProfileStore;
|
||||
toolAllowlist?: string[];
|
||||
toolDenylist?: string[];
|
||||
}): OptionalMediaToolFactoryPlan {
|
||||
const defaults = params.config?.agents?.defaults;
|
||||
const allowImageGenerate = isToolAllowedByFactoryAllowlist(
|
||||
"image_generate",
|
||||
params.toolAllowlist,
|
||||
);
|
||||
const allowVideoGenerate = isToolAllowedByFactoryAllowlist(
|
||||
"video_generate",
|
||||
params.toolAllowlist,
|
||||
);
|
||||
const allowMusicGenerate = isToolAllowedByFactoryAllowlist(
|
||||
"music_generate",
|
||||
params.toolAllowlist,
|
||||
);
|
||||
const allowPdf = isToolAllowedByFactoryAllowlist("pdf", params.toolAllowlist);
|
||||
const allowImageGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "image_generate",
|
||||
allowlist: params.toolAllowlist,
|
||||
denylist: params.toolDenylist,
|
||||
});
|
||||
const allowVideoGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "video_generate",
|
||||
allowlist: params.toolAllowlist,
|
||||
denylist: params.toolDenylist,
|
||||
});
|
||||
const allowMusicGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "music_generate",
|
||||
allowlist: params.toolAllowlist,
|
||||
denylist: params.toolDenylist,
|
||||
});
|
||||
const allowPdf = isToolAllowedByFactoryPolicy({
|
||||
toolName: "pdf",
|
||||
allowlist: params.toolAllowlist,
|
||||
denylist: params.toolDenylist,
|
||||
});
|
||||
const explicitImageGeneration = hasExplicitToolModelConfig(defaults?.imageGenerationModel);
|
||||
const explicitVideoGeneration = hasExplicitToolModelConfig(defaults?.videoGenerationModel);
|
||||
const explicitMusicGeneration = hasExplicitToolModelConfig(defaults?.musicGenerationModel);
|
||||
@@ -256,6 +283,7 @@ export function createOpenClawTools(
|
||||
sandboxed?: boolean;
|
||||
config?: OpenClawConfig;
|
||||
pluginToolAllowlist?: string[];
|
||||
pluginToolDenylist?: string[];
|
||||
/** Current channel ID for auto-threading. */
|
||||
currentChannelId?: string;
|
||||
/** Current thread timestamp for auto-threading. */
|
||||
@@ -348,6 +376,7 @@ export function createOpenClawTools(
|
||||
workspaceDir,
|
||||
authStore: options?.authProfileStore,
|
||||
toolAllowlist: options?.pluginToolAllowlist,
|
||||
toolDenylist: options?.pluginToolDenylist,
|
||||
});
|
||||
const imageToolAgentDir = options?.agentDir;
|
||||
const imageTool = resolveImageToolFactoryAvailable({
|
||||
|
||||
@@ -180,6 +180,21 @@ describe("createOpenClawCodingTools", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("passes explicit denylist entries to OpenClaw tool factory planning", () => {
|
||||
const createOpenClawToolsMock = vi.mocked(createOpenClawTools);
|
||||
createOpenClawToolsMock.mockClear();
|
||||
|
||||
createOpenClawCodingTools({
|
||||
config: { tools: { deny: ["pdf"] } },
|
||||
});
|
||||
|
||||
expect(createOpenClawToolsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pluginToolDenylist: expect.arrayContaining(["pdf"]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("records core tool-prep stages for hot-path diagnostics", () => {
|
||||
const stages: string[] = [];
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
import {
|
||||
applyOwnerOnlyToolPolicy,
|
||||
collectExplicitAllowlist,
|
||||
collectExplicitDenylist,
|
||||
mergeAlsoAllowPolicy,
|
||||
normalizeToolName,
|
||||
resolveToolProfilePolicy,
|
||||
@@ -617,6 +618,17 @@ export function createOpenClawCodingTools(options?: {
|
||||
subagentPolicy,
|
||||
options?.runtimeToolAllowlist ? { allow: options.runtimeToolAllowlist } : undefined,
|
||||
]);
|
||||
const pluginToolDenylist = collectExplicitDenylist([
|
||||
profilePolicy,
|
||||
providerProfilePolicy,
|
||||
globalPolicy,
|
||||
globalProviderPolicy,
|
||||
agentPolicy,
|
||||
agentProviderPolicy,
|
||||
groupPolicy,
|
||||
sandboxToolPolicy,
|
||||
subagentPolicy,
|
||||
]);
|
||||
const pluginToolsOnly = includeCoreTools
|
||||
? []
|
||||
: resolveOpenClawPluginToolsForOptions({
|
||||
@@ -705,6 +717,7 @@ export function createOpenClawCodingTools(options?: {
|
||||
sandboxed: !!sandbox,
|
||||
config: options?.config,
|
||||
pluginToolAllowlist,
|
||||
pluginToolDenylist,
|
||||
currentChannelId: options?.currentChannelId,
|
||||
currentThreadTs: options?.currentThreadTs,
|
||||
currentMessageId: options?.currentMessageId,
|
||||
|
||||
@@ -112,6 +112,25 @@ export function collectExplicitAllowlist(policies: Array<ToolPolicyLike | undefi
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function collectExplicitDenylist(policies: Array<ToolPolicyLike | undefined>): string[] {
|
||||
const entries: string[] = [];
|
||||
for (const policy of policies) {
|
||||
if (!policy?.deny) {
|
||||
continue;
|
||||
}
|
||||
for (const value of policy.deny) {
|
||||
if (typeof value !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (trimmed) {
|
||||
entries.push(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function buildPluginToolGroups<T extends { name: string }>(params: {
|
||||
tools: T[];
|
||||
toolMeta: (tool: T) => { pluginId: string } | undefined;
|
||||
|
||||
Reference in New Issue
Block a user