From 37f8507b4bbab346a0b5ee40212aef93ffcb4389 Mon Sep 17 00:00:00 2001 From: joshavant <830519+joshavant@users.noreply.github.com> Date: Fri, 15 May 2026 19:55:32 -0500 Subject: [PATCH] fix: start configured web search providers --- src/plugins/channel-plugin-ids.test.ts | 114 ++++++++++++++++++++++ src/plugins/gateway-startup-plugin-ids.ts | 83 ++++++++++++++++ 2 files changed, 197 insertions(+) diff --git a/src/plugins/channel-plugin-ids.test.ts b/src/plugins/channel-plugin-ids.test.ts index 5fb67f63286..9ade1f80a3b 100644 --- a/src/plugins/channel-plugin-ids.test.ts +++ b/src/plugins/channel-plugin-ids.test.ts @@ -183,6 +183,17 @@ function createManifestRegistryFixture(): PluginManifestRegistry { musicGenerationProviders: ["google"], }, }, + { + id: "brave", + channels: [], + origin: "global", + enabledByDefault: undefined, + providers: [], + cliBackends: [], + contracts: { + webSearchProviders: ["brave"], + }, + }, { id: "codex", channels: [], @@ -819,6 +830,75 @@ describe("resolveGatewayStartupPluginIds", () => { } as OpenClawConfig, ["browser", "memory-core"], ], + [ + "includes explicitly selected external web search providers at startup", + { + channels: {}, + tools: { + web: { + search: { + enabled: true, + provider: "brave", + }, + }, + }, + plugins: { + allow: ["brave"], + entries: { + brave: { + enabled: true, + }, + }, + }, + } as OpenClawConfig, + ["brave"], + ], + [ + "honors disabled web search when selecting startup providers", + { + channels: {}, + tools: { + web: { + search: { + enabled: false, + provider: "brave", + }, + }, + }, + plugins: { + allow: ["brave"], + entries: { + brave: { + enabled: true, + }, + }, + }, + } as OpenClawConfig, + [], + ], + [ + "honors explicit plugin disablement for configured web search providers", + { + channels: {}, + tools: { + web: { + search: { + enabled: true, + provider: "brave", + }, + }, + }, + plugins: { + allow: ["brave"], + entries: { + brave: { + enabled: false, + }, + }, + }, + } as OpenClawConfig, + [], + ], [ "keeps configured generation providers behind restrictive allowlists", { @@ -884,6 +964,40 @@ describe("resolveGatewayStartupPluginIds", () => { }); }); + it("includes auto-enabled external web search providers at startup", () => { + const rawConfig = { + channels: {}, + tools: { + web: { + search: { + enabled: true, + provider: "brave", + }, + }, + }, + plugins: { + allow: ["browser"], + }, + } as OpenClawConfig; + const effectiveConfig = { + ...rawConfig, + plugins: { + allow: ["browser", "brave"], + entries: { + brave: { + enabled: true, + }, + }, + }, + } as OpenClawConfig; + + expectStartupPluginIdsCase({ + config: effectiveConfig, + activationSourceConfig: rawConfig, + expected: ["browser", "brave"], + }); + }); + it("does not let runtime-default plugin entries bypass the authored startup allowlist", () => { const activationSourceConfig = { channels: {}, diff --git a/src/plugins/gateway-startup-plugin-ids.ts b/src/plugins/gateway-startup-plugin-ids.ts index f68f9e81ded..7c7a6408bf8 100644 --- a/src/plugins/gateway-startup-plugin-ids.ts +++ b/src/plugins/gateway-startup-plugin-ids.ts @@ -214,6 +214,28 @@ function manifestOwnsConfiguredSpeechProvider(params: { }); } +function collectConfiguredWebSearchProviderIds(config: OpenClawConfig): ReadonlySet { + const search = config.tools?.web?.search; + if (search?.enabled === false || typeof search?.provider !== "string") { + return new Set(); + } + const providerId = normalizeOptionalLowercaseString(search.provider); + return providerId ? new Set([providerId]) : new Set(); +} + +function manifestOwnsConfiguredWebSearchProvider(params: { + manifest: PluginManifestRecord | undefined; + configuredWebSearchProviderIds: ReadonlySet; +}): boolean { + if (params.configuredWebSearchProviderIds.size === 0) { + return false; + } + return (params.manifest?.contracts?.webSearchProviders ?? []).some((providerId) => { + const normalized = normalizeOptionalLowercaseString(providerId); + return normalized ? params.configuredWebSearchProviderIds.has(normalized) : false; + }); +} + function listModelProviderRefs(value: unknown): string[] { if (typeof value === "string") { return [value]; @@ -433,6 +455,52 @@ function canStartConfiguredSpeechProviderPlugin(params: { return activationState.enabled && activationState.explicitlyEnabled; } +function canStartConfiguredWebSearchProviderPlugin(params: { + plugin: InstalledPluginIndexRecord; + manifest: PluginManifestRecord | undefined; + config: OpenClawConfig; + pluginsConfig: ReturnType; + activationSource: { + plugins: ReturnType; + rootConfig?: OpenClawConfig; + }; + configuredWebSearchProviderIds: ReadonlySet; + platform?: NodeJS.Platform; +}): boolean { + if ( + !manifestOwnsConfiguredWebSearchProvider({ + manifest: params.manifest, + configuredWebSearchProviderIds: params.configuredWebSearchProviderIds, + }) + ) { + return false; + } + if (!params.pluginsConfig.enabled || !params.activationSource.plugins.enabled) { + return false; + } + if ( + params.pluginsConfig.deny.includes(params.plugin.pluginId) || + params.activationSource.plugins.deny.includes(params.plugin.pluginId) + ) { + return false; + } + if ( + params.pluginsConfig.entries[params.plugin.pluginId]?.enabled === false || + params.activationSource.plugins.entries[params.plugin.pluginId]?.enabled === false + ) { + return false; + } + const activationState = resolveEffectivePluginActivationState({ + id: params.plugin.pluginId, + origin: params.plugin.origin, + config: params.pluginsConfig, + rootConfig: params.config, + enabledByDefault: isPluginEnabledByDefaultForPlatform(params.plugin, params.platform), + activationSource: params.activationSource, + }); + return activationState.enabled; +} + function canStartConfiguredRootPlugin(params: { plugin: InstalledPluginIndexRecord; manifest: PluginManifestRecord | undefined; @@ -693,6 +761,8 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: { const startupDreamingPluginIds = resolveGatewayStartupDreamingPluginIds(params.config); const manifestLookup = createManifestRegistryLookup(params.manifestRegistry); const configuredSpeechProviderIds = collectConfiguredSpeechProviderIds(activationSourceConfig); + const configuredWebSearchProviderIds = + collectConfiguredWebSearchProviderIds(activationSourceConfig); const configuredGenerationProviderIds = collectConfiguredGenerationProviderIds(activationSourceConfig); const normalizePluginId = createPluginRegistryIdNormalizer(params.index, { @@ -763,6 +833,19 @@ export function resolveGatewayStartupPluginPlanFromRegistry(params: { ) { return true; } + if ( + canStartConfiguredWebSearchProviderPlugin({ + plugin, + manifest, + config: params.config, + pluginsConfig, + activationSource, + configuredWebSearchProviderIds, + platform: params.platform, + }) + ) { + return true; + } if ( canStartConfiguredGenerationProviderPlugin({ plugin,