fix(plugins): apply bundled allowlist compat in plugin status report (#55267)

* 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.
This commit is contained in:
Ping
2026-03-27 22:00:25 +08:00
committed by GitHub
parent c2ca99aa0b
commit a6f5e57f46
2 changed files with 55 additions and 1 deletions

View File

@@ -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: [

View File

@@ -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),
});