fix(browser): gate lightpanda --http-cache-dir on binary support
Salvage follow-up for PR #100269. The flag landed upstream in 0.3.x; binaries before it (e.g. 0.2.8, verified locally) fatally reject the flag with 'unknown argument', breaking every Browser Use launch. Probe 'lightpanda help' once per process and omit the flag when the binary predates it. Also soften the unverified concurrency claim in the _http_cache_dir docstring and tell docs readers to stop sessions before deleting the live sqlite cache.
This commit is contained in:
@@ -43,6 +43,7 @@ def _isolate(tmp_path, monkeypatch):
|
||||
# Never touch the developer's real ~/.local/bin/lightpanda.
|
||||
monkeypatch.setattr(lp, "_home_candidates", lambda: [])
|
||||
monkeypatch.setattr(lp, "_safe_start_time", lambda pid: 111)
|
||||
monkeypatch.setattr(lp, "_supports_http_cache", True)
|
||||
with lp._servers_lock:
|
||||
lp._servers.clear()
|
||||
yield state
|
||||
@@ -142,6 +143,39 @@ class TestLaunch:
|
||||
assert Path(cache).is_dir()
|
||||
assert not list(Path(cache).glob("*.json")) # never confused with a session record
|
||||
|
||||
def test_no_http_cache_flag_on_old_binary(self, monkeypatch, _isolate):
|
||||
monkeypatch.setattr(lp, "_supports_http_cache", False)
|
||||
_, err, calls = self._launch(monkeypatch)
|
||||
assert err is None
|
||||
assert "--http-cache-dir" not in calls["argv"]
|
||||
assert calls["argv"][-1] == "43111"
|
||||
|
||||
def test_http_cache_probe_caches_and_detects_flag(self, monkeypatch, tmp_path):
|
||||
exe = _exe(tmp_path / "lightpanda")
|
||||
runs = []
|
||||
|
||||
class FakeRun:
|
||||
def __init__(self, stdout):
|
||||
self.stdout = stdout
|
||||
|
||||
def fake_run(argv, **kwargs):
|
||||
runs.append(argv)
|
||||
return FakeRun("--http-cache-dir <PATH>" if len(runs) == 1 else "")
|
||||
|
||||
monkeypatch.setattr(lp, "_supports_http_cache", None)
|
||||
monkeypatch.setattr(lp.subprocess, "run", fake_run)
|
||||
assert lp._binary_supports_http_cache(str(exe)) is True
|
||||
assert lp._binary_supports_http_cache(str(exe)) is True # cached, single probe
|
||||
assert len(runs) == 1
|
||||
|
||||
monkeypatch.setattr(lp, "_supports_http_cache", None)
|
||||
|
||||
def fake_run_old(argv, **kwargs):
|
||||
return FakeRun("no such flag here")
|
||||
|
||||
monkeypatch.setattr(lp.subprocess, "run", fake_run_old)
|
||||
assert lp._binary_supports_http_cache(str(exe)) is False
|
||||
|
||||
def test_block_private_networks_flag(self, monkeypatch):
|
||||
_, err, calls = self._launch(monkeypatch, block_private_networks=True)
|
||||
assert err is None
|
||||
|
||||
@@ -119,15 +119,43 @@ def _http_cache_dir() -> Path:
|
||||
"""Filesystem HTTP cache shared by every Lightpanda this Hermes spawns.
|
||||
|
||||
Shared rather than per-session so a cached asset survives session churn.
|
||||
Lightpanda holds it in sqlite (WAL); a write that loses a race is a cache
|
||||
miss, never a failed page load, and ``--http-cache-entry-limit`` (default
|
||||
1000) bounds it without Hermes managing eviction.
|
||||
Lightpanda holds it in sqlite (WAL) with a best-effort write path, and
|
||||
``--http-cache-entry-limit`` (upstream default 1000, not passed here)
|
||||
bounds it without Hermes managing eviction.
|
||||
"""
|
||||
path = _state_dir() / "http-cache"
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
|
||||
_supports_http_cache: Optional[bool] = None
|
||||
|
||||
|
||||
def _binary_supports_http_cache(binary: str) -> bool:
|
||||
"""True if ``lightpanda serve`` accepts ``--http-cache-dir``.
|
||||
|
||||
The flag landed upstream in 0.3.x; older binaries fatally reject it
|
||||
("unknown argument"), which would break every launch. Probing ``help``
|
||||
output keeps working across future flag additions without parsing
|
||||
versions, and is cached for the process lifetime.
|
||||
"""
|
||||
global _supports_http_cache
|
||||
if _supports_http_cache is None:
|
||||
try:
|
||||
out = subprocess.run(
|
||||
[binary, "help"], capture_output=True, text=True, timeout=10
|
||||
)
|
||||
_supports_http_cache = "--http-cache-dir" in out.stdout
|
||||
except Exception as e:
|
||||
logger.debug("lightpanda http-cache probe failed (%s); assuming no", e)
|
||||
_supports_http_cache = False
|
||||
if not _supports_http_cache:
|
||||
logger.debug(
|
||||
"lightpanda %s predates --http-cache-dir; serving without a cache", binary
|
||||
)
|
||||
return _supports_http_cache
|
||||
|
||||
|
||||
def _record_path(session_name: str) -> Path:
|
||||
return _state_dir() / f"{session_name}.json"
|
||||
|
||||
@@ -229,10 +257,9 @@ def launch_lightpanda(
|
||||
)
|
||||
|
||||
port = _pick_free_loopback_port()
|
||||
argv = [
|
||||
binary, "serve", "--host", "127.0.0.1", "--port", str(port),
|
||||
"--http-cache-dir", str(_http_cache_dir()),
|
||||
]
|
||||
argv = [binary, "serve", "--host", "127.0.0.1", "--port", str(port)]
|
||||
if _binary_supports_http_cache(binary):
|
||||
argv += ["--http-cache-dir", str(_http_cache_dir())]
|
||||
if block_private_networks:
|
||||
argv.append("--block-private-networks")
|
||||
log_path = str(_state_dir() / f"{session_name}.log")
|
||||
|
||||
@@ -455,7 +455,7 @@ AGENT_BROWSER_ENGINE=lightpanda
|
||||
|
||||
The engine works with both browser drivers:
|
||||
|
||||
- **Browser Use mode (the default).** Hermes launches `lightpanda serve --host 127.0.0.1 --port <free>` itself — one process per `browser_exec` session name (or per task) — and points the Browser Use CLI at it. No Chromium, Playwright or Node.js is needed. The process is reaped after `browser.inactivity_timeout`, on exit, and by the orphan sweep if Hermes crashes. All of these processes share one on-disk HTTP cache at `$HERMES_HOME/cache/browser-use/lightpanda/http-cache`, so repeat visits skip re-downloading assets; delete that directory to clear it. Lightpanda has no graphical renderer, so `capture_screenshot()` is unavailable and the tool description tells the model to work text-first; it also holds one page per session, so the model is told to call `new_tab()` once and `goto_url()` afterwards (tracked upstream in [lightpanda-io/browser#1962](https://github.com/lightpanda-io/browser/issues/1962)).
|
||||
- **Browser Use mode (the default).** Hermes launches `lightpanda serve --host 127.0.0.1 --port <free>` itself — one process per `browser_exec` session name (or per task) — and points the Browser Use CLI at it. No Chromium, Playwright or Node.js is needed. The process is reaped after `browser.inactivity_timeout`, on exit, and by the orphan sweep if Hermes crashes. All of these processes share one on-disk HTTP cache at `$HERMES_HOME/cache/browser-use/lightpanda/http-cache`, so repeat visits skip re-downloading assets. Hermes passes the cache flag only when the installed Lightpanda supports it (0.3.x+); older binaries simply run without a cache. To clear it, stop your Lightpanda sessions first, then delete that directory. Lightpanda has no graphical renderer, so `capture_screenshot()` is unavailable and the tool description tells the model to work text-first; it also holds one page per session, so the model is told to call `new_tab()` once and `goto_url()` afterwards (tracked upstream in [lightpanda-io/browser#1962](https://github.com/lightpanda-io/browser/issues/1962)).
|
||||
- **Built-in browser tools** (`/browser use off`). Hermes drives Lightpanda through `agent-browser --engine lightpanda` over CDP, the same way it drives local Chrome, with **automatic Chrome fallback**: Lightpanda handles the actions it supports (navigate, snapshot, click, type, scroll, back, press, eval) and Hermes transparently retries on Chrome for anything it doesn't. Screenshots and `browser_vision` are routed straight to Chrome.
|
||||
|
||||
**When the engine is ignored.** `browser.engine` is the lowest-precedence browser setting: a cloud provider (including the Nous subscription browser — and on never-configured setups, any `BROWSERBASE_API_KEY` / `BROWSER_USE_API_KEY` in `~/.hermes/.env` auto-selects one), Camofox, a `browser.cdp_url` / `/browser connect` override, or `browser.use_real_profile` all take precedence. Picking Lightpanda in `hermes tools` writes `cloud_provider: local` for you; `/browser status` and `hermes doctor` report when the engine is configured but shadowed, and by what.
|
||||
|
||||
Reference in New Issue
Block a user