




















@@ -32,6 +32,7 @@ let resolvePluginTools: typeof import("./tools.js").resolvePluginTools;
3232let ensureStandalonePluginToolRegistryLoaded: typeof import("./tools.js").ensureStandalonePluginToolRegistryLoaded;
3333let buildPluginToolMetadataKey: typeof import("./tools.js").buildPluginToolMetadataKey;
3434let resetPluginToolFactoryCache: typeof import("./tools.js").resetPluginToolFactoryCache;
35+let getActivePluginRegistry: typeof import("./runtime.js").getActivePluginRegistry;
3536let pinActivePluginChannelRegistry: typeof import("./runtime.js").pinActivePluginChannelRegistry;
3637let resetPluginRuntimeStateForTest: typeof import("./runtime.js").resetPluginRuntimeStateForTest;
3738let setActivePluginRegistry: typeof import("./runtime.js").setActivePluginRegistry;
@@ -394,8 +395,12 @@ describe("resolvePluginTools optional tools", () => {
394395 resetPluginToolFactoryCache,
395396 resolvePluginTools,
396397} = await import("./tools.js"));
397-({ pinActivePluginChannelRegistry, resetPluginRuntimeStateForTest, setActivePluginRegistry } =
398-await import("./runtime.js"));
398+({
399+ getActivePluginRegistry,
400+ pinActivePluginChannelRegistry,
401+ resetPluginRuntimeStateForTest,
402+ setActivePluginRegistry,
403+} = await import("./runtime.js"));
399404({ clearCurrentPluginMetadataSnapshot, setCurrentPluginMetadataSnapshot } =
400405await import("./current-plugin-metadata-snapshot.js"));
401406});
@@ -553,6 +558,73 @@ describe("resolvePluginTools optional tools", () => {
553558);
554559});
555560561+it("does not reuse a partial active registry for wildcard-selected plugin tools", () => {
562+const context = createContext();
563+const config = context.config;
564+const optionalEntry = createOptionalDemoEntry();
565+const multiEntry: MockRegistryToolEntry = {
566+pluginId: "multi",
567+optional: false,
568+source: "/tmp/multi.js",
569+names: ["other_tool"],
570+declaredNames: ["other_tool"],
571+factory: () => makeTool("other_tool"),
572+};
573+installToolManifestSnapshots({
574+ config,
575+plugins: [
576+{
577+id: "multi",
578+origin: "bundled",
579+enabledByDefault: true,
580+channels: [],
581+providers: [],
582+contracts: {
583+tools: ["other_tool"],
584+},
585+},
586+{
587+id: "optional-demo",
588+origin: "bundled",
589+enabledByDefault: true,
590+channels: [],
591+providers: [],
592+contracts: {
593+tools: ["optional_tool"],
594+},
595+},
596+],
597+});
598+const partialRegistry = createToolRegistry([multiEntry]);
599+partialRegistry.plugins.push({ id: "optional-demo", status: "loaded" });
600+const fullRegistry = createToolRegistry([multiEntry, optionalEntry]);
601+setActivePluginRegistry?.(
602+partialRegistry as never,
603+"partial-test-tool-registry",
604+"gateway-bindable",
605+"/tmp",
606+);
607+resolveRuntimePluginRegistryMock.mockReturnValue(partialRegistry);
608+loadOpenClawPluginsMock.mockReturnValue(fullRegistry);
609+610+const tools = resolvePluginTools(
611+createResolveToolsParams({
612+ context,
613+toolAllowlist: ["*", "optional-demo"],
614+}),
615+);
616+617+expectResolvedToolNames(tools, ["other_tool", "optional_tool"]);
618+expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
619+expect.objectContaining({
620+activate: false,
621+cache: false,
622+onlyPluginIds: ["multi", "optional-demo"],
623+toolDiscovery: true,
624+}),
625+);
626+});
627+556628it("warns when cold registry load still does not provide the selected plugin tools", () => {
557629const context = {
558630 ...createContext(),
@@ -597,6 +669,72 @@ describe("resolvePluginTools optional tools", () => {
597669);
598670});
599671672+it("uses the fresh cold-loaded registry for diagnostics when partial active registries remain incomplete", () => {
673+const context = createContext();
674+const config = context.config;
675+const multiEntry: MockRegistryToolEntry = {
676+pluginId: "multi",
677+optional: false,
678+source: "/tmp/multi.js",
679+names: ["other_tool"],
680+declaredNames: ["other_tool"],
681+factory: () => makeTool("other_tool"),
682+};
683+const optionalEntry = createOptionalDemoEntry();
684+installToolManifestSnapshots({
685+ config,
686+plugins: [
687+{
688+id: "multi",
689+origin: "bundled",
690+enabledByDefault: true,
691+channels: [],
692+providers: [],
693+contracts: {
694+tools: ["other_tool"],
695+},
696+},
697+{
698+id: "optional-demo",
699+origin: "bundled",
700+enabledByDefault: true,
701+channels: [],
702+providers: [],
703+contracts: {
704+tools: ["optional_tool"],
705+},
706+},
707+],
708+});
709+const staleRegistry = createToolRegistry([multiEntry]);
710+staleRegistry.plugins.push({ id: "optional-demo", status: "loaded" });
711+const freshRegistry = createToolRegistry([optionalEntry]);
712+freshRegistry.plugins.push({ id: "multi", status: "loaded" });
713+setActivePluginRegistry?.(
714+staleRegistry as never,
715+"partial-test-tool-registry",
716+"gateway-bindable",
717+"/tmp",
718+);
719+resolveRuntimePluginRegistryMock.mockReturnValue(staleRegistry);
720+loadOpenClawPluginsMock.mockReturnValue(freshRegistry);
721+722+const tools = resolvePluginTools(
723+createResolveToolsParams({
724+ context,
725+toolAllowlist: ["*", "optional-demo"],
726+}),
727+);
728+729+expectResolvedToolNames(tools, ["optional_tool"]);
730+expect(getActivePluginRegistry?.()).toBe(staleRegistry);
731+expectSingleDiagnosticMessage(
732+freshRegistry.diagnostics,
733+"plugin tool registry did not include selected plugin tools after cold load (multi)",
734+);
735+expect(staleRegistry.diagnostics).toEqual([]);
736+});
737+600738it("does not reuse a pinned gateway registry for manifest-unavailable tools", () => {
601739const config = createContext().config;
602740installToolManifestSnapshot({
@@ -1504,26 +1642,54 @@ describe("resolvePluginTools optional tools", () => {
1504164215051643it("adds enabled non-startup tool plugins to the active tool runtime scope", () => {
15061644const activeRegistry = createOptionalDemoActiveRegistry();
1645+const context = createContext();
1646+const config = {
1647+ ...context.config,
1648+plugins: {
1649+ ...context.config.plugins,
1650+allow: ["tavily"],
1651+entries: {
1652+tavily: { enabled: true },
1653+},
1654+},
1655+};
1656+installToolManifestSnapshots({
1657+ config,
1658+plugins: [
1659+{
1660+id: "optional-demo",
1661+origin: "bundled",
1662+enabledByDefault: true,
1663+channels: [],
1664+providers: [],
1665+contracts: {
1666+tools: ["optional_tool"],
1667+},
1668+},
1669+{
1670+id: "tavily",
1671+origin: "bundled",
1672+enabledByDefault: false,
1673+channels: [],
1674+providers: [],
1675+contracts: {
1676+tools: ["tavily_search"],
1677+},
1678+},
1679+],
1680+});
15071681setActivePluginRegistry(activeRegistry as never, "gateway-startup", "gateway-bindable", "/tmp");
15081682resolveRuntimePluginRegistryMock.mockReturnValue(activeRegistry);
1683+loadOpenClawPluginsMock.mockReturnValue(createToolRegistry([]));
1509168415101685resolvePluginTools({
15111686context: {
1512- ...createContext(),
1513-config: {
1514-plugins: {
1515-enabled: true,
1516-allow: ["tavily"],
1517-entries: {
1518-tavily: { enabled: true },
1519-},
1520-},
1521-},
1687+ ...context,
1688+ config,
15221689} as never,
1523-toolAllowlist: ["optional_tool", "tavily"],
1690+toolAllowlist: ["*", "tavily"],
15241691allowGatewaySubagentBinding: true,
15251692});
1526-15271693expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
15281694expect.objectContaining({
15291695onlyPluginIds: expect.arrayContaining(["tavily"]),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。