From b2096d19ecde18efe98715e8c091e3d665ef508e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 4 May 2026 22:46:39 +0100 Subject: [PATCH] fix(plugins): default bundled discovery to allowlist --- CHANGELOG.md | 2 +- docs/.generated/config-baseline.sha256 | 4 +- docs/gateway/configuration-reference.md | 9 +-- docs/gateway/doctor.md | 6 +- docs/tools/plugin.md | 13 ++-- ....providers.plugin-allowlist-compat.test.ts | 29 +++++++- .../shared/legacy-config-migrate.test.ts | 27 ++++++++ ...acy-config-migrations.runtime.providers.ts | 34 ++++++++++ .../plugin-tool-allowlist-warnings.test.ts | 31 +++++---- .../shared/plugin-tool-allowlist-warnings.ts | 2 +- src/config/schema.base.generated.ts | 4 +- src/config/schema.help.ts | 2 +- src/config/types.plugins.ts | 8 +-- src/plugins/activation-context.ts | 4 +- src/plugins/bundled-compat.ts | 8 ++- src/plugins/providers.test.ts | 68 +++++++++++-------- src/plugins/providers.ts | 7 +- src/plugins/web-provider-public-artifacts.ts | 2 +- .../web-search-providers.runtime.test.ts | 2 +- 19 files changed, 185 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc9a44a6ce..b1143d810d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,7 +121,7 @@ Docs: https://docs.openclaw.ai - Plugins/packages: reject blank `openclaw.runtimeExtensions` entries instead of silently ignoring them and falling back to inferred TypeScript runtime entries. Thanks @vincentkoc. - Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc. - Plugins/runtime state: keep the key being registered when namespace eviction runs in the same millisecond as existing entries, so `register` and `registerIfAbsent` do not report success while evicting their own fresh value. Thanks @vincentkoc. -- Plugins/providers: add `plugins.bundledDiscovery: "allowlist"` so restrictive `plugins.allow` deployments can keep bundled provider and web-search provider discovery from auto-loading omitted bundled plugins. Thanks @dougbtv. +- Plugins/providers: make bundled provider discovery honor restrictive `plugins.allow` by default for new configs, while doctor migrates legacy restrictive allowlist configs to `plugins.bundledDiscovery: "compat"` to preserve upgrade behavior. Thanks @dougbtv. - Control UI/Talk: make failed Talk startup errors dismissable and clear the stale Talk error state when dismissed, so missing realtime voice provider configuration does not leave a permanent chat banner. Fixes #77071. Thanks @ijoshdavis. - Control UI/Talk: stop and clear failed realtime Talk sessions when dismissing runtime error banners, so the next Talk click starts a fresh session instead of only stopping the stale one. Thanks @vincentkoc. - Control UI/Talk: retry from a failed realtime Talk session on the next Talk click instead of requiring a separate stale-session stop click first. Thanks @vincentkoc. diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index d9affa3c7b4..e0a057e0e3d 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -0fc1b2f75e34ab92274067cdd6bfcfeac01c5b898a3aa355cfa1c3a5ec18bd5d config-baseline.json -0a6a2493d90ffe6204be807a8bc12dcf34f854602cc1c5e3c4917fed902d310e config-baseline.core.json +02987f4cecb64a98170b61c925fd7b16a22b276abfb261f9281b42f613ded923 config-baseline.json +de5a6f65ef09dc23453a2e12512e41c133c941519e0ebef7f2946e4a24265d17 config-baseline.core.json cd7c0c7fb1435bc7e59099e9ac334462d5ad444016e9ab4512aae63a238f78dc config-baseline.channel.json 9832b30a696930a3da7efccf38073137571e1b66cae84e54d747b733fdafcc54 config-baseline.plugin.json diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index d9301b1a412..789bc97c3e9 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -166,7 +166,7 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and plugins: { enabled: true, allow: ["voice-call"], - bundledDiscovery: "compat", + bundledDiscovery: "allowlist", deny: [], load: { paths: ["~/Projects/oss/voice-call-plugin"], @@ -188,9 +188,10 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and - Discovery accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles. - **Config changes require a gateway restart.** - `allow`: optional allowlist (only listed plugins load). `deny` wins. -- `bundledDiscovery`: defaults to `"compat"` for legacy bundled provider activation. - Use `"allowlist"` when a non-empty `plugins.allow` should also gate - bundled provider plugins, including web-search runtime providers. +- `bundledDiscovery`: defaults to `"allowlist"` for new configs, so a non-empty + `plugins.allow` also gates bundled provider plugins, including web-search + runtime providers. Doctor writes `"compat"` for migrated legacy allowlist + configs to preserve existing bundled provider behavior until you opt in. - `plugins.entries..apiKey`: plugin-level API key convenience field (when supported by the plugin). - `plugins.entries..env`: plugin-scoped env var map. - `plugins.entries..hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. Applies to native plugin hooks and supported bundle-provided hook directories. diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index c33f1000666..35389f89d9a 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -169,9 +169,9 @@ That stages grounded durable candidates into the short-term dreaming store while Doctor also warns when `plugins.allow` is non-empty and tool policy uses wildcard or plugin-owned tool entries. `tools.allow: ["*"]` only matches tools from plugins that actually load; it does not bypass the exclusive plugin - allowlist. If bundled provider discovery is still in legacy compatibility - mode, doctor also points to the stricter `plugins.bundledDiscovery: - "allowlist"` setting. + allowlist. Doctor writes `plugins.bundledDiscovery: "compat"` for migrated + legacy allowlist configs to preserve existing bundled provider behavior, and + then points to the stricter `"allowlist"` setting. diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index cdef016d8c0..007bc7cf232 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -264,7 +264,7 @@ Looking for third-party plugins? See [Community Plugins](/plugins/community). | ------------------ | --------------------------------------------------------- | | `enabled` | Master toggle (default: `true`) | | `allow` | Plugin allowlist (optional) | -| `bundledDiscovery` | Bundled plugin discovery mode (`compat` by default) | +| `bundledDiscovery` | Bundled plugin discovery mode (`allowlist` by default) | | `deny` | Plugin denylist (optional; deny wins) | | `load.paths` | Extra plugin files/directories | | `slots` | Exclusive slot selectors (e.g. `memory`, `contextEngine`) | @@ -276,11 +276,12 @@ tool name. If a tool allowlist references plugin tools, add the owning plugin id to `plugins.allow` or remove `plugins.allow`; `openclaw doctor` warns about this shape. -`plugins.bundledDiscovery` defaults to `"compat"` so older configs keep legacy -bundled provider behavior. Set it to `"allowlist"` when a restrictive -`plugins.allow` inventory should also block omitted bundled provider plugins, -including runtime web-search provider discovery. An empty `plugins.allow` is -still treated as unset/open. +`plugins.bundledDiscovery` defaults to `"allowlist"` for new configs, so a +restrictive `plugins.allow` inventory also blocks omitted bundled provider +plugins, including runtime web-search provider discovery. Doctor stamps older +restrictive allowlist configs with `"compat"` during migration so upgrades keep +legacy bundled provider behavior until the operator opts into the stricter mode. +An empty `plugins.allow` is still treated as unset/open. Config changes made through `/plugins enable` or `/plugins disable` trigger an in-process Gateway plugin reload. New agent turns rebuild their tool list from diff --git a/src/agents/models-config.providers.plugin-allowlist-compat.test.ts b/src/agents/models-config.providers.plugin-allowlist-compat.test.ts index aac1727d3d8..bd6070e98bc 100644 --- a/src/agents/models-config.providers.plugin-allowlist-compat.test.ts +++ b/src/agents/models-config.providers.plugin-allowlist-compat.test.ts @@ -61,7 +61,31 @@ const providerManifestRegistry: PluginManifestRegistry = { }; describe("implicit provider plugin allowlist compatibility", () => { - it("keeps bundled implicit providers discoverable when plugins.allow is set", () => { + it("keeps bundled implicit providers discoverable in explicit compat mode", () => { + const config = withBundledPluginEnablementCompat({ + config: withBundledPluginAllowlistCompat({ + config: { + plugins: { + allow: ["openrouter"], + bundledDiscovery: "compat", + }, + }, + pluginIds: ["kilocode", "moonshot"], + }), + pluginIds: ["kilocode", "moonshot"], + }); + + expect( + resolveEnabledProviderPluginIds({ + config, + registry: providerRegistry, + manifestRegistry: providerManifestRegistry, + onlyPluginIds: PROVIDER_PLUGIN_IDS, + }), + ).toEqual(["kilocode", "moonshot", "openrouter"]); + }); + + it("respects allowlist for bundled plugins by default", () => { const config = withBundledPluginEnablementCompat({ config: withBundledPluginAllowlistCompat({ config: { @@ -81,7 +105,7 @@ describe("implicit provider plugin allowlist compatibility", () => { manifestRegistry: providerManifestRegistry, onlyPluginIds: PROVIDER_PLUGIN_IDS, }), - ).toEqual(["kilocode", "moonshot", "openrouter"]); + ).toEqual(["openrouter"]); }); it("respects allowlist for bundled plugins when bundledDiscovery is allowlist", () => { @@ -114,6 +138,7 @@ describe("implicit provider plugin allowlist compatibility", () => { config: { plugins: { allow: ["openrouter"], + bundledDiscovery: "compat", deny: ["kilocode"], }, }, diff --git a/src/commands/doctor/shared/legacy-config-migrate.test.ts b/src/commands/doctor/shared/legacy-config-migrate.test.ts index 2497f26a455..6d008e3be03 100644 --- a/src/commands/doctor/shared/legacy-config-migrate.test.ts +++ b/src/commands/doctor/shared/legacy-config-migrate.test.ts @@ -267,6 +267,33 @@ describe("legacy migrate mention routing", () => { }); }); +describe("legacy bundled provider discovery migrate", () => { + it("sets compat mode for existing restrictive plugin allowlists", () => { + const res = migrateLegacyConfigForTest({ + plugins: { + allow: ["telegram"], + }, + }); + + expect(res.config?.plugins?.bundledDiscovery).toBe("compat"); + expect(res.changes).toContain( + 'Set plugins.bundledDiscovery="compat" to preserve legacy bundled provider discovery for this restrictive plugins.allow config.', + ); + }); + + it("does not override explicit bundled discovery mode", () => { + const res = migrateLegacyConfigForTest({ + plugins: { + allow: ["telegram"], + bundledDiscovery: "allowlist", + }, + }); + + expect(res.config).toBeNull(); + expect(res.changes).toEqual([]); + }); +}); + describe("legacy migrate sandbox scope aliases", () => { it("removes legacy agents.defaults.llm timeout config", () => { const res = migrateLegacyConfigForTest({ diff --git a/src/commands/doctor/shared/legacy-config-migrations.runtime.providers.ts b/src/commands/doctor/shared/legacy-config-migrations.runtime.providers.ts index 89e26d797e0..bce98e874ac 100644 --- a/src/commands/doctor/shared/legacy-config-migrations.runtime.providers.ts +++ b/src/commands/doctor/shared/legacy-config-migrations.runtime.providers.ts @@ -3,6 +3,7 @@ import { type LegacyConfigMigrationSpec, type LegacyConfigRule, } from "../../../config/legacy.shared.js"; +import { isRecord } from "./legacy-config-record-shared.js"; import { migrateLegacyXSearchConfig } from "./legacy-x-search-migrate.js"; const X_SEARCH_RULE: LegacyConfigRule = { @@ -11,7 +12,40 @@ const X_SEARCH_RULE: LegacyConfigRule = { 'tools.web.x_search.apiKey moved to the xAI plugin; use plugins.entries.xai.config.webSearch.apiKey instead. Run "openclaw doctor --fix".', }; +const BUNDLED_DISCOVERY_COMPAT_RULE: LegacyConfigRule = { + path: ["plugins", "allow"], + message: + 'plugins.allow now gates bundled provider discovery by default; run "openclaw doctor --fix" to preserve legacy bundled provider compatibility as plugins.bundledDiscovery="compat", or set plugins.bundledDiscovery="allowlist" to keep the stricter behavior.', + requireSourceLiteral: true, + match: (value, root) => { + if (!Array.isArray(value) || value.length === 0) { + return false; + } + const plugins = isRecord(root.plugins) ? root.plugins : undefined; + return plugins?.bundledDiscovery === undefined; + }, +}; + export const LEGACY_CONFIG_MIGRATIONS_RUNTIME_PROVIDERS: LegacyConfigMigrationSpec[] = [ + defineLegacyConfigMigration({ + id: "plugins.allow->plugins.bundledDiscovery.compat", + describe: "Preserve legacy bundled provider discovery for existing restrictive allowlists", + legacyRules: [BUNDLED_DISCOVERY_COMPAT_RULE], + apply: (raw, changes) => { + const plugins = isRecord(raw.plugins) ? raw.plugins : undefined; + if (!plugins || plugins.bundledDiscovery !== undefined) { + return; + } + const allow = plugins.allow; + if (!Array.isArray(allow) || allow.length === 0) { + return; + } + plugins.bundledDiscovery = "compat"; + changes.push( + 'Set plugins.bundledDiscovery="compat" to preserve legacy bundled provider discovery for this restrictive plugins.allow config.', + ); + }, + }), defineLegacyConfigMigration({ id: "tools.web.x_search.apiKey->plugins.entries.xai.config.webSearch.apiKey", describe: "Move legacy x_search auth into the xAI plugin webSearch config", diff --git a/src/commands/doctor/shared/plugin-tool-allowlist-warnings.test.ts b/src/commands/doctor/shared/plugin-tool-allowlist-warnings.test.ts index 2dbb491a7e2..981b21079f1 100644 --- a/src/commands/doctor/shared/plugin-tool-allowlist-warnings.test.ts +++ b/src/commands/doctor/shared/plugin-tool-allowlist-warnings.test.ts @@ -113,10 +113,13 @@ describe("collectPluginToolAllowlistWarnings", () => { expect(warnings).toEqual([]); }); - it("warns when restrictive plugins.allow leaves bundled provider discovery in compat mode", () => { + it("warns when restrictive plugins.allow leaves bundled provider discovery in explicit compat mode", () => { const warnings = collectBundledProviderAllowlistPolicyWarnings({ cfg: { - plugins: { allow: ["telegram"] }, + plugins: { + allow: ["telegram"], + bundledDiscovery: "compat", + }, }, }); @@ -125,16 +128,18 @@ describe("collectPluginToolAllowlistWarnings", () => { ]); }); - it("does not warn when bundled provider discovery follows the allowlist", () => { - const warnings = collectBundledProviderAllowlistPolicyWarnings({ - cfg: { - plugins: { - allow: ["telegram"], - bundledDiscovery: "allowlist", - }, - }, - }); + it.each([ + { name: "default", plugins: { allow: ["telegram"] } }, + { + name: "explicit allowlist", + plugins: { allow: ["telegram"], bundledDiscovery: "allowlist" }, + }, + ])( + "does not warn when bundled provider discovery follows the allowlist ($name)", + ({ plugins }) => { + const warnings = collectBundledProviderAllowlistPolicyWarnings({ cfg: { plugins } }); - expect(warnings).toEqual([]); - }); + expect(warnings).toEqual([]); + }, + ); }); diff --git a/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts b/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts index 725429406d9..3dfd6b4188f 100644 --- a/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts +++ b/src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts @@ -204,7 +204,7 @@ export function collectBundledProviderAllowlistPolicyWarnings(params: { if (!Array.isArray(allow) || allow.length === 0) { return []; } - if (params.cfg.plugins?.bundledDiscovery === "allowlist") { + if (params.cfg.plugins?.bundledDiscovery !== "compat") { return []; } return [ diff --git a/src/config/schema.base.generated.ts b/src/config/schema.base.generated.ts index db0ae151a10..940b6e97094 100644 --- a/src/config/schema.base.generated.ts +++ b/src/config/schema.base.generated.ts @@ -24191,7 +24191,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { enum: ["compat", "allowlist"], title: "Bundled Plugin Discovery", description: - 'Controls bundled plugin runtime discovery when plugins.allow is configured. "compat" (default) preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn. "allowlist" gates bundled provider plugins by plugins.allow like third-party plugins.', + 'Controls bundled plugin runtime discovery when plugins.allow is configured. "allowlist" (default) gates bundled provider plugins by plugins.allow like third-party plugins. "compat" preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn.', }, }, additionalProperties: false, @@ -28874,7 +28874,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { }, "plugins.bundledDiscovery": { label: "Bundled Plugin Discovery", - help: 'Controls bundled plugin runtime discovery when plugins.allow is configured. "compat" (default) preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn. "allowlist" gates bundled provider plugins by plugins.allow like third-party plugins.', + help: 'Controls bundled plugin runtime discovery when plugins.allow is configured. "allowlist" (default) gates bundled provider plugins by plugins.allow like third-party plugins. "compat" preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn.', tags: ["advanced"], }, "plugins.deny": { diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index 07562e0892f..78410289d5a 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -1213,7 +1213,7 @@ export const FIELD_HELP: Record = { "plugins.slots.contextEngine": "Selects the active context engine plugin by id so one plugin provides context orchestration behavior.", "plugins.bundledDiscovery": - 'Controls bundled plugin runtime discovery when plugins.allow is configured. "compat" (default) preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn. "allowlist" gates bundled provider plugins by plugins.allow like third-party plugins.', + 'Controls bundled plugin runtime discovery when plugins.allow is configured. "allowlist" (default) gates bundled provider plugins by plugins.allow like third-party plugins. "compat" preserves legacy behavior where bundled provider plugins can be force-loaded on every chat turn.', "plugins.entries": "Per-plugin settings keyed by plugin ID including enablement and plugin-specific runtime configuration payloads. Use this for scoped plugin tuning without changing global loader policy.", "plugins.entries.*.enabled": diff --git a/src/config/types.plugins.ts b/src/config/types.plugins.ts index c3c9d67d978..6025a11b44f 100644 --- a/src/config/types.plugins.ts +++ b/src/config/types.plugins.ts @@ -55,10 +55,10 @@ export type PluginsConfig = { * Controls how bundled plugins participate in runtime provider discovery when * `allow` is configured. * - * - `"compat"` (default): bundled provider plugins are force-loaded on - * every chat turn regardless of the allowlist (legacy behavior). - * - `"allowlist"`: bundled provider plugins are gated by `allow` and - * `entries..enabled` like third-party plugins. + * - `"allowlist"` (default): bundled provider plugins are gated by `allow` + * and `entries..enabled` like third-party plugins. + * - `"compat"`: legacy mode for migrated configs; bundled provider plugins + * can be force-loaded regardless of the allowlist. */ bundledDiscovery?: "compat" | "allowlist"; load?: PluginsLoadConfig; diff --git a/src/plugins/activation-context.ts b/src/plugins/activation-context.ts index 6f6a516ef84..851a390f2bc 100644 --- a/src/plugins/activation-context.ts +++ b/src/plugins/activation-context.ts @@ -79,9 +79,9 @@ export function withActivatedPluginIds(params: { return params.config; } const originalAllow = params.config?.plugins?.allow ?? []; - // Empty allowlists are still open; allowlist mode only stops compat from widening configured allowlists. + // Empty allowlists are still open; only explicit compat widens configured allowlists. const useAllowlistDiscovery = - params.config?.plugins?.bundledDiscovery === "allowlist" && originalAllow.length > 0; + params.config?.plugins?.bundledDiscovery !== "compat" && originalAllow.length > 0; const originalAllowSet = useAllowlistDiscovery ? new Set(originalAllow) : undefined; const allow = new Set(originalAllow); const entries = { diff --git a/src/plugins/bundled-compat.ts b/src/plugins/bundled-compat.ts index f1757864cb3..24fa7129038 100644 --- a/src/plugins/bundled-compat.ts +++ b/src/plugins/bundled-compat.ts @@ -6,7 +6,7 @@ export function withBundledPluginAllowlistCompat(params: { config: OpenClawConfig | undefined; pluginIds: readonly string[]; }): OpenClawConfig | undefined { - if (params.config?.plugins?.bundledDiscovery === "allowlist") { + if (params.config?.plugins?.bundledDiscovery !== "compat") { return params.config; } const allow = params.config?.plugins?.allow; @@ -42,8 +42,10 @@ export function withBundledPluginEnablementCompat(params: { }): OpenClawConfig | undefined { const existingEntries = params.config?.plugins?.entries ?? {}; const forcePluginsEnabled = params.config?.plugins?.enabled === false; - const useAllowlistDiscovery = params.config?.plugins?.bundledDiscovery === "allowlist"; - const allowSet = useAllowlistDiscovery ? new Set(params.config?.plugins?.allow ?? []) : undefined; + const useCompatDiscovery = params.config?.plugins?.bundledDiscovery === "compat"; + const allow = params.config?.plugins?.allow; + const allowSet = + !useCompatDiscovery && Array.isArray(allow) && allow.length > 0 ? new Set(allow) : undefined; let changed = false; const nextEntries: Record = { ...existingEntries }; diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index 3d678a7a02e..c5386ee0c16 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -295,6 +295,7 @@ function createBundledProviderCompatOptions(params?: { onlyPluginIds?: readonly config: { plugins: { allow: ["openrouter"], + bundledDiscovery: "compat" as const, }, }, bundledProviderAllowlistCompat: true, @@ -593,7 +594,7 @@ describe("resolvePluginProviders", () => { ).toEqual(["legacy-auth-owner"]); }); - it("filters bundled provider plugins by allowlist when bundledDiscovery is allowlist", () => { + it("filters bundled provider plugins by allowlist by default", () => { setManifestPlugins([ createManifestProviderPlugin({ id: "kilocode", @@ -619,7 +620,6 @@ describe("resolvePluginProviders", () => { config: { plugins: { allow: ["openrouter"], - bundledDiscovery: "allowlist", }, }, env: {} as NodeJS.ProcessEnv, @@ -628,7 +628,7 @@ describe("resolvePluginProviders", () => { expect(discovered).toEqual(["openrouter"]); }); - it("returns all bundled provider plugins in compat mode (default)", () => { + it("returns all bundled provider plugins in explicit compat mode", () => { setManifestPlugins([ createManifestProviderPlugin({ id: "kilocode", @@ -654,6 +654,7 @@ describe("resolvePluginProviders", () => { config: { plugins: { allow: ["openrouter"], + bundledDiscovery: "compat", }, }, env: {} as NodeJS.ProcessEnv, @@ -829,6 +830,7 @@ describe("resolvePluginProviders", () => { config: { plugins: { allow: ["openrouter"], + bundledDiscovery: "compat", }, }, bundledProviderAllowlistCompat: true, @@ -842,11 +844,38 @@ describe("resolvePluginProviders", () => { }); }); - it("loads all discovered provider plugins in setup mode", () => { + it("scopes setup provider plugin discovery to the allowlist by default", () => { + resolvePluginProviders({ + config: { + plugins: { + allow: ["google"], + }, + }, + mode: "setup", + includeUntrustedWorkspacePlugins: false, + }); + + expectLastSetupRegistryLoad({ + onlyPluginIds: ["google"], + }); + expect(getLastSetupLoadedPluginConfig()).toEqual( + expect.objectContaining({ + plugins: expect.objectContaining({ + allow: ["google"], + entries: expect.objectContaining({ + google: { enabled: true }, + }), + }), + }), + ); + }); + + it("loads all discovered provider plugins in setup mode for explicit compat configs", () => { resolvePluginProviders({ config: { plugins: { allow: ["openrouter"], + bundledDiscovery: "compat", entries: { google: { enabled: false }, }, @@ -884,6 +913,7 @@ describe("resolvePluginProviders", () => { config: { plugins: { allow: ["openrouter"], + bundledDiscovery: "compat", }, }, mode: "setup", @@ -900,6 +930,7 @@ describe("resolvePluginProviders", () => { config: { plugins: { allow: ["openrouter", "workspace-provider"], + bundledDiscovery: "compat", entries: { "workspace-provider": { enabled: false }, }, @@ -919,6 +950,7 @@ describe("resolvePluginProviders", () => { config: { plugins: { allow: ["openrouter", "workspace-provider"], + bundledDiscovery: "compat", deny: ["workspace-provider"], entries: { "workspace-provider": { enabled: false }, @@ -948,9 +980,7 @@ describe("resolvePluginProviders", () => { includeUntrustedWorkspacePlugins: false, }); - expectLastSetupRegistryLoad({ - onlyPluginIds: ["google", "kilocode", "moonshot"], - }); + expect(loadOpenClawPluginsMock).not.toHaveBeenCalled(); }); it("loads provider plugins from the auto-enabled config snapshot", () => { @@ -1242,16 +1272,7 @@ describe("resolvePluginProviders", () => { mode: "setup", }); - expect(loadOpenClawPluginsMock).toHaveBeenCalledWith( - expect.objectContaining({ - config: expect.objectContaining({ - plugins: expect.objectContaining({ - enabled: false, - allow: ["setup-owned-provider"], - }), - }), - }), - ); + expect(loadOpenClawPluginsMock).not.toHaveBeenCalled(); }); it("does not override explicitly disabled setup owners", () => { @@ -1278,18 +1299,7 @@ describe("resolvePluginProviders", () => { mode: "setup", }); - expect(loadOpenClawPluginsMock).toHaveBeenCalledWith( - expect.objectContaining({ - config: expect.objectContaining({ - plugins: expect.objectContaining({ - allow: ["setup-owned-provider"], - entries: { - "setup-owned-provider": { enabled: false }, - }, - }), - }), - }), - ); + expect(loadOpenClawPluginsMock).not.toHaveBeenCalled(); }); it("filters explicit setup owners through the untrusted workspace discovery gate", () => { diff --git a/src/plugins/providers.ts b/src/plugins/providers.ts index 68e9fff84d8..af75d5f41e7 100644 --- a/src/plugins/providers.ts +++ b/src/plugins/providers.ts @@ -255,7 +255,7 @@ export function resolveDiscoveredProviderPluginIds(params: { const { registry, onlyPluginIdSet } = loadScopedProviderRegistry(params); const providerSurfacePluginIds = resolveProviderSurfacePluginIdSet({ ...params, registry }); const shouldFilterUntrustedWorkspacePlugins = params.includeUntrustedWorkspacePlugins === false; - const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledDiscovery === "allowlist"; + const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledDiscovery !== "compat"; const normalizedConfig = normalizePluginsConfigWithRegistry(params.config?.plugins, registry); return listRegistryPluginIds(registry, (plugin) => { if ( @@ -298,6 +298,9 @@ function isProviderPluginEligibleForSetupDiscovery(params: { ) { return false; } + if (params.plugin.origin === "bundled") { + return true; + } return isActivatedManifestOwner({ plugin: toManifestOwnerRecord(params.plugin), normalizedConfig: params.normalizedConfig, @@ -313,7 +316,7 @@ export function resolveDiscoverableProviderOwnerPluginIds(params: { includeUntrustedWorkspacePlugins?: boolean; }): string[] { const shouldFilterUntrustedWorkspacePlugins = params.includeUntrustedWorkspacePlugins === false; - const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledDiscovery === "allowlist"; + const shouldFilterBundledByAllowlist = params.config?.plugins?.bundledDiscovery !== "compat"; return resolveProviderOwnerPluginIds({ ...params, isEligible: (plugin, normalizedConfig) => diff --git a/src/plugins/web-provider-public-artifacts.ts b/src/plugins/web-provider-public-artifacts.ts index cec130ae0fe..96631ee6720 100644 --- a/src/plugins/web-provider-public-artifacts.ts +++ b/src/plugins/web-provider-public-artifacts.ts @@ -32,7 +32,7 @@ function filterAllowlistedBundledPluginIds( ) { const allow = config?.plugins?.allow; if ( - config?.plugins?.bundledDiscovery !== "allowlist" || + config?.plugins?.bundledDiscovery === "compat" || !Array.isArray(allow) || allow.length === 0 ) { diff --git a/src/plugins/web-search-providers.runtime.test.ts b/src/plugins/web-search-providers.runtime.test.ts index 1dd2da06136..5b310d1f164 100644 --- a/src/plugins/web-search-providers.runtime.test.ts +++ b/src/plugins/web-search-providers.runtime.test.ts @@ -445,7 +445,7 @@ describe("resolvePluginWebSearchProviders", () => { const providers = resolvePluginWebSearchProviders({ config: { plugins: { - allow: ["perplexity"], + allow: ["brave"], }, }, mode: "setup",