diff --git a/__init__.py b/__init__.py index c5bc0cc..402e351 100644 --- a/__init__.py +++ b/__init__.py @@ -30,9 +30,14 @@ def register(registry) -> None: # Browser panel console view (embeds agent-browser's dashboard). Built out by the # board; register it best-effort so the foundation works before the view lands. + # TWO routers at DISTINCT prefixes: the PAGE (+ the iframe-loaded /panel/dash + # proxy, which can't carry a bearer) stays on the public /plugins/agent_browser; + # the shot/nav DATA routes mount under /api/plugins/agent_browser so they + # inherit the operator bearer gate (plugin-view rule 2). try: - from .browser_panel import build_panel_router + from .browser_panel import build_panel_data_router, build_panel_router registry.register_router(build_panel_router(cfg)) + registry.register_router(build_panel_data_router(cfg), prefix="/api/plugins/agent_browser") except ImportError: log.info("[agent_browser] browser panel not present yet — tools still serve") except Exception: # noqa: BLE001 diff --git a/browser_panel.py b/browser_panel.py index 14b45eb..be3c030 100644 --- a/browser_panel.py +++ b/browser_panel.py @@ -59,49 +59,16 @@ def build_panel_router(cfg: dict | None): router = APIRouter() - def _run(*args: str) -> tuple[int, str]: - try: - p = subprocess.run([binary, *args], capture_output=True, text=True, timeout=timeout) - return p.returncode, (p.stderr or p.stdout or "").strip() - except FileNotFoundError: - return 127, f"{binary!r} not on PATH" - except subprocess.TimeoutExpired: - return 124, "timed out" - @router.get("/panel") async def _panel(): return HTMLResponse(_MINIMAL_PAGE if mode == "minimal" else _FULL_PAGE) - # ── minimal-mode backing routes (same-origin) ───────────────────────────── - @router.get("/panel/shot") - async def _shot(): - """Latest viewport as a PNG. Throttled to ~1/0.8s so a fast poller can't - spawn a screenshot subprocess per frame.""" - global _shot_ts - now = time.monotonic() - if now - _shot_ts > 0.8 or not os.path.exists(_SHOT_PATH): - rc, err = await asyncio.to_thread(lambda: _run("screenshot", _SHOT_PATH)) - if rc == 0 and os.path.exists(_SHOT_PATH): - _shot_ts = now - else: - return Response(status_code=503, content=f"no frame: {err[:200]}") - return FileResponse(_SHOT_PATH, media_type="image/png", - headers={"Cache-Control": "no-store"}) - - @router.post("/panel/nav") - async def _nav(body: dict = Body(...)): - """Drive the viewport from the toolbar: open / back / forward / reload.""" - action = str(body.get("action", "")).strip().lower() - url = str(body.get("url", "")).strip() - if action == "open": - if not url: - return JSONResponse({"ok": False, "error": "url required"}) - rc, err = await asyncio.to_thread(lambda: _run("open", url)) - elif action in ("back", "forward", "reload"): - rc, err = await asyncio.to_thread(lambda: _run(action)) - else: - return JSONResponse({"ok": False, "error": f"bad action {action!r}"}) - return JSONResponse({"ok": rc == 0, "error": "" if rc == 0 else err[:200]}) + # The minimal-mode shot/nav DATA routes moved to build_panel_data_router — + # gated under /api/plugins/agent_browser (plugin-view rule 2). The /panel/dash + # reverse proxy below stays on the public prefix OF NECESSITY: it's loaded by + # an -""" @@ -302,38 +321,45 @@ async def _s2c():
Open a URL above, or let the agent drive — browser_open.
-""" diff --git a/protoagent.plugin.yaml b/protoagent.plugin.yaml index b97e3b5..044d369 100644 --- a/protoagent.plugin.yaml +++ b/protoagent.plugin.yaml @@ -1,6 +1,6 @@ id: agent_browser name: Agent Browser -version: 0.1.1 +version: 0.2.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