mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-01 08:21:50 +08:00
Fixes from Copilot's two passes on PR #17238: * Validate parsed URL once: reject missing host, invalid port, and unsupported scheme up front so malformed inputs (e.g. http://:9222 or http://localhost:abc) don't fall through to a generic 5031. * Tighten _is_default_local_cdp to require a discovery-style path so ws://127.0.0.1:9222/devtools/browser/<id> is not collapsed to bare http://127.0.0.1:9222 (which would lose the path and break the connect). * Move browser.manage into _LONG_HANDLERS so the up-to-10s launch-and-retry loop runs on the RPC pool instead of blocking the main dispatcher. * try_launch_chrome_debug uses Windows-appropriate detach kwargs (creationflags=DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP) instead of POSIX-only start_new_session=True. * manual_chrome_debug_command uses subprocess.list2cmdline on Windows so the printed instruction is cmd.exe-compatible. * Mirror host/port validation in cli.py /browser connect so the classic CLI never persists an invalid BROWSER_CDP_URL.
139 lines
4.1 KiB
Python
139 lines
4.1 KiB
Python
"""Shared helpers for attaching Hermes to a local Chrome CDP port."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import platform
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
|
|
from hermes_constants import get_hermes_home
|
|
|
|
|
|
DEFAULT_BROWSER_CDP_PORT = 9222
|
|
DEFAULT_BROWSER_CDP_URL = f"http://127.0.0.1:{DEFAULT_BROWSER_CDP_PORT}"
|
|
|
|
_DARWIN_APPS = (
|
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
)
|
|
|
|
_WINDOWS_INSTALL_PARTS = (
|
|
("Google", "Chrome", "Application", "chrome.exe"),
|
|
("Chromium", "Application", "chrome.exe"),
|
|
("Chromium", "Application", "chromium.exe"),
|
|
("BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
("Microsoft", "Edge", "Application", "msedge.exe"),
|
|
)
|
|
|
|
_LINUX_BIN_NAMES = (
|
|
"google-chrome", "google-chrome-stable", "chromium-browser",
|
|
"chromium", "brave-browser", "microsoft-edge",
|
|
)
|
|
|
|
_WINDOWS_BIN_NAMES = (
|
|
"chrome.exe", "msedge.exe", "brave.exe", "chromium.exe",
|
|
"chrome", "msedge", "brave", "chromium",
|
|
)
|
|
|
|
|
|
def get_chrome_debug_candidates(system: str) -> list[str]:
|
|
candidates: list[str] = []
|
|
seen: set[str] = set()
|
|
|
|
def add(path: str | None) -> None:
|
|
if not path:
|
|
return
|
|
normalized = os.path.normcase(os.path.normpath(path))
|
|
if normalized in seen or not os.path.isfile(path):
|
|
return
|
|
candidates.append(path)
|
|
seen.add(normalized)
|
|
|
|
def add_install_paths(bases: tuple[str | None, ...]) -> None:
|
|
for base in filter(None, bases):
|
|
for parts in _WINDOWS_INSTALL_PARTS:
|
|
add(os.path.join(base, *parts))
|
|
|
|
if system == "Darwin":
|
|
for app in _DARWIN_APPS:
|
|
add(app)
|
|
return candidates
|
|
|
|
if system == "Windows":
|
|
for name in _WINDOWS_BIN_NAMES:
|
|
add(shutil.which(name))
|
|
add_install_paths((
|
|
os.environ.get("ProgramFiles"),
|
|
os.environ.get("ProgramFiles(x86)"),
|
|
os.environ.get("LOCALAPPDATA"),
|
|
))
|
|
return candidates
|
|
|
|
for name in _LINUX_BIN_NAMES:
|
|
add(shutil.which(name))
|
|
add_install_paths(("/mnt/c/Program Files", "/mnt/c/Program Files (x86)"))
|
|
return candidates
|
|
|
|
|
|
def chrome_debug_data_dir() -> str:
|
|
return str(get_hermes_home() / "chrome-debug")
|
|
|
|
|
|
def _chrome_debug_args(port: int) -> list[str]:
|
|
return [
|
|
f"--remote-debugging-port={port}",
|
|
f"--user-data-dir={chrome_debug_data_dir()}",
|
|
"--no-first-run",
|
|
"--no-default-browser-check",
|
|
]
|
|
|
|
|
|
def manual_chrome_debug_command(port: int = DEFAULT_BROWSER_CDP_PORT, system: str | None = None) -> str | None:
|
|
system = system or platform.system()
|
|
candidates = get_chrome_debug_candidates(system)
|
|
|
|
if candidates:
|
|
argv = [candidates[0], *_chrome_debug_args(port)]
|
|
return subprocess.list2cmdline(argv) if system == "Windows" else shlex.join(argv)
|
|
|
|
if system == "Darwin":
|
|
data_dir = chrome_debug_data_dir()
|
|
return (
|
|
f'open -a "Google Chrome" --args --remote-debugging-port={port} '
|
|
f'--user-data-dir="{data_dir}" --no-first-run --no-default-browser-check'
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
def _detach_kwargs(system: str) -> dict:
|
|
if system != "Windows":
|
|
return {"start_new_session": True}
|
|
flags = getattr(subprocess, "DETACHED_PROCESS", 0) | getattr(
|
|
subprocess, "CREATE_NEW_PROCESS_GROUP", 0
|
|
)
|
|
return {"creationflags": flags} if flags else {}
|
|
|
|
|
|
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)
|
|
if not candidates:
|
|
return False
|
|
|
|
os.makedirs(chrome_debug_data_dir(), exist_ok=True)
|
|
try:
|
|
subprocess.Popen(
|
|
[candidates[0], *_chrome_debug_args(port)],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
**_detach_kwargs(system),
|
|
)
|
|
return True
|
|
except Exception:
|
|
return False
|