From ca87e5d75e50a5904c321913843ec62fd03cf391 Mon Sep 17 00:00:00 2001 From: Josh Mabry Date: Mon, 15 Jun 2026 20:30:28 -0700 Subject: [PATCH] feat: full mode embeds the local dashboard inline (default); clear error when not local (v0.5.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per feedback (the minimal screenshot mode isn't wanted): full mode is the default again and now embeds agent-browser's dashboard INLINE — but correctly, at its own local origin (http://:/), not through the impossible sub-path proxy. On a local setup (console + agent-browser on one machine) you get the real dashboard, feeds and all, in the panel. Verified e2e against the live binary (iframe → 127.0.0.1:4848, dashboard loaded — title + tabs, status "running", no error). "Assume local, error if not": full mode detects whether the dashboard is reachable from the operator's browser — LOCAL = loopback host (localhost/127.0.0.1) AND not fleet-proxied (base=="") AND not https. If not local (a member, a non-loopback host, or an https console where the http dashboard is mixed-content-blocked), it shows a clear, specific error pointing at panel_mode: minimal instead of a blank frame. Local-but-stopped shows a Start prompt; the Start/Stop control is in the bar. minimal mode stays as the remote/everywhere option. 19 tests (full-mode test now asserts the embed + the error path), ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 12 +++++ README.md | 19 +++---- browser_panel.py | 100 +++++++++++++++++++++++------------- protoagent.plugin.yaml | 21 ++++---- pyproject.toml | 2 +- tests/test_agent_browser.py | 19 +++---- 6 files changed, 109 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c594a0c..8c7be1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v0.5.0 +- **`full` is the default again, and it embeds the dashboard inline.** Full mode iframes + agent-browser's dashboard at its own **local origin** (`http://:/`) — so on a local + setup (console + agent-browser on one machine) you get the real dashboard, feeds and all, right + in the panel. (v0.4.0 turned it into a new-tab launcher; this brings the inline embed back, the + right way — at the dashboard's origin, not through the impossible sub-path proxy.) +- **A clear error instead of a blank frame when it can't embed.** If the console is opened remotely + (a fleet member, a non-loopback host, or over https) the dashboard's `localhost` isn't reachable + from your browser — the panel detects that (loopback host + not fleet-proxied + not https) and + says so, pointing you at `panel_mode: minimal` (which still works everywhere). Start/Stop the + dashboard from the panel as before; a not-yet-running dashboard shows a Start prompt. + ## v0.4.0 - **The Browser panel works out of the box now.** The default `panel_mode` is **`minimal`** — a live screenshot + nav toolbar driven through the gated same-origin routes; it works everywhere diff --git a/README.md b/README.md index 6ae2c47..0f1880b 100644 --- a/README.md +++ b/README.md @@ -39,15 +39,16 @@ point. - **Workflows** — declarative browser recipes (browse-and-extract, fill-a-form, …). - **Browser panel** — a console view (ADR 0026) for watching/driving the browser. Two modes, set by `panel_mode`: - - **`minimal` (default)** — a **live screenshot** of the viewport + a nav toolbar + a - **Dashboard control** (start/stop/status), all through the **gated same-origin routes**. - Works everywhere (host and member), no dashboard daemon needed. The reliable mode. - - **`full`** — a **launcher** for agent-browser's own dashboard (viewport + activity/ - console/network feeds). It's not embedded: that dashboard is a Next.js app with - **root-absolute asset paths** (no base-path), so it can't render under a sub-path - panel — it only loads at its **own origin**. Full mode opens it there - ("Open dashboard ↗"), which works on a local/host setup. For a remote member, use - `minimal`. + - **`full` (default)** — **embeds agent-browser's dashboard inline** (viewport + activity/ + console/network feeds) at its own **local origin** (`http://:/`). Best for a + **local setup** (console + agent-browser on one machine). Because the dashboard is a + Next.js app with root-absolute assets (no base-path), it only loads at its own origin — + so when the console is opened **remotely** (a fleet member, a non-loopback host, or over + https) its `localhost` isn't reachable from your browser, and the panel shows a **clear + error** (pointing you at `minimal`) rather than a blank frame. + - **`minimal`** — a **live screenshot** of the viewport + a nav toolbar, all through the + **gated same-origin routes**. Works everywhere (host and member), no dashboard daemon + needed — use it for a remote/member agent. Either mode can **start the dashboard from the panel** (no terminal) — the Start/Stop control hits the gated `POST /api/plugins/agent_browser/dashboard`. diff --git a/browser_panel.py b/browser_panel.py index a22ce94..c1ca1cf 100644 --- a/browser_panel.py +++ b/browser_panel.py @@ -138,71 +138,101 @@ async def _dash_control(body: dict = Body(...)): return router -# ── full mode: a LAUNCHER for agent-browser's dashboard (not an embed) ────────── -# The dashboard is a prebuilt Next.js app whose assets are root-absolute (/_next/…) -# with no base-path option, so it can't render under our sub-path panel — it only -# loads at its OWN origin. So full mode is a launcher: a Start/Stop control + an -# "Open dashboard ↗" link to that origin (reachable on a local/host setup). For a -# remote member, minimal mode is the one that works. +# ── full mode (default): embed agent-browser's dashboard at its OWN origin ────── +# The dashboard is a Next.js app with root-absolute assets (no base-path), so it only +# renders at its own origin (http://:/) — never under a sub-path proxy. Full +# mode therefore ASSUMES A LOCAL setup (console + agent-browser on one machine) and iframes +# that loopback origin directly. When the console is opened remotely (a fleet member, or a +# non-loopback host, or over https) that loopback dashboard isn't reachable from the +# operator's browser — we DETECT that and show a clear error instead of a blank frame. _FULL_PAGE = r""" Browser
Browser
-
-
agent-browser dashboard
-
The full dashboard — live viewport plus activity, console, and network - feeds — runs in its own window. It can't embed in this panel (its assets load from the - page root, which a sub-path panel can't serve), so it opens in a new tab.
- Open dashboard ↗ -
Want the browser inside the console? Set - panel_mode: minimal — a live viewport you can drive right here.
-
+
+ +
+
""" diff --git a/protoagent.plugin.yaml b/protoagent.plugin.yaml index aa64ff6..b4de326 100644 --- a/protoagent.plugin.yaml +++ b/protoagent.plugin.yaml @@ -1,6 +1,6 @@ id: agent_browser name: Agent Browser -version: 0.4.0 +version: 0.5.0 description: >- Browser automation for protoAgent, backed by **agent-browser** (vercel-labs) — a fast native-Rust CLI/daemon that drives Chrome over CDP with accessibility-tree @@ -23,14 +23,15 @@ config: binary: agent-browser # the agent-browser CLI on PATH (override for a pinned path) dashboard_port: 4848 # agent-browser dashboard start --port; full-mode panel iframes this timeout_s: 60 # per-command subprocess timeout - panel_mode: minimal # Browser panel layout. `minimal` (default): viewport-only — a live - # screenshot + a nav toolbar + a Dashboard control, driven through the - # gated same-origin routes. Works everywhere (host + member), no daemon - # needed. `full`: iframe agent-browser's own dashboard (viewport + feeds) - # — only renders when the dashboard is reachable at its OWN origin from - # your browser (a local/host setup); it can't be embedded through a - # sub-path reverse proxy because its Next.js assets are root-absolute. - # Use the panel's "Open ↗" to view the dashboard directly. + panel_mode: full # Browser panel layout. `full` (default): embed agent-browser's own + # dashboard (viewport + activity/console/network feeds) at its local + # origin — best for a LOCAL setup (console + agent-browser on one box). + # When the console is opened remotely (a fleet member, a non-loopback + # host, or over https) the dashboard's localhost isn't reachable from + # your browser, so the panel shows a clear error — use `minimal` there. + # `minimal`: a screenshot viewport + nav toolbar driven through the + # gated same-origin routes; works everywhere, no daemon needed. Both + # modes can start/stop the dashboard from the panel. # Runtime / launch options — a curated set passed to `agent-browser open` so you can # shape and lock down the browser the agent spins up. Blank/0/false = CLI default. headed: false # show a real browser window instead of headless @@ -44,7 +45,7 @@ config: # Editable in Settings ▸ Plugins (ADR 0019) — the operator knobs above as UI fields. settings: - - { key: panel_mode, label: "Browser panel mode", type: select, options: [minimal, full], description: "minimal (recommended): a live screenshot + nav toolbar + Dashboard control, via gated same-origin routes — works everywhere. full: iframe agent-browser's dashboard (viewport + feeds) — only renders when the dashboard is reachable at its own origin from your browser (local/host); it can't embed through a sub-path proxy. Use the panel's Open ↗ either way." } + - { key: panel_mode, label: "Browser panel mode", type: select, options: [full, minimal], description: "full (default): embed agent-browser's dashboard (viewport + activity/console/network feeds) at its local origin — for a local setup (console + agent-browser on one machine). Opened remotely (a fleet member / non-loopback host / https), it shows a clear error instead of a blank frame. minimal: a screenshot viewport + nav toolbar via gated same-origin routes — works everywhere, no daemon. Both can start/stop the dashboard from the panel." } - { key: headed, label: "Headed browser", type: bool, description: "Show a real browser window instead of running headless." } - { key: allowed_domains, label: "Allowed domains", type: string, description: "Comma-separated navigable-domain allowlist (e.g. example.com,*.foo.com). Blank = unrestricted." } - { key: confirm_actions, label: "Confirm actions", type: string, description: "Comma-separated action categories that require confirmation before the agent runs them." } diff --git a/pyproject.toml b/pyproject.toml index d2f5ecc..9e0cb73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agent-browser-plugin" -version = "0.4.0" +version = "0.5.0" description = "Browser-automation plugin for protoAgent, backed by vercel-labs/agent-browser (tools + skill + workflows + a live browser panel)." requires-python = ">=3.11" diff --git a/tests/test_agent_browser.py b/tests/test_agent_browser.py index bfd4ecb..8a23842 100644 --- a/tests/test_agent_browser.py +++ b/tests/test_agent_browser.py @@ -177,7 +177,7 @@ def _app(cfg=None): return app -def test_panel_page_full_mode_is_a_launcher(): +def test_panel_page_full_mode_embeds_local_or_errors(): from fastapi.testclient import TestClient c = TestClient(_app({"panel_mode": "full", "dashboard_port": 4955})) @@ -186,14 +186,15 @@ def test_panel_page_full_mode_is_a_launcher(): html = r.text assert "/_ds/plugin-kit.css" in html # DS kit assert 'location.pathname.split("/plugins/")[0]' in html # slug-aware base - assert "Open dashboard" in html # a launcher to the dashboard's own origin (not an embed) + assert 'id="f"' in html # the dashboard iframe + assert '"http://"+location.hostname+":"+PORT' in html # embed the dashboard's OWN local origin + assert "LOOPBACK" in html # local detection (loopback host + not fleet-proxied) + assert "Open the console locally" in html # the clear error shown when NOT local assert "/api/plugins/agent_browser/dashboard" in html # the start/stop control - assert "panel_mode: minimal" in html # points users at the mode that embeds inline - # full mode no longer embeds a sub-path proxy; the port placeholder is interpolated. + assert "panel_mode: minimal" in html # the remote alternative the error points at + # the dead sub-path proxy is gone; the port placeholder is interpolated. assert "/panel/dash" not in html assert "__DASH_PORT__" not in html and "4955" in html - # the "open" link is built from location.hostname, never a hardcoded origin. - assert "http://localhost" not in html and "http://127.0.0.1" not in html def test_panel_page_minimal_mode_uses_gated_data_routes(): @@ -210,13 +211,13 @@ def test_panel_page_minimal_mode_uses_gated_data_routes(): assert "__DASH_PORT__" not in html and "4933" in html -def test_default_panel_mode_is_minimal(): +def test_default_panel_mode_is_full(): import yaml m = yaml.safe_load((ROOT / "protoagent.plugin.yaml").read_text()) - assert m["config"]["panel_mode"] == "minimal" # the reliable mode is the default now + assert m["config"]["panel_mode"] == "full" # embed the dashboard by default (local setup) by_key = {f["key"]: f for f in m["settings"]} - assert by_key["panel_mode"]["options"][0] == "minimal" # recommended first + assert by_key["panel_mode"]["options"][0] == "full" # default listed first def test_dashboard_control_endpoint(monkeypatch):