From 03566e5124d106656f4152c1b084c233d9c07f3f Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 8 May 2026 07:56:35 -0700 Subject: [PATCH] fix(windows): auto-install Playwright Chromium + surface it in doctor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/install.sh runs 'npx playwright install --with-deps chromium' on every Linux distro after the npm-install step, which is why browser tools Just Work on Linux. scripts/install.ps1 never did the equivalent step, so on native Windows installs check_browser_requirements() in tools/browser_tool.py would return False (no Chromium under %LOCALAPPDATA%\ms-playwright) and every browser_* tool got silently filtered out of the agent's tool schema — no error, no log entry, user just wondered why the tools didn't exist. Two-part fix: 1. scripts/install.ps1: after 'npm install' in InstallDir succeeds, run 'npx playwright install chromium'. Resolves npx via the same execution-policy-aware logic already used for npm (prefer npx.cmd next to npmExe, fall back to Get-Command). Surfaces a warning + manual-recovery hint when the install fails, matching install.sh behaviour for distros. 2. hermes_cli/doctor.py: after the agent-browser check, lazily import tools.browser_tool and reuse the exact same _chromium_installed() predicate check_browser_requirements() uses, so the doctor signal cannot drift from the runtime gate. Skip the check when Camofox / CDP override / a cloud provider / Lightpanda is configured (those bypass local Chromium). On missing Chromium, the hint is platform-correct: '--with-deps' on POSIX, plain 'install chromium' on win32. Verified on Windows 10: - 'npx playwright install chromium' completes successfully, drops Chrome Headless Shell under %LOCALAPPDATA%\ms-playwright - check_browser_requirements() flips from False -> True - 'hermes doctor' now prints either '✓ Playwright Chromium (browser engine)' or '⚠ Playwright Chromium not installed' + fix command - tests/hermes_cli/test_doctor.py: 38/38 pass - tests/tools/test_browser_chromium_check.py: 16/16 pass --- hermes_cli/doctor.py | 53 +++++++++++++++++++++++++++++++++++++++ scripts/install.ps1 | 59 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index 3cc21c586d..2b66318487 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -1035,10 +1035,13 @@ def run_doctor(args): check_ok("Node.js") # Check if agent-browser is installed agent_browser_path = PROJECT_ROOT / "node_modules" / "agent-browser" + agent_browser_ok = False if agent_browser_path.exists(): check_ok("agent-browser (Node.js)", "(browser automation)") + agent_browser_ok = True elif shutil.which("agent-browser"): check_ok("agent-browser", "(browser automation)") + agent_browser_ok = True else: if _is_termux(): check_info("agent-browser is not installed (expected in the tested Termux path)") @@ -1048,6 +1051,56 @@ def run_doctor(args): check_info(step) else: check_warn("agent-browser not installed", "(run: npm install)") + + # Chromium presence — the browser tools silently fail to register when + # agent-browser is found but no Playwright-managed Chromium is on disk + # (tools/browser_tool.py::check_browser_requirements filters them out + # before the agent ever sees them). Reuse the exact predicate it uses + # so the two checks cannot diverge. Skip on Termux (not a tested + # path). + if agent_browser_ok and not _is_termux(): + try: + # Lazy import: browser_tool is a ~150KB module we don't want + # to eagerly load in every `hermes doctor` invocation. + from tools.browser_tool import ( + _chromium_installed, + _is_camofox_mode, + _get_cloud_provider, + _get_cdp_override, + _using_lightpanda_engine, + ) + except Exception: + # If browser_tool can't even import, that's a separate bug + # surfaced elsewhere; don't crash doctor. + pass + else: + # Only warn about Chromium if the installed engine actually + # requires it: Camofox, CDP override, a cloud provider, or + # Lightpanda all bypass the local Chromium requirement. + skip_chromium_check = ( + _is_camofox_mode() + or bool(_get_cdp_override()) + or _get_cloud_provider() is not None + or _using_lightpanda_engine() + ) + if not skip_chromium_check: + if _chromium_installed(): + check_ok("Playwright Chromium", "(browser engine)") + else: + check_warn( + "Playwright Chromium not installed", + "(browser_* tools will be hidden from the agent)", + ) + if sys.platform == "win32": + check_info( + f"Install with: cd {PROJECT_ROOT} && " + "npx playwright install chromium" + ) + else: + check_info( + f"Install with: cd {PROJECT_ROOT} && " + "npx playwright install --with-deps chromium" + ) else: if _is_termux(): check_info("Node.js not found (browser tools are optional in the tested Termux path)") diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 2ffca87adb..d390d8fae0 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1040,7 +1040,64 @@ function Install-NodeDeps { if (Test-Path "$InstallDir\package.json") { Write-Info "Installing Node.js dependencies (browser tools)..." $browserLog = "$env:TEMP\hermes-npm-browser-$(Get-Random).log" - [void](_Run-NpmInstall "Browser tools" $InstallDir $browserLog $npmExe) + $browserNpmOk = _Run-NpmInstall "Browser tools" $InstallDir $browserLog $npmExe + + # Install Playwright Chromium (mirrors scripts/install.sh behaviour for + # Linux). Without this, tools/browser_tool.py::check_browser_requirements + # returns False (no Chromium under %LOCALAPPDATA%\ms-playwright), and the + # browser_* tools are silently filtered out of the agent's tool schema. + # System Chrome at "C:\Program Files\Google\Chrome\..." is NOT used by + # agent-browser — it expects a Playwright-managed Chromium. + if ($browserNpmOk) { + Write-Info "Installing browser engine (Playwright Chromium)..." + # npx lives next to npm in the same bin dir. Prefer .cmd to dodge + # the same execution-policy gotcha that affects npm.ps1 (see above). + $npmDir = Split-Path $npmExe -Parent + $npxExe = $null + foreach ($cand in @("npx.cmd", "npx.exe", "npx")) { + $try = Join-Path $npmDir $cand + if (Test-Path $try) { $npxExe = $try; break } + } + if (-not $npxExe) { + $npxCmd = Get-Command npx -ErrorAction SilentlyContinue + if ($npxCmd) { $npxExe = $npxCmd.Source } + } + if (-not $npxExe) { + Write-Warn "npx not found — cannot install Playwright Chromium." + Write-Info "Run manually later: cd `"$InstallDir`"; npx playwright install chromium" + } else { + $pwLog = "$env:TEMP\hermes-playwright-install-$(Get-Random).log" + Push-Location $InstallDir + try { + & $npxExe playwright install chromium *> $pwLog + $pwCode = $LASTEXITCODE + if ($pwCode -eq 0) { + Write-Success "Playwright Chromium installed (browser tools ready)" + Remove-Item -Force $pwLog -ErrorAction SilentlyContinue + } else { + Write-Warn "Playwright Chromium install failed — exit code $pwCode" + Write-Warn "Browser tools will not work until Chromium is installed." + if (Test-Path $pwLog) { + $pwErr = Get-Content $pwLog -Raw -ErrorAction SilentlyContinue + if ($pwErr) { + $snippet = if ($pwErr.Length -gt 1200) { $pwErr.Substring(0, 1200) + "..." } else { $pwErr } + Write-Info " playwright output:" + foreach ($line in $snippet -split "`n") { + Write-Host " $line" -ForegroundColor DarkGray + } + Write-Info " Full log: $pwLog" + } + } + Write-Info "Run manually later: cd `"$InstallDir`"; npx playwright install chromium" + } + } catch { + Write-Warn "Playwright Chromium install could not be launched: $_" + Write-Info "Run manually later: cd `"$InstallDir`"; npx playwright install chromium" + } finally { + Pop-Location + } + } + } } # TUI