From 6b3d3f5e21ca786fcd54bfa2bdd6b37ef4d0e642 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Jan 2026 04:18:32 +0000 Subject: [PATCH] refactor: centralize plugin tool policy helpers --- CHANGELOG.md | 1 + src/agents/pi-tools.ts | 83 +++++---------------------------------- src/agents/tool-policy.ts | 81 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b2d478bb..23a5608da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Docs: https://docs.clawd.bot - Memory: add SQLite embedding cache to speed up reindexing and frequent updates. - CLI: surface FTS + embedding cache state in `clawdbot memory status`. - Plugins: allow optional agent tools with explicit allowlists and add plugin tool authoring guide. https://docs.clawd.bot/plugins/agent-tools +- Tools: centralize plugin tool policy helpers. ## 2026.1.18-1 diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index 6b028ba56..33622e2d2 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -39,7 +39,12 @@ import { import { cleanToolSchemaForGemini, normalizeToolParameters } from "./pi-tools.schema.js"; import type { AnyAgentTool } from "./pi-tools.types.js"; import type { SandboxContext } from "./sandbox.js"; -import { normalizeToolName, resolveToolProfilePolicy } from "./tool-policy.js"; +import { + buildPluginToolGroups, + collectExplicitAllowlist, + expandPolicyWithPluginGroups, + resolveToolProfilePolicy, +} from "./tool-policy.js"; import { getPluginToolMeta } from "../plugins/tools.js"; function isOpenAIProvider(provider?: string) { @@ -69,77 +74,6 @@ function isApplyPatchAllowedForModel(params: { }); } -type ToolPolicyLike = { - allow?: string[]; - deny?: string[]; -}; - -function collectExplicitAllowlist(policies: Array): string[] { - const entries: string[] = []; - for (const policy of policies) { - if (!policy?.allow) continue; - for (const value of policy.allow) { - if (typeof value !== "string") continue; - const trimmed = value.trim(); - if (trimmed) entries.push(trimmed); - } - } - return entries; -} - -function buildPluginToolGroups(tools: AnyAgentTool[]) { - const all: string[] = []; - const byPlugin = new Map(); - for (const tool of tools) { - const meta = getPluginToolMeta(tool); - if (!meta) continue; - const name = normalizeToolName(tool.name); - all.push(name); - const pluginId = meta.pluginId.toLowerCase(); - const list = byPlugin.get(pluginId) ?? []; - list.push(name); - byPlugin.set(pluginId, list); - } - return { all, byPlugin }; -} - -function expandPluginGroups( - list: string[] | undefined, - groups: { all: string[]; byPlugin: Map }, -): string[] | undefined { - if (!list || list.length === 0) return list; - const expanded: string[] = []; - for (const entry of list) { - const normalized = normalizeToolName(entry); - if (normalized === "group:plugins") { - if (groups.all.length > 0) { - expanded.push(...groups.all); - } else { - expanded.push(normalized); - } - continue; - } - const tools = groups.byPlugin.get(normalized); - if (tools && tools.length > 0) { - expanded.push(...tools); - continue; - } - expanded.push(normalized); - } - return Array.from(new Set(expanded)); -} - -function expandPolicyWithPluginGroups( - policy: ToolPolicyLike | undefined, - groups: { all: string[]; byPlugin: Map }, -): ToolPolicyLike | undefined { - if (!policy) return undefined; - return { - allow: expandPluginGroups(policy.allow, groups), - deny: expandPluginGroups(policy.deny, groups), - }; -} - export const __testing = { cleanToolSchemaForGemini, normalizeToolParams, @@ -323,7 +257,10 @@ export function createClawdbotCodingTools(options?: { hasRepliedRef: options?.hasRepliedRef, }), ]; - const pluginGroups = buildPluginToolGroups(tools); + const pluginGroups = buildPluginToolGroups({ + tools, + toolMeta: (tool) => getPluginToolMeta(tool), + }); const profilePolicyExpanded = expandPolicyWithPluginGroups(profilePolicy, pluginGroups); const providerProfileExpanded = expandPolicyWithPluginGroups( providerProfilePolicy, diff --git a/src/agents/tool-policy.ts b/src/agents/tool-policy.ts index 8d3d2c812..72d27b182 100644 --- a/src/agents/tool-policy.ts +++ b/src/agents/tool-policy.ts @@ -85,6 +85,16 @@ export function normalizeToolList(list?: string[]) { return list.map(normalizeToolName).filter(Boolean); } +export type ToolPolicyLike = { + allow?: string[]; + deny?: string[]; +}; + +export type PluginToolGroups = { + all: string[]; + byPlugin: Map; +}; + export function expandToolGroups(list?: string[]) { const normalized = normalizeToolList(list); const expanded: string[] = []; @@ -99,6 +109,77 @@ export function expandToolGroups(list?: string[]) { return Array.from(new Set(expanded)); } +export function collectExplicitAllowlist( + policies: Array, +): string[] { + const entries: string[] = []; + for (const policy of policies) { + if (!policy?.allow) continue; + for (const value of policy.allow) { + if (typeof value !== "string") continue; + const trimmed = value.trim(); + if (trimmed) entries.push(trimmed); + } + } + return entries; +} + +export function buildPluginToolGroups(params: { + tools: Array<{ name: string }>; + toolMeta: (tool: { name: string }) => { pluginId: string } | undefined; +}): PluginToolGroups { + const all: string[] = []; + const byPlugin = new Map(); + for (const tool of params.tools) { + const meta = params.toolMeta(tool); + if (!meta) continue; + const name = normalizeToolName(tool.name); + all.push(name); + const pluginId = meta.pluginId.toLowerCase(); + const list = byPlugin.get(pluginId) ?? []; + list.push(name); + byPlugin.set(pluginId, list); + } + return { all, byPlugin }; +} + +export function expandPluginGroups( + list: string[] | undefined, + groups: PluginToolGroups, +): string[] | undefined { + if (!list || list.length === 0) return list; + const expanded: string[] = []; + for (const entry of list) { + const normalized = normalizeToolName(entry); + if (normalized === "group:plugins") { + if (groups.all.length > 0) { + expanded.push(...groups.all); + } else { + expanded.push(normalized); + } + continue; + } + const tools = groups.byPlugin.get(normalized); + if (tools && tools.length > 0) { + expanded.push(...tools); + continue; + } + expanded.push(normalized); + } + return Array.from(new Set(expanded)); +} + +export function expandPolicyWithPluginGroups( + policy: ToolPolicyLike | undefined, + groups: PluginToolGroups, +): ToolPolicyLike | undefined { + if (!policy) return undefined; + return { + allow: expandPluginGroups(policy.allow, groups), + deny: expandPluginGroups(policy.deny, groups), + }; +} + export function resolveToolProfilePolicy(profile?: string): ToolProfilePolicy | undefined { if (!profile) return undefined; const resolved = TOOL_PROFILES[profile as ToolProfileId];