Symptom
The first-class assistant web_search tool fails with:
web_search is disabled or no provider is available
…even though the same machine resolves the configured Brave plugin successfully via:
openclaw capability web search
- Direct runtime provider execution (the gateway's web search runtime path)
Affected Surface
src/agents/tools/web-search.ts — createWebSearchTool with lateBindRuntimeConfig: true
src/plugins/web-provider-runtime-shared.ts — resolvePluginWebProviders / resolveRuntimeWebProviders
The bug only reproduces when the assistant tool is built with lateBindRuntimeConfig: true from src/agents/openclaw-tools.ts and the agent context does not share the gateway's in-process runtime snapshot (e.g. embedded subagents, scoped runtime plugin loads).
Probable Cause
Two interacting regressions:
-
The active gateway plugin registry is intentionally scoped to channels, memory, harnesses, and sidecars on startup. It can be otherwise compatible with the active OpenClaw config while contributing zero web-provider entries (e.g. when Brave/web providers live in a separately-loaded plugin set). The current resolver treats that empty active-registry result as authoritative and short-circuits to [] instead of falling back to a scoped provider plugin load. First-class tools then see no providers.
-
When the late-bound createWebSearchTool execute lambda runs in an agent context where neither getActiveRuntimeWebToolsMetadata() nor getActiveSecretsRuntimeSnapshot() is populated, the local runtimeWebSearch and config both resolve to undefined. With no provider id and no config, preferRuntimeProviders cannot route to the configured plugin, and the tool ultimately reports "no provider available". The configured provider id from config.tools.web.search.provider is never consulted.
Proposed Fix Scope
Source-only fixes in src/agents/tools/web-search.ts and src/plugins/web-provider-runtime-shared.ts:
- Late-bind config / runtime fallback in
createWebSearchTool:
runtimeWebSearch = getActiveRuntimeWebToolsMetadata()?.search ?? options?.runtimeWebSearch
config = getActiveSecretsRuntimeSnapshot()?.config ?? options?.config
- Derive
configuredProviderId from config.tools.web.search.provider.
- Compose
providerSelectionId = runtimeProviderId || configuredProviderId and use that for preferRuntimeProviders, so an explicit Brave/Perplexity selection still resolves the configured plugin when no runtime provider id is bound.
- Web provider runtime registry fallback:
- When the compatible/runtime active plugin registry maps to zero web providers, fall through to a scoped provider plugin load instead of returning
[].
- Preserve explicit
onlyPluginIds: [] semantics: an explicitly empty scope still returns [].
Targeted regression tests:
src/plugins/web-provider-runtime-shared.test.ts — fall-through under empty active registry, no fall-through under explicit empty scope, both for resolvePluginWebProviders and resolveRuntimeWebProviders.
src/agents/tools/web-search.late-bind.test.ts (new) — ?? fallbacks for runtime metadata and config; configured-provider-only routing; no preferRuntimeProviders when nothing is selected; bundled-manifest owner takes priority over runtime preference.
A PR implementing these fixes will follow.
Symptom
The first-class assistant
web_searchtool fails with:…even though the same machine resolves the configured Brave plugin successfully via:
openclaw capability web searchAffected Surface
src/agents/tools/web-search.ts—createWebSearchToolwithlateBindRuntimeConfig: truesrc/plugins/web-provider-runtime-shared.ts—resolvePluginWebProviders/resolveRuntimeWebProvidersThe bug only reproduces when the assistant tool is built with
lateBindRuntimeConfig: truefromsrc/agents/openclaw-tools.tsand the agent context does not share the gateway's in-process runtime snapshot (e.g. embedded subagents, scoped runtime plugin loads).Probable Cause
Two interacting regressions:
The active gateway plugin registry is intentionally scoped to channels, memory, harnesses, and sidecars on startup. It can be otherwise compatible with the active OpenClaw config while contributing zero web-provider entries (e.g. when Brave/web providers live in a separately-loaded plugin set). The current resolver treats that empty active-registry result as authoritative and short-circuits to
[]instead of falling back to a scoped provider plugin load. First-class tools then see no providers.When the late-bound
createWebSearchToolexecute lambda runs in an agent context where neithergetActiveRuntimeWebToolsMetadata()norgetActiveSecretsRuntimeSnapshot()is populated, the localruntimeWebSearchandconfigboth resolve toundefined. With no provider id and no config,preferRuntimeProviderscannot route to the configured plugin, and the tool ultimately reports "no provider available". The configured provider id fromconfig.tools.web.search.provideris never consulted.Proposed Fix Scope
Source-only fixes in
src/agents/tools/web-search.tsandsrc/plugins/web-provider-runtime-shared.ts:createWebSearchTool:runtimeWebSearch = getActiveRuntimeWebToolsMetadata()?.search ?? options?.runtimeWebSearchconfig = getActiveSecretsRuntimeSnapshot()?.config ?? options?.configconfiguredProviderIdfromconfig.tools.web.search.provider.providerSelectionId = runtimeProviderId || configuredProviderIdand use that forpreferRuntimeProviders, so an explicit Brave/Perplexity selection still resolves the configured plugin when no runtime provider id is bound.[].onlyPluginIds: []semantics: an explicitly empty scope still returns[].Targeted regression tests:
src/plugins/web-provider-runtime-shared.test.ts— fall-through under empty active registry, no fall-through under explicit empty scope, both forresolvePluginWebProvidersandresolveRuntimeWebProviders.src/agents/tools/web-search.late-bind.test.ts(new) —??fallbacks for runtime metadata and config; configured-provider-only routing; nopreferRuntimeProviderswhen nothing is selected; bundled-manifest owner takes priority over runtime preference.A PR implementing these fixes will follow.