Modified 9Router's callback page to call the exchange endpoint directly
instead of relying on postMessage/BroadcastChannel relay (which fails
in Electron where window.opener is null).
Flow: click Connect → sign in → callback page fetches pending state
from our backend → calls 9Router exchange → connection saved → done.
Backend:
- Added GET /api/subscriptions/pending/{state} with CORS for callback
- Added GET /api/subscriptions/callback as fallback
- Store pending OAuth state (codeVerifier, redirectUri) keyed by state
- redirect_uri stays on localhost:20128 (required by Anthropic)
Frontend:
- Popup window for auth URL + postMessage listener + status polling
- Works in both Electron and regular browser
Shows when no API keys configured AND no 9Router subscriptions connected:
- "Welcome to OpenSwarm — Connect an AI model to get started"
- Subscription buttons: Claude, ChatGPT, GitHub Copilot, Gemini
(click → OAuth flow directly, no 9Router GUI)
- "I have an API key" → dismisses modal, user goes to Settings
- "Skip for now" → dismisses modal
- Auto-checks 9Router status on mount
- Dismissed state persists for session (doesn't nag)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: Our code sent http://localhost:20128/api/oauth/claude/callback
as redirect_uri, but Anthropic only accepts http://localhost:PORT/callback.
Fix: For authorization_code providers (Claude, Codex, Gemini), open
9Router's own dashboard page instead of trying to proxy the OAuth flow.
9Router handles the full popup + callback internally.
Flow: Click Connect → opens 9Router dashboard → user clicks Connect there →
OAuth popup → callback handled → poll from OpenSwarm until connected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MCP fix:
- Reverted to parallel eager MCP connection (not lazy)
- Lazy schemas had empty input_schema ({}) causing models to send
wrong/empty arguments → MCP servers returned empty responses
- Now connects all MCP servers in parallel with 10s timeout each
- Gets real tool schemas from servers so models know exact parameters
System prompt:
- Updated saved settings.json to use new MCP-first default prompt
- Old prompt ("ask follow ups", "prioritize browser") was still saved
from before the code change — only affected new installs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixed:
- validate_credentials now handles display names (Anthropic, OpenAI, Google)
not just lowercase — was causing "Provider not found" errors
- OpenRouter-backed providers (xAI, Meta, DeepSeek, Mistral, Qwen, Cohere)
now fall back to 9Router when no OpenRouter API key is set
- OpenAICompatProvider no longer crashes on empty API key
- Clear error messages per provider type when nothing is configured
Credential resolution order (per provider):
1. Direct API key → use native provider
2. 9Router running → route through 9Router
3. Neither → clear error with instructions
All 9 providers tested: validate + create_provider pass with 9Router,
correctly blocked with descriptive errors when 9Router is down + no keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Headless browser (Playwright):
- New: headless_browser.py — full Playwright-based browser implementation
- Supports: navigate, screenshot, get_text, click, type, evaluate, get_elements
- Auto-fallback: execute_browser_tool tries Electron webview first, falls back to
headless Playwright when "not found" error detected
- Works everywhere: web browser, Electron app, server, CI
- Pages managed per browser_id, cleaned up on session close
WebSearch fix:
- Changed User-Agent from bot identifier to Chrome UA
- DDG serves different HTML to bots vs browsers, causing parse failures
- Now returns proper results (tested: Eater SF, Infatuation, etc.)
Requirements:
- Added playwright to requirements.txt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: two WebSocket connections open to the same session
(dashboard card + expanded chat). Every event delivered twice.
Fix: track seen stream IDs and message IDs in WebSocketManager.
- stream_start: skip if message_id already seen
- stream_delta: only accept for streams WE initiated
- stream_end: dedup + clean up tracking
- agent:message: skip if message id already processed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Old prompt: "ask as many follow ups as needed" + "prioritize browser"
New prompt:
- MCP tools first, WebSearch second, browser last
- Do not narrate routine tool calls — just call the tool
- Be direct — make reasonable assumptions, don't over-ask
- Modeled after OpenClaw's tool call style
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Performance fix:
- MCP servers no longer spawned upfront for every message
- Tool schemas loaded from cached metadata (instant, no subprocess)
- MCP servers only connected when their tools are first called (lazy)
- Browser agent MCP server still connects upfront (lightweight)
- Reduces startup from 30+ seconds to <1 second for simple messages
Browser agent 9Router support:
- Browser agent now works through 9Router when no API key set
- Uses OpenAI client format when base_url provided
- Model ID mapping for 9Router (cc/ prefix)
- Passes auth_token and base_url through run_browser_agents
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>