From a6f5e57f46d05eaa41d123ae653264318ca08cd5 Mon Sep 17 00:00:00 2001 From: Ping <5123601+pingren@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:00:25 +0800 Subject: [PATCH] fix(plugins): apply bundled allowlist compat in plugin status report (#55267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(plugins): apply bundled allowlist compat in plugin status report `buildPluginStatusReport` (used by `openclaw plugins list` and `openclaw doctor`) was calling `loadOpenClawPlugins` without applying `withBundledPluginAllowlistCompat`. When `plugins.allow` is set, the allowlist check in `resolveEffectiveEnableState` runs before the bundled-default-enable check, causing all bundled plugins not explicitly in the allowlist to be reported as "disabled". The gateway runtime already applies this compat via `providers.runtime.ts`, so the actual loaded state differs from what CLI diagnostics report. Apply the same `withBundledPluginAllowlistCompat` transform so the status report matches gateway runtime behavior. * add regression test for bundled allowlist compat wiring Address review feedback: the previous mocks were identity stubs that did not exercise the compat wiring. Now the mocks are spies, and a new test verifies that: 1. loadPluginManifestRegistry is called to discover bundled plugin IDs 2. withBundledPluginAllowlistCompat receives only bundled IDs (not workspace) 3. loadOpenClawPlugins receives the compat-adjusted config * scope compat to bundled providers only (address codex review) Use resolveBundledProviderCompatPluginIds instead of injecting all bundled plugin IDs. This matches the runtime compat surface in providers.runtime.ts — non-provider bundled plugins (device-pair, phone-control, etc.) are not auto-added to the allowlist, keeping the status report consistent with gateway startup behavior. --- src/plugins/status.test.ts | 36 ++++++++++++++++++++++++++++++++++++ src/plugins/status.ts | 20 +++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/plugins/status.test.ts b/src/plugins/status.test.ts index d9657e4f628..83830e7c6ca 100644 --- a/src/plugins/status.test.ts +++ b/src/plugins/status.test.ts @@ -11,6 +11,8 @@ import { const loadConfigMock = vi.fn(); const loadOpenClawPluginsMock = vi.fn(); +const resolveBundledProviderCompatPluginIdsMock = vi.fn(); +const withBundledPluginAllowlistCompatMock = vi.fn(); let buildPluginStatusReport: typeof import("./status.js").buildPluginStatusReport; let buildPluginInspectReport: typeof import("./status.js").buildPluginInspectReport; let buildAllPluginInspectReports: typeof import("./status.js").buildAllPluginInspectReports; @@ -27,6 +29,16 @@ vi.mock("./loader.js", () => ({ loadOpenClawPlugins: (...args: unknown[]) => loadOpenClawPluginsMock(...args), })); +vi.mock("./providers.js", () => ({ + resolveBundledProviderCompatPluginIds: (...args: unknown[]) => + resolveBundledProviderCompatPluginIdsMock(...args), +})); + +vi.mock("./bundled-compat.js", () => ({ + withBundledPluginAllowlistCompat: (...args: unknown[]) => + withBundledPluginAllowlistCompatMock(...args), +})); + vi.mock("../agents/agent-scope.js", () => ({ resolveAgentWorkspaceDir: () => undefined, resolveDefaultAgentId: () => "default", @@ -50,7 +62,13 @@ describe("buildPluginStatusReport", () => { vi.resetModules(); loadConfigMock.mockReset(); loadOpenClawPluginsMock.mockReset(); + resolveBundledProviderCompatPluginIdsMock.mockReset(); + withBundledPluginAllowlistCompatMock.mockReset(); loadConfigMock.mockReturnValue({}); + resolveBundledProviderCompatPluginIdsMock.mockReturnValue([]); + withBundledPluginAllowlistCompatMock.mockImplementation( + (params: { config: unknown }) => params.config, + ); setPluginLoadResult({ plugins: [] }); ({ buildAllPluginInspectReports, @@ -81,6 +99,24 @@ describe("buildPluginStatusReport", () => { ); }); + it("applies bundled provider allowlist compat before loading plugins", () => { + const config = { plugins: { allow: ["telegram"] } }; + loadConfigMock.mockReturnValue(config); + resolveBundledProviderCompatPluginIdsMock.mockReturnValue(["anthropic", "openai"]); + const compatConfig = { plugins: { allow: ["telegram", "anthropic", "openai"] } }; + withBundledPluginAllowlistCompatMock.mockReturnValue(compatConfig); + + buildPluginStatusReport({ config }); + + expect(withBundledPluginAllowlistCompatMock).toHaveBeenCalledWith({ + config, + pluginIds: ["anthropic", "openai"], + }); + expect(loadOpenClawPluginsMock).toHaveBeenCalledWith( + expect.objectContaining({ config: compatConfig }), + ); + }); + it("normalizes bundled plugin versions to the core base release", () => { setPluginLoadResult({ plugins: [ diff --git a/src/plugins/status.ts b/src/plugins/status.ts index 4aef457a265..826fcce0ae4 100644 --- a/src/plugins/status.ts +++ b/src/plugins/status.ts @@ -6,9 +6,11 @@ import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveCompatibilityHostVersion } from "../version.js"; import { inspectBundleLspRuntimeSupport } from "./bundle-lsp.js"; import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js"; +import { withBundledPluginAllowlistCompat } from "./bundled-compat.js"; import { normalizePluginsConfig } from "./config-state.js"; import { loadOpenClawPlugins } from "./loader.js"; import { createPluginLoaderLogger } from "./logger.js"; +import { resolveBundledProviderCompatPluginIds } from "./providers.js"; import type { PluginRegistry } from "./registry.js"; import type { PluginDiagnostic, PluginHookName } from "./types.js"; @@ -143,10 +145,26 @@ export function buildPluginStatusReport(params?: { : (resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config)) ?? resolveDefaultAgentWorkspaceDir()); - const registry = loadOpenClawPlugins({ + // Apply bundled-provider allowlist compat so that `plugins list` and `doctor` + // report the same loaded/disabled status the gateway uses at runtime. Without + // this, bundled provider plugins are incorrectly shown as "disabled" when + // `plugins.allow` is set because the allowlist check runs before the + // bundled-default-enable check. Scoped to bundled providers only (not all + // bundled plugins) to match the runtime compat surface in providers.runtime.ts. + const bundledProviderIds = resolveBundledProviderCompatPluginIds({ config, workspaceDir, env: params?.env, + }); + const effectiveConfig = withBundledPluginAllowlistCompat({ + config, + pluginIds: bundledProviderIds, + }); + + const registry = loadOpenClawPlugins({ + config: effectiveConfig, + workspaceDir, + env: params?.env, logger: createPluginLoaderLogger(log), });