96c2fd3c04
With zero web credentials configured, web_search/web_extract previously resolved to the nonfunctional firecrawl sentinel and errored. Now the backend resolution walks a strictly-last keyless tier: Parallel's and Exa's public anonymous MCP endpoints (the same free tiers opencode ships as its default search path). - plugins/web/keyless_mcp.py: minimal JSON-RPC tools/call client for mcp.exa.ai + search.parallel.ai (SSE + plain JSON parsing, typed errors, per-process random session id, no user identifiers) - WebSearchProvider.is_keyless_available(): separate weaker tier that never leaks into is_available(), so keyed setups are never pre-empted - Exa/Parallel providers: route to keyless endpoints when their key is absent; keyed SDK path unchanged - registry + _get_backend(): keyless walk (parallel -> exa) strictly after every keyed/importable candidate; check_web_api_key() lights the tools up on zero-credential installs - web.keyless_fallback config key (default true) to disable the tier - docs: web-search.md + configuration.md E2E-verified against both live endpoints from an isolated HERMES_HOME (search + extract via the real dispatchers, disable-flag negative path).