fix: install Browser Use CLI for every browser backend except Camofox

The Browser Use CLI 3.0 is the primary driver engine for all browser
backends except Camofox, but only the explicit 'Browser Use' picker row
ran the install hook. Local Browser, Browserbase, Firecrawl, and the
Nous-managed cloud rows left the CLI uninstalled, so those selections
depended on the uvx zero-install fallback (first-use PyPI download
inside the tool-call timeout) or silently downgraded to the built-in
browser tools where uvx was unavailable.

- Extract the install logic into _ensure_browser_use_cli() and run it
  from the agent_browser/browserbase post_setup branch too (Firecrawl
  and the Nous cloud row both declare post_setup: browserbase).
- Camofox is untouched: Firefox-based, no CDP surface, cannot be driven
  by the CDP-only browser-use harness.
- Failure stays non-fatal: uvx fallback, then built-in tools.

Tests pin the contract: every browser post_setup key except camofox
attempts the install; camofox never does; install failure never raises.
This commit is contained in:
Teknium
2026-08-14 10:46:19 -07:00
parent d6a5cb9725
commit 1169fb50a4
2 changed files with 101 additions and 21 deletions

View File

@@ -1642,11 +1642,52 @@ def _run_cua_driver_installer(
pass
def _ensure_browser_use_cli(*, verbose_hints: bool = False) -> None:
"""Install the Browser Use CLI if it isn't already runnable.
The Browser Use CLI 3.0 is the primary driver engine for EVERY browser
backend except Camofox (which is Firefox-based with no CDP surface, so
the CDP-only browser-use harness cannot drive it). Local, Browserbase,
Firecrawl, and the Nous-managed cloud rows all execute through
``browser_exec`` when the CLI is runnable — so every one of those
picker selections must attempt this install, not just the explicit
"Browser Use" row. Failure is non-fatal: ``browser_exec`` can still run
zero-install via ``uvx browser-use``, and the built-in browser tools
remain the final fallback.
"""
if shutil.which("browser-use"):
_print_success(" browser-use CLI found on PATH")
else:
_print_info(" Installing browser-use CLI (uv tool install browser-use)...")
try:
from tools.browser_use_cli import install_cli
ok, message = install_cli()
except Exception as exc: # pragma: no cover — defensive
ok, message = False, f"install failed: {exc}"
if ok:
_print_success(f" {message}")
else:
for line in str(message).splitlines():
_print_warning(f" {line[:200]}")
if shutil.which("uvx"):
_print_info(" Falling back to zero-install runs via `uvx browser-use`")
else:
_print_info(" Install manually: uv tool install browser-use (https://docs.astral.sh/uv/)")
if verbose_hints:
_print_info(" Local Chrome needs remote debugging: chrome://inspect/#remote-debugging")
_print_info(" Cloud browsers: browser-use auth login (or set BROWSER_USE_API_KEY)")
def _run_post_setup(post_setup_key: str):
"""Run post-setup hooks for tools that need extra installation steps."""
from hermes_constants import find_node_executable
if post_setup_key in {"agent_browser", "browserbase"}:
# Every non-Camofox browser backend drives through the Browser Use
# CLI when it's runnable — install it here too, not only on the
# explicit "Browser Use" picker row.
_ensure_browser_use_cli()
# agent-browser is no longer a root package.json dependency (#43564)
# — it resolves lazily via npx (or a global/Hermes-managed install)
# instead of a local `npm install`, so there's no node_modules/
@@ -1753,27 +1794,7 @@ def _run_post_setup(post_setup_key: str):
_print_info(" Run manually: npx agent-browser install --with-deps")
elif post_setup_key == "browser_use_cli":
if shutil.which("browser-use"):
_print_success(" browser-use CLI found on PATH")
else:
_print_info(" Installing browser-use CLI (uv tool install browser-use)...")
try:
from tools.browser_use_cli import install_cli
ok, message = install_cli()
except Exception as exc: # pragma: no cover — defensive
ok, message = False, f"install failed: {exc}"
if ok:
_print_success(f" {message}")
else:
for line in str(message).splitlines():
_print_warning(f" {line[:200]}")
if shutil.which("uvx"):
_print_info(" Falling back to zero-install runs via `uvx browser-use`")
else:
_print_info(" Install manually: uv tool install browser-use (https://docs.astral.sh/uv/)")
_print_info(" Local Chrome needs remote debugging: chrome://inspect/#remote-debugging")
_print_info(" Cloud browsers: browser-use auth login (or set BROWSER_USE_API_KEY)")
_ensure_browser_use_cli(verbose_hints=True)
elif post_setup_key == "camofox":
camofox_dir = PROJECT_ROOT / "node_modules" / "@askjo" / "camofox-browser"

View File

@@ -359,6 +359,16 @@ class TestAgentBrowserPostSetup:
(and Windows .cmd-shim) lookup.
"""
@pytest.fixture(autouse=True)
def _stub_browser_use_install(self):
"""Both browser branches now attempt a Browser Use CLI install first
(the CLI drives every non-Camofox backend). Stub it so these
Chromium-branch tests never bootstrap uv / hit the network, and so
their print/subprocess assertions stay scoped to the agent-browser
logic under test."""
with patch("hermes_cli.tools_config._ensure_browser_use_cli") as stub:
yield stub
def test_warns_when_neither_npx_nor_agent_browser_on_path(self):
with patch("shutil.which", return_value=None), patch(
"subprocess.run"
@@ -616,6 +626,55 @@ class TestAgentBrowserPostSetup:
assert any("timed out" in c.args[0] for c in warn.call_args_list)
class TestBrowserUseCliInstalledForAllNonCamofoxBackends:
"""The Browser Use CLI is the primary driver engine for every browser
backend except Camofox — so EVERY browser picker selection except
Camofox must attempt the CLI install, not just the explicit
"Browser Use" row."""
@pytest.mark.parametrize("key", ["agent_browser", "browserbase", "browser_use_cli"])
def test_browser_post_setup_attempts_cli_install(self, key):
with patch("hermes_cli.tools_config._ensure_browser_use_cli") as ensure, patch(
"shutil.which", return_value=None
), patch("subprocess.run"):
_run_post_setup(key)
ensure.assert_called_once()
def test_camofox_post_setup_never_touches_browser_use(self):
"""Camofox is Firefox-based with no CDP surface; the CDP-only
browser-use harness cannot drive it, so its setup must not pull
the CLI in."""
with patch("hermes_cli.tools_config._ensure_browser_use_cli") as ensure, patch(
"hermes_constants.find_node_executable", return_value=None
), patch("subprocess.run"):
_run_post_setup("camofox")
ensure.assert_not_called()
def test_ensure_helper_short_circuits_when_cli_on_path(self):
with patch(
"hermes_cli.tools_config.shutil.which", return_value="/usr/bin/browser-use"
), patch("tools.browser_use_cli.install_cli") as install:
from hermes_cli.tools_config import _ensure_browser_use_cli
_ensure_browser_use_cli()
install.assert_not_called()
def test_ensure_helper_install_failure_is_non_fatal(self):
"""A failed install must warn and fall back, never raise — the
uvx zero-install path and the built-in tools remain available."""
from hermes_cli.tools_config import _ensure_browser_use_cli
with patch(
"hermes_cli.tools_config.shutil.which", return_value=None
), patch(
"tools.browser_use_cli.install_cli",
return_value=(False, "`uv tool install browser-use` failed:\nboom"),
), patch("hermes_cli.tools_config._print_warning") as warn:
_ensure_browser_use_cli() # must not raise
assert any("failed" in c.args[0] for c in warn.call_args_list)
class TestImagegenBackendRegistry:
"""IMAGEGEN_BACKENDS tags drive the model picker flow in tools_config."""