mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(browser): retry next candidate when debug launch exits early
This commit is contained in:
parent
c7103c637c
commit
c74f093523
2 changed files with 67 additions and 2 deletions
|
|
@ -7,6 +7,7 @@ import platform
|
|||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
|
|
@ -196,6 +197,31 @@ def _detach_kwargs(system: str) -> dict:
|
|||
return {"creationflags": flags} if flags else {}
|
||||
|
||||
|
||||
def _wait_for_browser_debug_ready_or_exit(
|
||||
proc: subprocess.Popen,
|
||||
port: int,
|
||||
timeout: float = 2.0,
|
||||
interval: float = 0.1,
|
||||
) -> str:
|
||||
"""Classify a launched browser as ready, exited, or still starting.
|
||||
|
||||
We only need to wait long enough to catch the common failure mode where a
|
||||
candidate binary exists but exits immediately before exposing the CDP port.
|
||||
Slower browsers can still finish starting after this grace window.
|
||||
"""
|
||||
cdp_url = f"http://127.0.0.1:{port}"
|
||||
deadline = time.monotonic() + timeout
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
if is_browser_debug_ready(cdp_url, timeout=min(interval, 0.2)):
|
||||
return "ready"
|
||||
if proc.poll() is not None:
|
||||
return "exited"
|
||||
time.sleep(interval)
|
||||
|
||||
return "starting"
|
||||
|
||||
|
||||
def try_launch_chrome_debug(port: int = DEFAULT_BROWSER_CDP_PORT, system: str | None = None) -> bool:
|
||||
system = system or platform.system()
|
||||
candidates = get_chrome_debug_candidates(system)
|
||||
|
|
@ -205,13 +231,15 @@ def try_launch_chrome_debug(port: int = DEFAULT_BROWSER_CDP_PORT, system: str |
|
|||
os.makedirs(chrome_debug_data_dir(), exist_ok=True)
|
||||
for candidate in candidates:
|
||||
try:
|
||||
subprocess.Popen(
|
||||
proc = subprocess.Popen(
|
||||
[candidate, *_chrome_debug_args(port)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
**_detach_kwargs(system),
|
||||
)
|
||||
return True
|
||||
state = _wait_for_browser_debug_ready_or_exit(proc, port)
|
||||
if state != "exited":
|
||||
return True
|
||||
except Exception:
|
||||
continue
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from unittest.mock import patch
|
|||
|
||||
from cli import HermesCLI
|
||||
from hermes_cli.browser_connect import (
|
||||
_wait_for_browser_debug_ready_or_exit,
|
||||
get_chrome_debug_candidates,
|
||||
is_browser_debug_ready,
|
||||
manual_chrome_debug_command,
|
||||
|
|
@ -65,6 +66,7 @@ class TestChromeDebugLaunch:
|
|||
|
||||
with patch("hermes_cli.browser_connect.shutil.which", side_effect=lambda name: r"C:\Chrome\chrome.exe" if name == "chrome.exe" else None), \
|
||||
patch("hermes_cli.browser_connect.os.path.isfile", side_effect=lambda path: path == r"C:\Chrome\chrome.exe"), \
|
||||
patch("hermes_cli.browser_connect._wait_for_browser_debug_ready_or_exit", return_value="ready"), \
|
||||
patch("subprocess.Popen", side_effect=fake_popen):
|
||||
assert HermesCLI._try_launch_chrome_debug(9333, "Windows") is True
|
||||
|
||||
|
|
@ -94,6 +96,7 @@ class TestChromeDebugLaunch:
|
|||
|
||||
with patch("hermes_cli.browser_connect.shutil.which", return_value=None), \
|
||||
patch("hermes_cli.browser_connect.os.path.isfile", side_effect=lambda path: path == installed), \
|
||||
patch("hermes_cli.browser_connect._wait_for_browser_debug_ready_or_exit", return_value="ready"), \
|
||||
patch("subprocess.Popen", side_effect=fake_popen):
|
||||
assert HermesCLI._try_launch_chrome_debug(9222, "Windows") is True
|
||||
|
||||
|
|
@ -199,6 +202,40 @@ class TestChromeDebugLaunch:
|
|||
|
||||
assert attempts == [brave, chrome]
|
||||
|
||||
def test_wait_for_browser_debug_ready_or_exit_detects_early_exit(self, monkeypatch):
|
||||
class _Proc:
|
||||
def __init__(self):
|
||||
self.calls = 0
|
||||
|
||||
def poll(self):
|
||||
self.calls += 1
|
||||
return 1 if self.calls >= 2 else None
|
||||
|
||||
monkeypatch.setattr("hermes_cli.browser_connect.time.sleep", lambda _seconds: None)
|
||||
with patch("hermes_cli.browser_connect.is_browser_debug_ready", return_value=False):
|
||||
state = _wait_for_browser_debug_ready_or_exit(_Proc(), 9222, timeout=0.3, interval=0.01)
|
||||
|
||||
assert state == "exited"
|
||||
|
||||
def test_launch_tries_next_browser_when_first_candidate_exits_before_debug_ready(self):
|
||||
brave = "/usr/bin/brave-browser"
|
||||
chrome = "/usr/bin/google-chrome"
|
||||
attempts = []
|
||||
|
||||
class _Proc:
|
||||
pass
|
||||
|
||||
def fake_popen(cmd, **kwargs):
|
||||
attempts.append(cmd[0])
|
||||
return _Proc()
|
||||
|
||||
with patch("hermes_cli.browser_connect.get_chrome_debug_candidates", return_value=[brave, chrome]), \
|
||||
patch("hermes_cli.browser_connect._wait_for_browser_debug_ready_or_exit", side_effect=["exited", "ready"]), \
|
||||
patch("subprocess.Popen", side_effect=fake_popen):
|
||||
assert HermesCLI._try_launch_chrome_debug(9222, "Linux") is True
|
||||
|
||||
assert attempts == [brave, chrome]
|
||||
|
||||
def test_manual_command_uses_wsl_windows_chrome_when_available(self):
|
||||
chrome = "/mnt/c/Program Files/Google/Chrome/Application/chrome.exe"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue