

























@@ -32,6 +32,7 @@ vi.mock("../config/plugin-auto-enable.js", () => ({
3232let resolvePluginTools: typeof import("./tools.js").resolvePluginTools;
3333let ensureStandalonePluginToolRegistryLoaded: typeof import("./tools.js").ensureStandalonePluginToolRegistryLoaded;
3434let buildPluginToolMetadataKey: typeof import("./tools.js").buildPluginToolMetadataKey;
35+let getPluginToolMeta: typeof import("./tools.js").getPluginToolMeta;
3536let resetPluginToolFactoryCache: typeof import("./tools.js").resetPluginToolFactoryCache;
3637let getActivePluginRegistry: typeof import("./runtime.js").getActivePluginRegistry;
3738let pinActivePluginChannelRegistry: typeof import("./runtime.js").pinActivePluginChannelRegistry;
@@ -410,6 +411,7 @@ describe("resolvePluginTools optional tools", () => {
410411({
411412 buildPluginToolMetadataKey,
412413 ensureStandalonePluginToolRegistryLoaded,
414+ getPluginToolMeta,
413415 resetPluginToolFactoryCache,
414416 resolvePluginTools,
415417} = await import("./tools.js"));
@@ -1270,6 +1272,70 @@ describe("resolvePluginTools optional tools", () => {
12701272expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
12711273});
127212741275+it("marks allowlisted manifest-optional sibling tools from non-optional factories as optional", () => {
1276+const config = createContext().config;
1277+installToolManifestSnapshot({
1278+ config,
1279+plugin: {
1280+id: "multi",
1281+origin: "bundled",
1282+enabledByDefault: true,
1283+channels: [],
1284+providers: [],
1285+contracts: {
1286+tools: ["other_tool", "optional_tool"],
1287+},
1288+toolMetadata: {
1289+optional_tool: {
1290+optional: true,
1291+},
1292+},
1293+},
1294+});
1295+const factory = vi.fn(() => [makeTool("other_tool"), makeTool("optional_tool")]);
1296+setActivePluginRegistry(
1297+createToolRegistry([
1298+{
1299+pluginId: "multi",
1300+optional: false,
1301+source: "/tmp/multi.js",
1302+names: ["other_tool", "optional_tool"],
1303+declaredNames: ["other_tool", "optional_tool"],
1304+ factory,
1305+},
1306+]) as never,
1307+"test-tool-registry",
1308+"gateway-bindable",
1309+"/tmp",
1310+);
1311+1312+const first = resolvePluginTools(
1313+createResolveToolsParams({
1314+context: {
1315+ ...createContext(),
1316+ config,
1317+},
1318+toolAllowlist: [DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, "optional_tool"],
1319+}),
1320+);
1321+const second = resolvePluginTools(
1322+createResolveToolsParams({
1323+context: {
1324+ ...createContext(),
1325+ config,
1326+},
1327+toolAllowlist: [DEFAULT_PLUGIN_TOOLS_ALLOWLIST_ENTRY, "optional_tool"],
1328+}),
1329+);
1330+1331+expectResolvedToolNames(first, ["other_tool", "optional_tool"]);
1332+expectResolvedToolNames(second, ["other_tool", "optional_tool"]);
1333+expect(getPluginToolMeta(first[0])?.optional).toBe(false);
1334+expect(getPluginToolMeta(first[1])?.optional).toBe(true);
1335+expect(getPluginToolMeta(second[1])?.optional).toBe(true);
1336+expect(factory).toHaveBeenCalledTimes(1);
1337+});
1338+12731339it("rejects plugin id collisions with core tool names", () => {
12741340const registry = setRegistry([
12751341{
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。