Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
96013cc
feat(computer_use): add remote CUA transport with authenticated host …
bennybuoy Sep 5, 2026
85f582a
feat(computer_use): remote CUA availability + config discovery — chec…
bennybuoy Sep 5, 2026
3039213
fix(computer_use): harden CUA bridge launchers — sanitized child envs…
bennybuoy Sep 5, 2026
7ef4729
fix(computer_use): harden CUA host bridge — transport-security-first …
bennybuoy Sep 5, 2026
dbd7dbe
fix(computer_use): remote CUA client transport — MCP 2.0 indexed stre…
bennybuoy Sep 5, 2026
30dbe5e
fix(computer_use): align bridge session idle timeout at 1800s across …
bennybuoy Sep 5, 2026
25e7cde
fix(computer_use): document Host-header port requirement in bridge --…
bennybuoy Sep 5, 2026
ebb6f92
fix(computer_use): plumb bridge --session-idle-timeout through run_ho…
bennybuoy Sep 6, 2026
40a244f
refactor(computer_use): rename bridge subcommand to host-bridge to ma…
bennybuoy Sep 6, 2026
512ecf5
test(computer_use): port launcher parity tests (bind gate, env saniti…
bennybuoy Sep 6, 2026
b78b571
fix(environments): strip HERMES_CUA_REMOTE_TOKEN unconditionally from…
bennybuoy Sep 6, 2026
6c26c4b
fix(computer_use): sanitize cua-driver child env in standalone launch…
bennybuoy Sep 6, 2026
fe0f22b
fix(computer_use): recover remote CUA session from bridge idle-expiry…
bennybuoy Sep 6, 2026
2d76267
fix(computer_use): resolve remote CUA token via profile secret scope;…
bennybuoy Sep 6, 2026
7f53ce6
fix(computer_use): no-store on all bridge responses; behavior-based p…
bennybuoy Sep 6, 2026
f33569a
fix(computer_use): port-suffixed allowlist docs; accurate Host/Origin…
bennybuoy Sep 6, 2026
b69b055
fix(computer_use): profile-qualify backend cache keys; move start() o…
bennybuoy Sep 6, 2026
e53cf25
test(computer_use): regression tests for profile-qualified backend ke…
bennybuoy Sep 6, 2026
ebfa49f
fix(computer_use): fence released backend start generations
bennybuoy Sep 7, 2026
32edc71
fix(computer_use): qualify approval grants by profile owner
bennybuoy Sep 7, 2026
fd982f6
test(computer_use): pin owner collisions and revoked startup
bennybuoy Sep 7, 2026
c347729
fix(computer_use): use structural owners and home-scoped empty hooks
bennybuoy Sep 7, 2026
bce4d37
fix(computer_use): fence replacement generations and clean failed starts
bennybuoy Sep 7, 2026
21c0302
fix(computer_use): retain session profile through teardown
bennybuoy Sep 7, 2026
0999d6f
test(computer_use): keep teardown regression metadata probes offline
bennybuoy Sep 7, 2026
29a6337
fix(computer_use): fence released backend dispatch authority
bennybuoy Sep 8, 2026
6e0d47f
fix(computer_use): forbid local CLI refetch for remote sessions
bennybuoy Sep 8, 2026
78738a9
docs(computer_use): document direct transport and standalone dependen…
bennybuoy Sep 8, 2026
43c8c8a
fix(computer_use): restore owner profile during exit teardown
bennybuoy Sep 8, 2026
cd6ec38
fix(computer_use): revoke approvals from failed acquisition generations
bennybuoy Sep 8, 2026
f6fd8a0
fix: reject stale direct CUA configuration loader results
bennybuoy Sep 8, 2026
e28a380
fix: bind CUA availability and diagnosis to constructed target
bennybuoy Sep 8, 2026
7524ad5
fix: reject unsupported direct CUA selectors and null remote intent
bennybuoy Sep 8, 2026
89d5a7d
fix(computer_use): diagnose captures against their bound desktop
bennybuoy Sep 8, 2026
9a79716
fix(computer_use): reject incomplete direct remote selection
bennybuoy Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agent/agent_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,8 @@ def _init_session_state(agent, session_id, session_db, parent_session_id, reason
f"{agent.session_start.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}"
)
_publish_session_id(agent.session_id)
# Reaper/close threads need the session's home after its build scope is gone.
agent._session_hermes_home = get_hermes_home().resolve()

# ~/.hermes/sessions/ — kept unconditionally for request_dump_*.json debug breadcrumbs.
agent.logs_dir = get_hermes_home() / "sessions"
Expand Down
10 changes: 9 additions & 1 deletion agent/client_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,16 @@ def kill_processes() -> None:
)

def release_computer_use() -> None:
from hermes_constants import set_hermes_home_override, reset_hermes_home_override
from tools.computer_use.tool import release_computer_use_session
release_computer_use_session(task_id)
# Never guess an unknown owner's home from the thread doing teardown.
if (home := getattr(self, "_session_hermes_home", None)) is None:
return
token = set_hermes_home_override(home)
try:
release_computer_use_session(task_id)
finally:
reset_hermes_home_override(token)

for step in (kill_processes, lambda: cleanup_vm(task_id), lambda: cleanup_browser(task_id), release_computer_use):
_quietly(step)
Expand Down
8 changes: 8 additions & 0 deletions hermes_cli/config_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,10 @@ def _aux(timeout, *, reasoning_effort=True, **extra):
# closed unless signed with the official com.trycua.driver identity. Only for local driver
# development from source.
"allow_unsigned_driver": False,
# Authenticated remote transport: drive a cua-driver host bridge (hermes computer-use
# bridge) on another machine instead of a local driver. Requires the bridge URL here and
# HERMES_CUA_REMOTE_TOKEN in .env (>= 32 bytes; secrets.token_hex(32)); HTTP is loopback-only.
"remote": {"enabled": False, "url": ""},
},
# Egress credential-injection proxy (iron-proxy) for remote terminal sandboxes (Docker today):
# the sandbox sees opaque tokens and iron-proxy swaps in real credentials at egress, so a
Expand Down Expand Up @@ -2496,6 +2500,10 @@ def _base_url(name, prompt_name=None):
"Azure Foundry base URL (set via 'hermes model' for endpoint-specific config)",
"Azure Foundry base URL", None, password=False),
# ── Tool API keys ──
"HERMES_CUA_REMOTE_TOKEN": _tool(
"Bearer token for remote CUA transport (computer_use.remote.enabled=true; >= 32 bytes; "
"generate with secrets.token_hex(32))", "Remote CUA transport token", advanced=True,
tools=["computer_use"]),
"EXA_API_KEY": _tool("Exa API key for AI-native web search and contents", "Exa API key",
"https://exa.ai/", tools=["web_search", "web_extract"]),
"PARALLEL_API_KEY": _tool("Parallel API key for AI-native web search and extract",
Expand Down
33 changes: 32 additions & 1 deletion hermes_cli/subcommands/computer_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ def _cu_perms_grant(args) -> None:
sys.exit(request_permissions_grant())


def _cu_host_bridge(args) -> int:
from tools.computer_use.host_bridge_cli import run_host_bridge
run_host_bridge(allowed_hosts=[h.strip() for h in args.allowed_hosts.split(",") if h.strip()],
allowed_origins=[o.strip() for o in args.allowed_origins.split(",") if o.strip()] if args.allowed_origins else [],
port=args.port, bind=args.bind,
session_idle_timeout=getattr(args, "session_idle_timeout", 1800) or 1800)
return 0


def build_computer_use_parser(subparsers) -> None:
"""Attach the ``computer-use`` subcommand to ``subparsers``."""
computer_use_parser = subparsers.add_parser(
Expand Down Expand Up @@ -140,6 +149,28 @@ def build_computer_use_parser(subparsers) -> None:
"PATH. The upstream install.sh always pulls the latest release, "
"so this performs an in-place upgrade.")
computer_use_sub.add_parser("status", help="Print whether cua-driver is installed and on PATH")
computer_use_bridge = computer_use_sub.add_parser(
"host-bridge", help="Run the CUA host bridge on this machine (drive this desktop remotely)",
description="Expose this machine's cua-driver over an authenticated streamable-HTTP\n"
"bridge so a remote Hermes agent (computer_use.remote.* + HERMES_CUA_REMOTE_TOKEN)\n"
"can drive this desktop. --allowed-hosts is the mandatory Host-header allowlist —\n"
"entries are exact-match Host values and must include the port when the bridge\n"
"listens on a nonstandard port (e.g. host:8765); an empty allowlist would let any\n"
"page drive the desktop, so the bridge refuses to start without it.\n"
"--allowed-origins is a separate list for browser-origin protection, not an\n"
"alternative spelling of --allowed-hosts; the two headers are validated\n"
"independently. Pass --bind 0.0.0.0 only when the remote agent reaches this host\n"
"over a trusted network.")
computer_use_bridge.add_argument("--port", type=int, default=8765,
help="TCP port to listen on")
computer_use_bridge.add_argument("--bind", default="127.0.0.1",
help="Address to bind (default 127.0.0.1; use 0.0.0.0 for remote access)")
computer_use_bridge.add_argument("--allowed-hosts", required=True,
help="Comma-separated allowed Host headers (required; include the port, e.g. host:8765)")
computer_use_bridge.add_argument("--allowed-origins", default="",
help="Comma-separated allowed browser Origins (default: none beyond --allowed-hosts)")
computer_use_bridge.add_argument("--session-idle-timeout", type=int, default=1800,
help="Seconds an idle bridge session is kept before teardown")
computer_use_doctor = computer_use_sub.add_parser(
"doctor", help="Run cua-driver `health_report` and surface the check matrix",
description="Drive cua-driver's stable `health_report` MCP tool and render\n"
Expand Down Expand Up @@ -181,7 +212,7 @@ def _cu_permissions(args):
computer_use_perms.print_help()

_actions = {"install": _cu_install, "status": _cu_status, "doctor": _cu_doctor,
"permissions": _cu_permissions}
"permissions": _cu_permissions, "host-bridge": _cu_host_bridge}

def cmd_computer_use(args):
handler = _actions.get(getattr(args, "computer_use_action", None))
Expand Down
Loading
Loading