# Agent Tools Implementation Plan ## Overview Make sovereign_browser agentically capable by embedding a WebSocket server in the browser process. External AI agents (LLM tools, scripts, other processes) connect to `ws://localhost:PORT` and send JSON tool commands to navigate, inspect, and interact with web pages. The design is inspired by [agent-browser](https://github.com/vercel-labs/agent-browser), which uses a **snapshot + ref** workflow: take a snapshot (accessibility tree with element refs), then interact by ref (click `@e2`, fill `@e3`). This is the optimal pattern for LLM-driven browser automation. ## Architecture ```mermaid flowchart TB subgraph AgentProcess Agent[AI Agent / Script / CLI] end subgraph BrowserProcess WSServer[WebSocket Server — SoupServer] ToolDispatch[Tool Dispatcher] Snapshot[Snapshot Engine] Tools[Tool Implementations] TabMgr[Tab Manager] end subgraph WebKitGTK Webview[WebKitWebView — active tab] JSEval[webkit_web_view_evaluate_javascript] end Agent -->|ws://localhost:PORT JSON| WSServer WSServer --> ToolDispatch ToolDispatch --> Tools Tools --> Snapshot Tools --> TabMgr Tools -->|JS eval| JSEval JSEval --> Webview Snapshot -->|inject + extract| JSEval TabMgr --> Webview ``` ### Key design decisions 0. **Agent tools must use the same code path as the GUI** — Agent tools should call the same C functions the GUI calls, not a parallel implementation. This ensures that any error a user would see, the agent also sees. Debugging WebKit internals is not our concern; our C code is. If the agent and GUI take different paths, bugs can hide from the agent. The login tools are the one exception (backend-only, bypassing the GTK dialog) — all browser tools go through the same `WebKitWebView` and `tab_manager` APIs. 1. **WebSocket server via libsoup** — `SoupServer` with `soup_server_add_websocket_handler()` provides a production-ready WebSocket server. libsoup is already linked (WebKitGTK dependency). No new dependencies, no hand-rolling the WebSocket protocol. 2. **JSON protocol via cJSON** — cJSON is already vendored in `nostr_core_lib/cjson/`. All tool requests and responses are JSON. 3. **Snapshot + ref pattern** — The core workflow: - `snapshot` injects a JS script that walks the accessibility tree, assigns sequential refs (`e1`, `e2`, ...) to interactive elements, and returns a text representation of the tree. - `click @e2` looks up the ref in a per-tab ref map, finds the DOM element, and clicks it via `element.click()`. - Refs are per-tab and persist until the next `snapshot` call. 4. **Per-tab ref storage** — Each `tab_info_t` gets a ref map (hash table from ref string → element selector or DOM node reference). Since JS execution is per-webview, refs are scoped to the tab's webview. 5. **Async JS execution** — `webkit_web_view_evaluate_javascript()` is async. For tools that need the return value (snapshot, get_text, eval), we use a callback-based approach: the tool injects JS that stores the result in a known global variable, then reads it back. Alternatively, we can use a Promise-based approach with a polling mechanism. 6. **Simple HTTP fallback** — The WebSocket server also handles plain HTTP GET requests to `http://localhost:PORT/` returning a status JSON (server info, connected clients, tab count). This makes it easy to discover the server with `curl` without a WebSocket client. ## WebSocket protocol ### Request format ```json { "id": 1, "tool": "snapshot", "params": { "interactive": true, "compact": true } } ``` ### Response format ```json { "id": 1, "success": true, "data": { "snapshot": "- heading \"Example Domain\" [ref=e1] [level=1]\n- link \"More information...\" [ref=e2]", "refs": { "e1": {"role": "heading", "name": "Example Domain", "level": 1}, "e2": {"role": "link", "name": "More information...", "href": "https://example.com"} } } } ``` ### Error response ```json { "id": 1, "success": false, "error": { "code": "ELEMENT_NOT_FOUND", "message": "No element with ref @e99" } } ``` ### Push events (server → client) ```json { "type": "event", "event": "load", "data": {"url": "https://example.com", "title": "Example Domain", "tab": 0} } ``` ```json { "type": "event", "event": "tab_changed", "data": {"tab": 1, "url": "https://example.com", "title": "Example"} } ``` ## Tool catalog — full comparison with agent-browser The table below lists every agent-browser CLI command and whether we plan to implement it. "Phase 1" tools are in the initial implementation; "Phase 2" tools are deferred but designed for; "N/A" tools don't apply to our architecture (CDP-specific, cloud providers, etc.). ### Core commands | agent-browser command | Our tool | Phase | Notes | |------------------------|----------|-------|-------| | `open ` | `open` | 1 | Navigate active tab | | `click ` | `click` | 1 | By ref or CSS selector | | `dblclick ` | `dblclick` | 2 | Double-click | | `focus ` | `focus` | 1 | Focus element | | `type ` | `type` | 1 | Type into element | | `fill ` | `fill` | 1 | Clear and fill | | `press ` | `press` | 1 | Press keyboard key | | `keyboard type ` | `keyboard_type` | 2 | Type with real keystrokes | | `keyboard inserttext ` | `insert_text` | 2 | Insert text without key events | | `keydown ` | `keydown` | 2 | Hold key down | | `keyup ` | `keyup` | 2 | Release key | | `hover ` | `hover` | 1 | Hover element | | `select ` | `select` | 2 | Select dropdown option | | `check ` | `check` | 2 | Check checkbox | | `uncheck ` | `uncheck` | 2 | Uncheck checkbox | | `scroll [px]` | `scroll` | 1 | Scroll page | | `scrollintoview ` | `scroll_into_view` | 2 | Scroll element into view | | `drag ` | `drag` | 2 | Drag and drop | | `upload ` | `upload` | 2 | Upload files | | `screenshot [path]` | `screenshot` | 1 | Take screenshot | | `screenshot --annotate` | `screenshot_annotated` | 2 | Annotated with element labels | | `pdf ` | `pdf` | 2 | Save as PDF | | `snapshot` | `snapshot` | 1 | Accessibility tree with refs | | `eval ` | `eval` | 1 | Run JavaScript | | `connect ` | N/A | — | CDP-specific, we are the server | | `stream enable` | `stream_enable` | 2 | WebSocket viewport streaming | | `stream status` | `stream_status` | 2 | Streaming state | | `stream disable` | `stream_disable` | 2 | Stop streaming | | `close` | `close` | 1 | Close active tab | | `close --all` | `close_all` | 2 | Close all tabs | ### Get info commands | agent-browser command | Our tool | Phase | Notes | |------------------------|----------|-------|-------| | `get text ` | `get_text` | 1 | Get text content | | `get html ` | `get_html` | 1 | Get innerHTML | | `get value ` | `get_value` | 2 | Get input value | | `get attr ` | `get_attr` | 1 | Get attribute | | `get title` | `get_title` | 1 | Get page title | | `get url` | `get_url` | 1 | Get current URL | | `get cdp-url` | N/A | — | CDP-specific | | `get count ` | `get_count` | 2 | Count matching elements | | `get box ` | `get_box` | 2 | Get bounding box | | `get styles ` | `get_styles` | 2 | Get computed styles | ### Check state commands | agent-browser command | Our tool | Phase | Notes | |------------------------|----------|-------|-------| | `is visible ` | `is_visible` | 2 | Check if visible | | `is enabled ` | `is_enabled` | 2 | Check if enabled | | `is checked ` | `is_checked` | 2 | Check if checked | ### Find elements (semantic locators) | agent-browser command | Our tool | Phase | Notes | |------------------------|----------|-------|-------| | `find role ...` | `find_role` | 2 | By ARIA role | | `find text ...` | `find_text` | 2 | By text content | | `find label