mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
browser_exec is the only core browser path, so there is no Browser Use / built-in tools switch to flip and no silent downgrade to announce. - classic CLI, Ink TUI and the gateway browser.manage RPC lose the `use` action (contract regenerated: BrowserAction, enabled, browser_use); - /browser status no longer reports a mode; the Chromium-fallback line for Lightpanda and the dead "chrome" engine row go with the built-in tools; - browser_backend_notice leaves the session snapshot, the CLI banner and the canonical contract. TUI locale packs and _keys.tui.json drop the five slashCmd.ops.browser keys whose only caller was `/browser use`.
75 lines
3.6 KiB
Python
75 lines
3.6 KiB
Python
"""Classic CLI ``/browser`` on the shared gateway (#134889 CLI-9): ``browser.manage``, as Ink and
|
|
Desktop send it. Split from ``cli_commands_mixin`` (the file is over its size target)."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from urllib.parse import urlparse
|
|
|
|
from hermes_cli.browser_connect import DEFAULT_BROWSER_CDP_URL
|
|
from hermes_cli.cli_commands_session_tools import _say_block, _t
|
|
|
|
|
|
def _gateway_is_remote() -> bool:
|
|
"""True when this CLI attaches to an explicitly named gateway WebSocket that is not loopback
|
|
(``HERMES_TUI_GATEWAY_URL``): Desktop's ``mode === 'remote'``."""
|
|
raw = os.environ.get("HERMES_TUI_GATEWAY_URL", "").strip()
|
|
if not raw:
|
|
return False
|
|
host = (urlparse(raw).hostname or "").lower()
|
|
return host not in {"127.0.0.1", "localhost", "::1"}
|
|
|
|
|
|
def gateway_browser(cli, connection, word: str, rest: str) -> None:
|
|
"""``/browser`` on the shared gateway (#134889 CLI-9): ``browser.manage``, the verb Ink and
|
|
Desktop send. The browser tools run in the gateway, so changing this process's
|
|
``BROWSER_CDP_URL`` would change nothing the agent uses. ``connect``/``disconnect`` manage a
|
|
browser on the gateway HOST: only against a local gateway (Desktop's rule). The gateway scopes
|
|
them to this session's profile."""
|
|
from hermes_cli.gateway_client import GatewayClientError
|
|
if word in {"connect", "disconnect"} and _gateway_is_remote():
|
|
return _say_block(_t("browser.remote_gateway"))
|
|
params = {"action": word, "session_id": cli.session_id}
|
|
if word == "connect":
|
|
normalized = _normalize_cdp_url(rest or DEFAULT_BROWSER_CDP_URL)
|
|
if normalized is None:
|
|
return
|
|
params["url"] = normalized[0]
|
|
print(f" {_t('browser.checking', url=params['url'])}")
|
|
try:
|
|
result = connection.call("browser.manage", **params) or {}
|
|
except GatewayClientError as exc:
|
|
return _say_block(f" ⚠ {exc}")
|
|
for message in result.get("messages") or ():
|
|
print(f" {message}")
|
|
if word == "disconnect":
|
|
return _say_block(_t("browser.disconnected"), f" {_t('browser.reverted')}")
|
|
if word == "connect":
|
|
if not result.get("connected"):
|
|
return _say_block(_t("browser.not_connected"))
|
|
return _say_block(_t("browser.connected"), f" {_t('browser.endpoint', url=result.get('url') or '')}")
|
|
if result.get("connected"):
|
|
return _say_block(_t("browser.status_connected"), f" {_t('browser.endpoint', url=result.get('url') or '')}")
|
|
_say_block(_t("browser.status_gateway_default"),
|
|
f" {_t('browser.status_connect_hint')}", f" {_t('browser.status_disconnect_hint')}")
|
|
|
|
|
|
def _normalize_cdp_url(cdp_url: str):
|
|
"""Validate a /browser connect URL: ``(cdp_url, port)`` or None after printing the error.
|
|
A ``/devtools/browser/<id>`` path is kept verbatim; anything else is reduced to the origin."""
|
|
parsed = urlparse(cdp_url if "://" in cdp_url else f"http://{cdp_url}")
|
|
if parsed.scheme not in {"http", "https", "ws", "wss"}:
|
|
_say_block(f" {_t('browser.unsupported_scheme', scheme=parsed.scheme or _t('browser.scheme_missing'))}")
|
|
return None
|
|
try:
|
|
port = parsed.port or (443 if parsed.scheme in {"https", "wss"} else 80)
|
|
except ValueError:
|
|
_say_block(f" {_t('browser.invalid_port', url=cdp_url)}")
|
|
return None
|
|
if not parsed.hostname:
|
|
_say_block(f" {_t('browser.missing_host', url=cdp_url)}")
|
|
return None
|
|
if parsed.path.startswith("/devtools/browser/"):
|
|
return parsed.geturl(), port
|
|
return parsed._replace(path="", params="", query="", fragment="").geturl(), port
|
|
|