Skip to content

Web: derive search provider metadata from plugin contracts#50935

Merged
gumadeiras merged 8 commits intoopenclaw:mainfrom
gumadeiras:codex/web-search-provider-ownership
Mar 20, 2026
Merged

Web: derive search provider metadata from plugin contracts#50935
gumadeiras merged 8 commits intoopenclaw:mainfrom
gumadeiras:codex/web-search-provider-ownership

Conversation

@gumadeiras
Copy link
Copy Markdown
Member

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: onboarding and configure resolved web-search provider behavior from enabled-provider state and from duplicated core metadata, so disabled plugin-backed providers could lose their plugin-owned credential hooks and bundled metadata could drift from the actual plugin contracts.
  • Why it matters: selecting a disabled bundled provider like Firecrawl could appear to work while leaving the plugin disabled or failing to persist the credential on the provider-owned config surface; the duplication also made it easy to miss newly added bundled providers like Tavily during refactors.
  • What changed: onboarding/configure now use the real PluginWebSearchProviderEntry contract, the shared bundled web-search registry is the single source of truth for bundled fast-path metadata, the agent web_search tool uses the shared runtime resolver, and the shared bundled registry now includes Tavily from main.
  • What did NOT change (scope boundary): no config schema, CLI flags, or plugin SDK contracts changed; this PR does not alter Tavily’s upstream implementation beyond consuming its existing registration through the shared registry.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #

User-visible / Behavior Changes

  • Disabled bundled web-search providers now re-enable correctly during onboarding/configure when selected again.
  • Provider-owned configured credentials are now detected and persisted consistently for onboarding/configure flows.
  • Bundled-provider discovery and fast-path web-search metadata now automatically include Tavily because they derive from shared plugin registrations instead of a duplicated descriptor list.

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) Yes
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:
    This changes where existing web-search credentials are read/written during onboarding/configure, but it narrows behavior onto existing provider contracts instead of adding a new credential surface. Validation covers provider-owned config persistence and disabled-provider re-enable flows.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 25 / Bun 1.3.9 local dev checkout
  • Model/provider: N/A
  • Integration/channel (if any): N/A
  • Relevant config (redacted): disabled bundled Firecrawl/Tavily plugin configs with provider-owned plugins.entries.<id>.config.webSearch.apiKey

Steps

  1. Start with a config where a bundled web-search provider plugin is disabled but already has a provider-owned configured key.
  2. Run onboarding or configure and select that provider again, or rely on quickstart auto-detection.
  3. Inspect tools.web.search, the plugin enable bit, and the provider-owned plugin config after the flow.

Expected

  • The provider is selectable even from a disabled state, the plugin is re-enabled if its contract requires it, and the credential is preserved on the provider-owned config surface.
  • The bundled-provider registry and fast-path web-search metadata include the same bundled providers as the actual plugin registrations, including Tavily.

Actual

  • Before this PR, disabled plugin-backed providers could lose provider-owned behavior during onboarding/configure, and bundled fast-path metadata could drift from the actual registration list.
  • After this PR, those flows resolve against the shared provider contract and bundled registration list.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: pnpm tsgo; OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test -- src/plugins/bundled-web-search.test.ts src/plugins/web-search-providers.test.ts src/config/config.web-search-provider.test.ts; OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test -- src/agents/tools/web-tools.enabled-defaults.test.ts; OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test -- src/commands/configure.wizard.test.ts; direct bun run - exercise of disabled-Firecrawl interactive + quickstart onboarding paths and current SEARCH_PROVIDER_OPTIONS membership including Tavily.
  • Edge cases checked: disabled plugin with existing provider-owned credential; interactive onboarding write path; quickstart existing-key detection; Tavily membership in bundled provider options after rebasing onto current main.
  • What you did not verify: src/commands/onboard-search.test.ts still hangs locally in the repo’s existing Vitest harness pattern and did not produce a clean completion signal on this machine.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR or set the desired web-search provider explicitly and reconfigure the provider key.
  • Files/config to restore: src/commands/onboard-search.ts, src/commands/configure.wizard.ts, src/plugins/bundled-web-search.ts, src/plugins/contracts/registry.ts
  • Known bad symptoms reviewers should watch for: onboarding/configure no longer seeing existing provider-owned keys, bundled provider list missing Tavily, or web_search selecting a different provider than runtime resolution.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: the shared bundled registration export could diverge from future registry use if a new bundled provider is added but not inserted into bundledWebSearchPluginRegistrations.
    • Mitigation: the fast path now derives from the shared registration list instead of a second descriptor table, reducing the duplication to one explicit list and keeping provider metadata contract-owned.
  • Risk: local onboarding test harness instability can mask unrelated regressions in src/commands/onboard-search.test.ts.
    • Mitigation: direct Bun-level exercises validated the exact changed onboarding paths, and the adjacent configure/provider/runtime tests passed on the rebased branch.

Copilot AI review requested due to automatic review settings March 20, 2026 08:24
@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations agents Agent runtime and tooling size: L maintainer Maintainer-authored PR labels Mar 20, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 20, 2026

Greptile Summary

This PR eliminates the duplicate SearchProviderEntry/BundledWebSearchProviderDescriptor tables and makes onboarding, configure, and the agent web_search tool all derive their provider metadata from the real PluginWebSearchProviderEntry plugin contracts. The key behavioural fixes are: (1) applySearchKey now writes the credential to both the top-level search config and the plugin-owned config surface, resolving the case where firecrawl/tavily-like providers could silently lose the provider-owned credential during configure; (2) the applyProviderOnlyapplySearchProviderSelection export ensures the plugin re-enable side effect is applied consistently when a key is already present; and (3) Tavily is automatically included in bundled-provider lists without a separate descriptor entry.

  • Architecture: The ~240-line hand-maintained descriptor table in bundled-web-search.ts is replaced by a thin cache over capturePluginRegistration calls driven by the shared bundledWebSearchPluginRegistrations constant — a clear reduction in duplication.
  • Renamed surface: SearchProviderEntry.value/.envKeysPluginWebSearchProviderEntry.id/.envVars; all call sites updated consistently across onboard-search.ts, configure.wizard.ts, and setup.finalize.ts.
  • Test coverage: New scenarios cover disabled-plugin re-enable, provider-owned credential persistence, and quickstart auto-detection with a disabled plugin — the exact paths described in the PR's repro steps.
  • Minor concern: bundledWebSearchPluginRegistrations in registry.ts is exported as a mutable Array<...> rather than ReadonlyArray<...>. Since it is now the single source of truth for the bundled web-search registry, adding a ReadonlyArray type would prevent accidental mutations from future callers.

Confidence Score: 4/5

  • Safe to merge — the refactor is well-scoped, tested, and backward compatible; the one open item (mutable export typing) is stylistic.
  • The PR is a focused refactor with clear test coverage for the exact bug scenarios described. All renamed fields are updated consistently across the codebase, the credential write path change is intentional and verified by new tests, and no config schema or plugin SDK contracts are modified. The only remaining rough edge is the bundledWebSearchPluginRegistrations export being mutable rather than ReadonlyArray, which is a style/safety concern but not a correctness risk given current consumers.
  • src/plugins/contracts/registry.ts — the new bundledWebSearchPluginRegistrations export should be ReadonlyArray to protect the source-of-truth list from accidental mutation.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/plugins/contracts/registry.ts
Line: 82-93

Comment:
**Exported mutable array for source-of-truth registry**

`bundledWebSearchPluginRegistrations` is now exported as a plain mutable `Array<...>`. Since it becomes the single source of truth for bundled web-search providers (consumed by both `bundled-web-search.ts` and the internal `bundledWebSearchPlugins` derivation), any caller that accidentally calls `.push()`, `.splice()`, or mutates an element would silently corrupt the shared state.

Consider typing it as `ReadonlyArray` to make the immutability contract explicit:

```suggestion
export const bundledWebSearchPluginRegistrations: ReadonlyArray<{
  plugin: RegistrablePlugin;
  credentialValue: unknown;
}> = [
  { plugin: bravePlugin, credentialValue: "BSA-test" },
  { plugin: firecrawlPlugin, credentialValue: "fc-test" },
  { plugin: googlePlugin, credentialValue: "AIza-test" },
  { plugin: moonshotPlugin, credentialValue: "sk-test" },
  { plugin: perplexityPlugin, credentialValue: "pplx-test" },
  { plugin: tavilyPlugin, credentialValue: "tvly-test" },
  { plugin: xaiPlugin, credentialValue: "xai-test" },
];
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Web: derive search p..."

Comment thread src/plugins/contracts/registry.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors web-search onboarding/configure/runtime paths to derive provider metadata directly from plugin web-search provider contracts and a shared bundled registry, reducing drift and fixing issues when selecting disabled bundled providers.

Changes:

  • Switch onboarding/configure flows to consume PluginWebSearchProviderEntry directly (instead of duplicating provider metadata).
  • Introduce a shared bundledWebSearchPluginRegistrations export and rebuild bundled provider fast-path metadata from captured plugin registrations (including Tavily).
  • Align the web_search tool’s provider resolution to the shared runtime resolver.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/wizard/setup.finalize.ts Updates finalize-step messaging to use provider id/envVars fields from the shared provider contract shape.
src/plugins/contracts/registry.ts Exports bundledWebSearchPluginRegistrations as a shared source of truth for bundled web-search plugin registrations.
src/plugins/bundled-web-search.ts Replaces duplicated bundled provider descriptors with provider entries derived from captured plugin registrations.
src/commands/onboard-search.ts Refactors search onboarding to use PluginWebSearchProviderEntry and new selection helpers.
src/commands/onboard-search.test.ts Adds coverage for disabled bundled-provider re-enable + persistence behavior and updates expectations for id-based options.
src/commands/configure.wizard.ts Updates configure wizard web-search flow to use the new id contract + apply provider-selection side effects.
src/commands/configure.wizard.test.ts Adds mocks + tests asserting provider-owned config returned from apply functions is persisted.
src/agents/tools/web-search.ts Adjusts internal testing helper typing to match the shared runtime resolver signature.
Comments suppressed due to low confidence (1)

src/commands/onboard-search.ts:42

  • resolveSearchProviderEntry() only searches SEARCH_PROVIDER_OPTIONS, which is built without a config/workspaceDir and therefore only reflects the default bundled provider set. As a result, rawKeyValue()/hasExistingKey()/applySearchKey() can fail for non-bundled or config-dependent web search providers (e.g., providers coming from plugins.installs/plugins.load.paths), causing existing keys to be missed and new keys not to be written via the provider’s setters.

Suggestion: when an entry isn’t found in SEARCH_PROVIDER_OPTIONS, fall back to resolving providers with resolvePluginWebSearchProviders({ config, workspaceDir, env, bundledAllowlistCompat: true, ... }) using the current config (or otherwise accept config in resolveSearchProviderEntry). This preserves support for custom providers while keeping the bundled fast-path list for the common case.

function resolveSearchProviderEntry(
  provider: SearchProvider,
): PluginWebSearchProviderEntry | undefined {
  return SEARCH_PROVIDER_OPTIONS.find((entry) => entry.id === provider);
}

export function hasKeyInEnv(entry: Pick<PluginWebSearchProviderEntry, "envVars">): boolean {
  return entry.envVars.some((k) => Boolean(process.env[k]?.trim()));
}

function rawKeyValue(config: OpenClawConfig, provider: SearchProvider): unknown {
  const search = config.tools?.web?.search;
  const entry = resolveSearchProviderEntry(provider);
  return (
    entry?.getConfiguredCredentialValue?.(config) ??
    entry?.getCredentialValue(search as Record<string, unknown> | undefined)
  );

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8180cf32e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/onboard-search.ts Outdated
@gumadeiras gumadeiras force-pushed the codex/web-search-provider-ownership branch from b8180cf to c3c3569 Compare March 20, 2026 16:54
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d575f1085

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/onboard-search.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb06375a1b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/configure.wizard.ts Outdated
Comment thread src/wizard/setup.finalize.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 603d16faed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/wizard/setup.finalize.ts Outdated
@@ -483,23 +484,36 @@ export async function finalizeSetupWizard(

const webSearchProvider = nextConfig.tools?.web?.search?.provider;
const webSearchEnabled = nextConfig.tools?.web?.search?.enabled;
const runtimeSearchProviders = listWebSearchProviders({ config: nextConfig });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve final wizard provider notes from nextConfig

When the setup wizard is run through the Gateway/Control UI, this process already has an active plugin registry from loadOpenClawPlugins in src/gateway/server-plugins.ts:408-422. listWebSearchProviders then delegates to resolveRuntimeWebSearchProviders, which ignores the passed config whenever getActivePluginRegistry().webSearchProviders is non-empty (src/plugins/web-search-providers.runtime.ts:46-52). As a result, a provider that this wizard just enabled in nextConfig can still be missing here, so the summary reports it as unavailable and skips key detection until after a reload/restart. This summary path needs a config-snapshot lookup, not the already-active runtime registry.

Useful? React with 👍 / 👎.

@gumadeiras gumadeiras self-assigned this Mar 20, 2026
@gumadeiras gumadeiras force-pushed the codex/web-search-provider-ownership branch 2 times, most recently from cfb3bdd to e1ef201 Compare March 20, 2026 19:27
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1ef201bd5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +230 to +233
nextSearch = {
...existingSearch,
enabled: false,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve existing web_search state when no providers are available

When resolveSearchProviderOptions(nextConfig) is empty (for example plugins.enabled=false or every provider is denied), this branch rewrites tools.web.search.enabled to false and the wizard later persists that change. In that configuration, running openclaw configure --section web just to inspect settings or change web_fetch silently turns off an otherwise valid search setup, so once the plugin policy is relaxed the stored provider/key still exists but web_search remains disabled until the user notices and re-enables it manually.

Useful? React with 👍 / 👎.

@gumadeiras gumadeiras force-pushed the codex/web-search-provider-ownership branch from 60be8a7 to e1c7d72 Compare March 20, 2026 19:40
@gumadeiras gumadeiras merged commit 3da6671 into openclaw:main Mar 20, 2026
29 of 35 checks passed
frankekn pushed a commit to artwalker/openclaw that referenced this pull request Mar 23, 2026
…50935)

Merged via squash.

Prepared head SHA: e1c7d72
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
furaul pushed a commit to furaul/openclaw that referenced this pull request Mar 24, 2026
…50935)

Merged via squash.

Prepared head SHA: e1c7d72
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…50935)

Merged via squash.

Prepared head SHA: e1c7d72
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…50935)

Merged via squash.

Prepared head SHA: e1c7d72
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations extensions: tavily maintainer Maintainer-authored PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants