Browser Extension Architecture
How the Chrome Extension communicates with the Python CLI for browser automation
Browser Extension Architecture
The PraisonAI browser automation uses a 3-layer architecture for AI-powered browser control.
Communication Flow
Python CLI ──► Bridge Server ◄──► Chrome Extension
│ │ │
│ │ │
start_session observation/action CDP- CLI sends
start_sessionwith goal - Bridge Server routes to Extension
- Extension captures page state (screenshot, elements)
- Extension sends
observationto Bridge - Bridge processes with BrowserAgent (LLM decision)
- Bridge returns
actionto Extension - Extension executes action via CDP
- Loop continues until goal complete
Architecture Diagram
┌─────────────────────────────────────────────────────────────┐
│ Python CLI │
│ praisonai browser launch "goal" │
└─────────────────────┬───────────────────────────────────────┘
│ WebSocket
▼
┌─────────────────────────────────────────────────────────────┐
│ Bridge Server (FastAPI) │
│ - Routes messages CLI ↔ Extension │
│ - Hosts BrowserAgent for LLM decisions │
│ - WebSocket endpoint: ws://localhost:8765/ws │
└─────────────────────┬───────────────────────────────────────┘
│ WebSocket
▼
┌─────────────────────────────────────────────────────────────┐
│ Chrome Extension │
│ - Background service worker │
│ - CDP debugger for page control │
│ - Captures screenshots, clickable elements │
│ - Executes click/type/scroll actions │
└─────────────────────────────────────────────────────────────┘Running the Browser Agent
# Simple usage (auto-selects engine)
praisonai browser launch "go to google and search for AI"
# Force extension mode
praisonai browser launch "go to google" --engine extension
# Force CDP mode (no extension required)
praisonai browser launch "go to google" --engine cdp
# Debug mode
praisonai browser launch "test" --debugMessage Types
| Type | Direction | Description |
|---|---|---|
start_session | CLI → Server | Start new automation |
observation | Ext → Server | Page state snapshot |
action | Server → Ext | Next action to execute |
status | Server → Both | Session status updates |
stop_session | Any → Server | End session |
cancel_session | Any → Server | Cancel an in-flight session (routes through the same handler as stop_session). |
register | Ext/CLI → Server | Declares the socket’s role (extension or cli) for sockets that connected without an Origin header. Returns {"type": "status", "status": "registered", "role": "..."}, or {"type": "error", "code": "INVALID_ROLE"} if rejected. See Role Registration. |
Extension Readiness Wait
Before sending start_session, run_browser_agent_with_progress polls the bridge /health endpoint until an extension connects.
The poll caps at min(15.0, timeout) seconds — a caller passing timeout=2.0 waits at most 2 seconds. Set PRAISONAI_BROWSER_SKIP_EXTENSION_WAIT=1 to skip the poll entirely (for unit tests / mocked bridges). See Environment Variables.
Role Registration
Sockets that connect without an Origin header declare their role with a register message.
Chrome MV3 offscreen documents often omit Origin on ws://127.0.0.1:8765/ws. The bridge classifies such sockets as CLI, so without register the extension pool stays empty and start_session returns NO_EXTENSION.
Send it once, on open:
ws.addEventListener("open", () => {
ws.send(JSON.stringify({ type: "register", client: "extension" }));
});| Rule | Behavior |
|---|---|
client: "extension" | Accepted when the socket connected without Origin (or is already the extension). |
client: "cli" | Accepted when the socket connected without Origin and has no active session. |
| Web-origin socket | Rejected with INVALID_ROLE — it cannot claim either role. |
Unregistered, no Origin | Stays CLI. This default blocks extension hijacking (advisory GHSA-8x8f-54wf-vv92). |
Success response:
{"type": "status", "status": "registered", "role": "extension"}Rejection response:
{"type": "error", "code": "INVALID_ROLE", "error": "Cannot register as client: extension"}Troubleshooting
Extension Not Connecting
If you see “Extension did not connect after 15s”:
- Check extension console:
- Go to
chrome://extensions/ - Find “PraisonAI Browser Agent”
- Click “service worker” link
- Look for errors
- Kill stale Chrome processes:
pkill -f "Google Chrome"- Rebuild extension:
cd /path/to/praisonai-chrome-extension
npm run build- Check bridge server:
curl http://localhost:8765/healthExpected output:
{
"status": "ok",
"connections": 2,
"extension_connections": 1,
"sessions": 0,
"extension_busy": false,
"active_session_id": null
}connections counts all WebSocket clients (extension, CLI, curl’d probes). Check extension_connections >= 1 instead — it counts only clients whose Origin starts with chrome-extension://, so a stray CLI probe won’t make it look connected. connections >= 1 alone is no longer sufficient.
extension_busy and active_session_id (added in PraisonAI #3095 hardening) tell you whether the extension is currently owned by a session — a healthy-and-idle bridge shows extension_busy: false. praisonai browser run fails fast with an “Extension already running a session” error rather than hanging when this is true. Servers older than that commit omit both fields; treat missing as false / null.
start_session returns NO_EXTENSION even though the WebSocket is open
The offscreen document has not registered. Add this to its onopen handler:
ws.addEventListener("open", () => {
ws.send(JSON.stringify({ type: "register", client: "extension" }));
});Expect the server log line [SERVER][REGISTER] Connection registered as extension. Once the extension has sent register, /health (extension_connections) and session routing agree. If /health shows extension_connections: 1 but start_session still returns NO_EXTENSION, check that the start_session is sent after the registered status and from the expected socket.
No Observations Sent
If session starts but times out:
- Check extension console for
[PraisonAI] onStartAutomation FATAL ERROR - CDP debugger may fail to attach
- Page may be a chrome:// URL (unsupported)
Debug Mode
praisonai browser launch "test" --debugThis enables verbose logging and saves screenshots to ~/.praisonai/browser_screenshots/.
Performance Profiling
Track timing breakdown for each automation step:
# Basic profiling with timing summary
praisonai browser launch "search for AI" --engine cdp --profile
# Deep profiling with cProfile trace
praisonai browser launch "search for AI" --engine cdp --deep-profileSample Output
📊 Performance Profile
──────────────────────────────────────────────────────────────────────
Total Time: 24.2s | Steps: 3 | Avg: 8.1s/step
Step | LLM | Screen | Action | Verify | Stable | Total
0 | 2.4s | 0.0s | 1.0s | 0.0s | 0.0s | 3.5s
1 | 2.2s | 0.0s | 0.2s | 0.0s | 0.0s | 2.5s
Bottlenecks: LLM 51% | Verify 0% | Stable 0%Timing Breakdown
| Metric | Description |
|---|---|
| LLM | Time spent waiting for LLM decision |
| Screen | Screenshot capture time |
| Action | CDP action execution time |
| Verify | Action verification time |
| Stable | Page stability wait time |