diff --git a/src/gateway/server-plugin-bootstrap.ts b/src/gateway/server-plugin-bootstrap.ts index 99e64f70d18..ffb354cf54a 100644 --- a/src/gateway/server-plugin-bootstrap.ts +++ b/src/gateway/server-plugin-bootstrap.ts @@ -2,6 +2,7 @@ import { primeConfiguredBindingRegistry } from "../channels/plugins/binding-regi import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { BundledRuntimeDepsInstallParams } from "../plugins/bundled-runtime-deps.js"; +import type { PluginLookUpTable } from "../plugins/plugin-lookup-table.js"; import type { PluginRegistry } from "../plugins/registry.js"; import { pinActivePluginChannelRegistry } from "../plugins/runtime.js"; import { @@ -32,6 +33,7 @@ type GatewayPluginBootstrapParams = { coreGatewayMethodNames?: readonly string[]; baseMethods: string[]; pluginIds?: string[]; + pluginLookUpTable?: PluginLookUpTable; preferSetupRuntimeForChannelPlugins?: boolean; suppressPluginInfoLogs?: boolean; logDiagnostics?: boolean; @@ -72,6 +74,9 @@ export function prepareGatewayPluginLoad(params: GatewayPluginBootstrapParams) { const autoEnabled = applyPluginAutoEnable({ config: activationSourceConfig, env: process.env, + ...(params.pluginLookUpTable?.manifestRegistry + ? { manifestRegistry: params.pluginLookUpTable.manifestRegistry } + : {}), }); const resolvedConfig = autoEnabled.config; installGatewayPluginRuntimeEnvironment(resolvedConfig); @@ -89,6 +94,7 @@ export function prepareGatewayPluginLoad(params: GatewayPluginBootstrapParams) { }), baseMethods: params.baseMethods, pluginIds: params.pluginIds, + pluginLookUpTable: params.pluginLookUpTable, preferSetupRuntimeForChannelPlugins: params.preferSetupRuntimeForChannelPlugins, suppressPluginInfoLogs: params.suppressPluginInfoLogs, bundledRuntimeDepsInstaller: params.bundledRuntimeDepsInstaller, diff --git a/src/gateway/server-plugins.test.ts b/src/gateway/server-plugins.test.ts index b0c6e639bdb..391cc30e0ca 100644 --- a/src/gateway/server-plugins.test.ts +++ b/src/gateway/server-plugins.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import type { PluginLookUpTable } from "../plugins/plugin-lookup-table.js"; import type { PluginRegistry } from "../plugins/registry.js"; import type { PluginRuntimeGatewayRequestScope } from "../plugins/runtime/gateway-request-scope.js"; import type { PluginRuntime } from "../plugins/runtime/types.js"; @@ -108,6 +109,47 @@ const createRegistry = (diagnostics: PluginDiagnostic[]): PluginRegistry => ({ diagnostics, }); +function createLookUpTableForTest(params: { + manifestRegistry?: PluginLookUpTable["manifestRegistry"]; + pluginIds?: readonly string[]; +}): PluginLookUpTable { + return { + key: "test", + index: { + version: 1, + hostContractVersion: "test", + compatRegistryVersion: "test", + migrationVersion: 1, + policyHash: "test", + generatedAtMs: 1, + installRecords: {}, + plugins: [], + diagnostics: [], + }, + registryDiagnostics: [], + manifestRegistry: params.manifestRegistry ?? { plugins: [], diagnostics: [] }, + plugins: [], + diagnostics: [], + byPluginId: new Map(), + normalizePluginId: (pluginId) => pluginId, + owners: { + channels: new Map(), + channelConfigs: new Map(), + providers: new Map(), + modelCatalogProviders: new Map(), + cliBackends: new Map(), + setupProviders: new Map(), + commandAliases: new Map(), + contracts: new Map(), + }, + startup: { + channelPluginIds: [], + configuredDeferredChannelPluginIds: [], + pluginIds: params.pluginIds ?? [], + }, + }; +} + type ServerPluginsModule = typeof import("./server-plugins.js"); type ServerPluginBootstrapModule = typeof import("./server-plugin-bootstrap.js"); type PluginRuntimeModule = typeof import("../plugins/runtime/index.js"); @@ -372,6 +414,30 @@ describe("loadGatewayPlugins", () => { ); }); + test("reuses a provided lookup table for startup scope and auto-enable manifests", async () => { + loadOpenClawPlugins.mockReturnValue(createRegistry([])); + const manifestRegistry = { plugins: [], diagnostics: [] }; + + loadGatewayPluginsForTest({ + pluginLookUpTable: createLookUpTableForTest({ + manifestRegistry, + pluginIds: ["telegram"], + }), + }); + + expect(loadPluginLookUpTable).not.toHaveBeenCalled(); + expect(applyPluginAutoEnable).toHaveBeenCalledWith({ + config: {}, + env: process.env, + manifestRegistry, + }); + expect(loadOpenClawPlugins).toHaveBeenCalledWith( + expect.objectContaining({ + onlyPluginIds: ["telegram"], + }), + ); + }); + test("pins the initial startup channel registry against later active-registry churn", async () => { const startupRegistry = createRegistry([]); loadOpenClawPlugins.mockReturnValue(startupRegistry); diff --git a/src/gateway/server-plugins.ts b/src/gateway/server-plugins.ts index 68bf197a9cc..2494525b145 100644 --- a/src/gateway/server-plugins.ts +++ b/src/gateway/server-plugins.ts @@ -5,7 +5,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { BundledRuntimeDepsInstallParams } from "../plugins/bundled-runtime-deps.js"; import { normalizePluginsConfig } from "../plugins/config-state.js"; import { loadOpenClawPlugins } from "../plugins/loader.js"; -import { loadPluginLookUpTable } from "../plugins/plugin-lookup-table.js"; +import { loadPluginLookUpTable, type PluginLookUpTable } from "../plugins/plugin-lookup-table.js"; import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { getPluginRuntimeGatewayRequestScope } from "../plugins/runtime/gateway-request-scope.js"; @@ -447,6 +447,7 @@ export function loadGatewayPlugins(params: { coreGatewayMethodNames?: readonly string[]; baseMethods: string[]; pluginIds?: string[]; + pluginLookUpTable?: PluginLookUpTable; preferSetupRuntimeForChannelPlugins?: boolean; suppressPluginInfoLogs?: boolean; bundledRuntimeDepsInstaller?: (params: BundledRuntimeDepsInstallParams) => void; @@ -456,6 +457,9 @@ export function loadGatewayPlugins(params: { ? applyPluginAutoEnable({ config: params.activationSourceConfig, env: process.env, + ...(params.pluginLookUpTable?.manifestRegistry + ? { manifestRegistry: params.pluginLookUpTable.manifestRegistry } + : {}), }) : undefined; const autoEnabled = @@ -475,15 +479,21 @@ export function loadGatewayPlugins(params: { : applyPluginAutoEnable({ config: params.cfg, env: process.env, + ...(params.pluginLookUpTable?.manifestRegistry + ? { manifestRegistry: params.pluginLookUpTable.manifestRegistry } + : {}), }); const resolvedConfig = autoEnabled.config; const pluginIds = params.pluginIds ?? [ - ...loadPluginLookUpTable({ - config: resolvedConfig, - activationSourceConfig: params.activationSourceConfig, - workspaceDir: params.workspaceDir, - env: process.env, - }).startup.pluginIds, + ...( + params.pluginLookUpTable ?? + loadPluginLookUpTable({ + config: resolvedConfig, + activationSourceConfig: params.activationSourceConfig, + workspaceDir: params.workspaceDir, + env: process.env, + }) + ).startup.pluginIds, ]; if (pluginIds.length === 0) { const pluginRegistry = createEmptyPluginRegistry(); diff --git a/src/gateway/server-startup-plugins.test.ts b/src/gateway/server-startup-plugins.test.ts index f2fc3d38fdc..71150aa1dfc 100644 --- a/src/gateway/server-startup-plugins.test.ts +++ b/src/gateway/server-startup-plugins.test.ts @@ -20,8 +20,10 @@ const repairBundledRuntimeDepsInstallRootAsync = vi.hoisted(() => const resolveBundledRuntimeDependencyPackageInstallRoot = vi.hoisted(() => vi.fn((_packageRoot: string, _params: unknown) => "/runtime"), ); +const pluginManifestRegistry = vi.hoisted(() => ({ plugins: [], diagnostics: [] })); const loadPluginLookUpTable = vi.hoisted(() => vi.fn((_params: unknown) => ({ + manifestRegistry: pluginManifestRegistry, startup: { configuredDeferredChannelPluginIds: [], pluginIds: ["telegram"], @@ -117,6 +119,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => { repairBundledRuntimeDepsInstallRootAsync.mockReset().mockResolvedValue({}); resolveBundledRuntimeDependencyPackageInstallRoot.mockClear(); loadPluginLookUpTable.mockClear().mockReturnValue({ + manifestRegistry: pluginManifestRegistry, startup: { configuredDeferredChannelPluginIds: [], pluginIds: ["telegram"], @@ -152,6 +155,13 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => { expect(loadGatewayStartupPlugins).toHaveBeenCalledOnce(); expect(loadPluginLookUpTable).toHaveBeenCalledOnce(); + expect(loadGatewayStartupPlugins).toHaveBeenCalledWith( + expect.objectContaining({ + pluginLookUpTable: expect.objectContaining({ + manifestRegistry: pluginManifestRegistry, + }), + }), + ); expect(scanBundledPluginRuntimeDeps).toHaveBeenCalledWith( expect.objectContaining({ selectedPluginIds: ["telegram"], diff --git a/src/gateway/server-startup-plugins.ts b/src/gateway/server-startup-plugins.ts index 93e8d5e1dd6..a39ba93ffe7 100644 --- a/src/gateway/server-startup-plugins.ts +++ b/src/gateway/server-startup-plugins.ts @@ -168,6 +168,7 @@ export async function prepareGatewayPluginBootstrap(params: { coreGatewayMethodNames: baseMethods, baseMethods, pluginIds: startupPluginIds, + pluginLookUpTable, preferSetupRuntimeForChannelPlugins: deferredConfiguredChannelPluginIds.length > 0, suppressPluginInfoLogs: deferredConfiguredChannelPluginIds.length > 0, }));