Skip to content

Commit 2ec5dbb

Browse files
mabry1985claude
andauthored
chore: host-free tests + CI + Settings UI + lint fix (v0.3.0) (#10)
Brings agent-browser-plugin to fleet parity. The plugin was solid (tools, same-origin dashboard proxy, lifecycle) but had no tests, no CI, and lint was quietly red. - Host-free test suite (tests/, 18 tests) — tool arg-building + graceful error degradation (missing binary / timeout / nonzero exit), the panel routers (page, four-rules compliance, /api gating, dashboard-proxy 502-when-down), register() wiring, the dashboard lifecycle, and manifest/version coherence. subprocess.run is mocked, so no agent-browser binary and no real browser are needed (verified in a clean venv). - CI (.github/workflows/ci.yml) — ruff check + pytest on every PR. - Settings ▸ Plugins UI — the operator knobs (panel_mode select, headed, allowed_domains, confirm_actions, profile, device, max_output, ports, timeout, manage_dashboard, binary) as editable fields (settings: block, ADR 0019). - Fixed two dead locals in build_panel_router (the red lint, unseen without CI) and re-synced pyproject.toml (0.1.0) with the manifest version → both 0.3.0. - Seeded CHANGELOG; README layout + Development section. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c506654 commit 2ec5dbb

10 files changed

Lines changed: 399 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
# Lint + host-free tests. Runs on the org's Namespace Linux profile (overridable via the
4+
# NSC_RUNNER repo var; falls back to a hosted runner on a fork). The suite mocks the
5+
# agent-browser CLI, so no browser/binary is needed.
6+
on:
7+
pull_request:
8+
push:
9+
branches: [main]
10+
workflow_dispatch:
11+
12+
jobs:
13+
test:
14+
runs-on: ${{ vars.NSC_RUNNER || 'namespace-profile-protolabs-linux' }}
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Setup Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: "3.12"
22+
23+
- name: Install dev deps
24+
run: pip install -r requirements-dev.txt ruff
25+
26+
- name: Lint
27+
run: ruff check .
28+
29+
- name: Test
30+
run: pytest -q

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ __pycache__/
33
.venv/
44
*.png
55
*.pdf
6+
.ruff_cache/
7+
.worktrees/
8+
.pytest_cache/
9+
.DS_Store

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Changelog
2+
3+
## v0.3.0
4+
- **Host-free test suite** (`tests/`) — the tool subprocess wrappers (arg-building +
5+
graceful error degradation), the panel routers (page / four-rules / gating / dashboard
6+
proxy 502), `register()` wiring, the dashboard lifecycle, and manifest/version coherence.
7+
subprocess is mocked, so no `agent-browser` binary and no real browser are needed.
8+
- **CI** (`.github/workflows/ci.yml`) — `ruff check` + `pytest` on every PR.
9+
- **Settings** — the operator config (panel mode, headed, allowed domains, profile, …) is
10+
now editable in **Settings ▸ Plugins** (`settings:` block; `panel_mode` is a select).
11+
- Fixed two dead locals in `build_panel_router` (lint was red — it had gone unnoticed
12+
without CI) and re-synced `pyproject.toml` (was `0.1.0`) with the manifest version.
13+
14+
## v0.2.0
15+
- Gated the minimal-mode `shot`/`nav` data routes under `/api/plugins/agent_browser`
16+
(plugin-view rule 2) and adopted the DS plugin-kit for the panel chrome (#8).
17+
18+
## v0.1.1
19+
- Serve the full-mode dashboard same-origin via a reverse proxy so it rides the fleet
20+
proxy on a member box instead of a hardcoded `localhost:PORT` (#6/#7); link the DS kit
21+
same-origin (`/_ds/`) instead of the CDN (#5).
22+
23+
## v0.1.0
24+
- Initial release: browser tools over the `agent-browser` CLI, a discovery skill, browser
25+
workflows, the Browser panel (`full` | `minimal`), and the dashboard lifecycle surface.

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,25 @@ agent_browser:
7777
| File | What |
7878
|---|---|
7979
| `tools.py` | the browser tools — subprocess wrappers over the `agent-browser` CLI |
80-
| `browser_panel.py` | the console view that embeds the agent-browser dashboard |
80+
| `browser_panel.py` | the console view that embeds the agent-browser dashboard (same-origin reverse proxy) |
81+
| `lifecycle.py` | the dashboard daemon surface — start on boot, stop on shutdown (ADR 0018) |
8182
| `skills/` | the discovery skill (defers to `agent-browser skills get core`) |
8283
| `workflows/` | declarative browser recipes |
83-
| `__init__.py` | `register()` — wires tools + panel; skills/workflows auto-discovered |
84+
| `tests/` | the host-free pytest suite (subprocess mocked — no binary needed) |
85+
| `__init__.py` | `register()` — wires tools + panel + lifecycle; skills/workflows auto-discovered |
86+
87+
The operator knobs (panel mode, headed, allowed domains, profile, device, …) are editable in
88+
**Settings ▸ Plugins ▸ Agent Browser**, or under `agent_browser:` in `langgraph-config.yaml`.
89+
90+
## Development
91+
92+
```bash
93+
pip install -r requirements-dev.txt
94+
pytest -q # host-free — subprocess is mocked, no agent-browser binary needed
95+
ruff check .
96+
```
97+
98+
CI runs the same on every PR.
8499

85100
Ships **disabled**; nothing runs until you enable it and the `agent-browser` binary
86101
is installed.

browser_panel.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ def build_panel_router(cfg: dict | None):
5454
cfg = cfg or {}
5555
port = int(cfg.get("dashboard_port", 4848))
5656
mode = str(cfg.get("panel_mode", "full")).strip().lower()
57-
binary = str(cfg.get("binary") or "agent-browser")
58-
timeout = float(cfg.get("timeout_s", 60))
57+
# NB: the page + dash-proxy routes don't shell out (binary/timeout) — they only
58+
# reverse-proxy the dashboard daemon on `port`. The shot/nav DATA routes that DO
59+
# run the CLI live in build_panel_data_router (gated under /api).
5960

6061
router = APIRouter()
6162

protoagent.plugin.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
id: agent_browser
22
name: Agent Browser
3-
version: 0.2.0
3+
version: 0.3.0
44
description: >-
55
Browser automation for protoAgent, backed by **agent-browser** (vercel-labs) — a
66
fast native-Rust CLI/daemon that drives Chrome over CDP with accessibility-tree
@@ -36,6 +36,20 @@ config:
3636
manage_dashboard: true # own the dashboard lifecycle (ADR 0018): start it on boot, stop it on
3737
# shutdown. Set false to leave a shared/persistent dashboard untouched.
3838

39+
# Editable in Settings ▸ Plugins (ADR 0019) — the operator knobs above as UI fields.
40+
settings:
41+
- { key: panel_mode, label: "Browser panel mode", type: select, options: [full, minimal], description: "full = iframe agent-browser's live dashboard (viewport + activity/console/network feeds); minimal = viewport-only (a polled screenshot + a nav toolbar)." }
42+
- { key: headed, label: "Headed browser", type: bool, description: "Show a real browser window instead of running headless." }
43+
- { key: allowed_domains, label: "Allowed domains", type: string, description: "Comma-separated navigable-domain allowlist (e.g. example.com,*.foo.com). Blank = unrestricted." }
44+
- { key: confirm_actions, label: "Confirm actions", type: string, description: "Comma-separated action categories that require confirmation before the agent runs them." }
45+
- { key: profile, label: "Browser profile dir", type: string, description: "Path to a browser profile directory — isolation + persisted auth/cookies across sessions." }
46+
- { key: device, label: "Device emulation", type: string, description: "Emulate a device, e.g. \"iPhone 16 Pro\". Blank = desktop." }
47+
- { key: max_output, label: "Max page-text output (chars)", type: number, description: "Cap characters of page text returned to the model (LLM-safety). 0 = the CLI default." }
48+
- { key: dashboard_port, label: "Dashboard port", type: number, description: "Port for agent-browser's dashboard daemon; the full-mode panel reverse-proxies it." }
49+
- { key: timeout_s, label: "Command timeout (s)", type: number, description: "Per-command subprocess timeout for the browser tools." }
50+
- { key: manage_dashboard, label: "Manage dashboard lifecycle", type: bool, description: "Start the dashboard on boot and stop it on shutdown. Turn off to leave a shared/persistent dashboard untouched." }
51+
- { key: binary, label: "agent-browser binary", type: string, description: "The agent-browser CLI on PATH (override with a pinned absolute path if needed)." }
52+
3953
# Console view (ADR 0026) — embeds agent-browser's live dashboard (viewport + feeds).
4054
views:
4155
- { id: panel, label: "Browser", icon: Globe, path: /plugins/agent_browser/panel }

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "agent-browser-plugin"
3-
version = "0.1.0"
3+
version = "0.3.0"
44
description = "Browser-automation plugin for protoAgent, backed by vercel-labs/agent-browser (tools + skill + workflows + a live browser panel)."
55
requires-python = ">=3.11"
66

requirements-dev.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Test-only deps. At runtime the host (protoAgent) provides fastapi + langchain-core
2+
# (and httpx/websockets for the dashboard proxy); the only runtime requirement is the
3+
# `agent-browser` CLI on PATH. These let the host-free suite run standalone in CI.
4+
fastapi>=0.110
5+
langchain-core>=0.2
6+
httpx>=0.27
7+
pyyaml>=6
8+
pytest>=8
9+
pytest-asyncio>=0.23

tests/conftest.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Host-free test bootstrap. Register the plugin under a synthetic `agent_browser` package
2+
so the modules' relative imports resolve with no protoAgent host — the host-only imports are
3+
lazy (inside register()), so importing needs only the dev deps (fastapi + langchain-core).
4+
The browser tools shell out to the `agent-browser` CLI, so the suite mocks subprocess.run —
5+
no real binary, no real browser."""
6+
7+
from __future__ import annotations
8+
9+
import importlib.util
10+
import sys
11+
import types
12+
from pathlib import Path
13+
14+
import pytest
15+
16+
ROOT = Path(__file__).resolve().parent.parent
17+
PKG = "agent_browser"
18+
19+
if PKG not in sys.modules:
20+
_spec = importlib.util.spec_from_file_location(PKG, ROOT / "__init__.py", submodule_search_locations=[str(ROOT)])
21+
assert _spec and _spec.loader
22+
_mod = importlib.util.module_from_spec(_spec)
23+
sys.modules[PKG] = _mod
24+
_spec.loader.exec_module(_mod)
25+
26+
27+
class FakeRegistry:
28+
"""Mirrors the registry surface register() touches."""
29+
30+
def __init__(self, config=None):
31+
self.config = config or {}
32+
self.tools = []
33+
self.routers = []
34+
self.surfaces = []
35+
self.skill_dirs = []
36+
self.workflow_dirs = []
37+
38+
def register_tool(self, t):
39+
self.tools.append(t)
40+
41+
def register_router(self, router, prefix=None):
42+
self.routers.append((prefix, router))
43+
44+
def register_surface(self, start, stop=None, name=None):
45+
self.surfaces.append((name, start, stop))
46+
47+
def register_skill_dir(self, path):
48+
self.skill_dirs.append(path)
49+
50+
def register_workflow_dir(self, path):
51+
self.workflow_dirs.append(path)
52+
53+
54+
@pytest.fixture
55+
def registry():
56+
return FakeRegistry()
57+
58+
59+
def fake_run(rc=0, stdout="ok", stderr="", record=None):
60+
"""A subprocess.run stand-in: records the argv and returns a canned CompletedProcess."""
61+
62+
def _run(args, **kw):
63+
if record is not None:
64+
record.append(list(args))
65+
return types.SimpleNamespace(returncode=rc, stdout=stdout, stderr=stderr)
66+
67+
return _run

0 commit comments

Comments
 (0)