From 35fbf26d24e898a6c707b9438c4c584fb42328cb Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Mon, 23 Feb 2026 00:05:57 -0600 Subject: [PATCH] Gateway: suppress tools.catalog plugin conflict diagnostics --- src/gateway/server-methods/tools-catalog.ts | 1 + src/plugins/tools.optional.test.ts | 20 +++++++++++++ src/plugins/tools.ts | 33 ++++++++++++--------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/gateway/server-methods/tools-catalog.ts b/src/gateway/server-methods/tools-catalog.ts index 42c539be002..27f488822a3 100644 --- a/src/gateway/server-methods/tools-catalog.ts +++ b/src/gateway/server-methods/tools-catalog.ts @@ -84,6 +84,7 @@ function buildPluginGroups(params: { }, existingToolNames: params.existingToolNames, toolAllowlist: ["group:plugins"], + suppressNameConflicts: true, }); const groups = new Map(); for (const tool of pluginTools) { diff --git a/src/plugins/tools.optional.test.ts b/src/plugins/tools.optional.test.ts index 85c2a101928..e73aa2f9dd5 100644 --- a/src/plugins/tools.optional.test.ts +++ b/src/plugins/tools.optional.test.ts @@ -154,4 +154,24 @@ describe("resolvePluginTools optional tools", () => { expect(registry.diagnostics).toHaveLength(1); expect(registry.diagnostics[0]?.message).toContain("plugin tool name conflict"); }); + + it("suppresses conflict diagnostics when requested", () => { + const registry = setRegistry([ + { + pluginId: "multi", + optional: false, + source: "/tmp/multi.js", + factory: () => [makeTool("message"), makeTool("other_tool")], + }, + ]); + + const tools = resolvePluginTools({ + context: createContext() as never, + existingToolNames: new Set(["message"]), + suppressNameConflicts: true, + }); + + expect(tools.map((tool) => tool.name)).toEqual(["other_tool"]); + expect(registry.diagnostics).toHaveLength(0); + }); }); diff --git a/src/plugins/tools.ts b/src/plugins/tools.ts index 155a7609eaa..055f092416f 100644 --- a/src/plugins/tools.ts +++ b/src/plugins/tools.ts @@ -46,6 +46,7 @@ export function resolvePluginTools(params: { context: OpenClawPluginToolContext; existingToolNames?: Set; toolAllowlist?: string[]; + suppressNameConflicts?: boolean; }): AnyAgentTool[] { // Fast path: when plugins are effectively disabled, avoid discovery/jiti entirely. // This matters a lot for unit tests and for tool construction hot paths. @@ -74,13 +75,15 @@ export function resolvePluginTools(params: { const pluginIdKey = normalizeToolName(entry.pluginId); if (existingNormalized.has(pluginIdKey)) { const message = `plugin id conflicts with core tool name (${entry.pluginId})`; - log.error(message); - registry.diagnostics.push({ - level: "error", - pluginId: entry.pluginId, - source: entry.source, - message, - }); + if (!params.suppressNameConflicts) { + log.error(message); + registry.diagnostics.push({ + level: "error", + pluginId: entry.pluginId, + source: entry.source, + message, + }); + } blockedPlugins.add(entry.pluginId); continue; } @@ -111,13 +114,15 @@ export function resolvePluginTools(params: { for (const tool of list) { if (nameSet.has(tool.name) || existing.has(tool.name)) { const message = `plugin tool name conflict (${entry.pluginId}): ${tool.name}`; - log.error(message); - registry.diagnostics.push({ - level: "error", - pluginId: entry.pluginId, - source: entry.source, - message, - }); + if (!params.suppressNameConflicts) { + log.error(message); + registry.diagnostics.push({ + level: "error", + pluginId: entry.pluginId, + source: entry.source, + message, + }); + } continue; } nameSet.add(tool.name);