feat(tools/computer_use): native per-OS desktop control (macOS / Linux / Windows) - #20660
feat(tools/computer_use): native per-OS desktop control (macOS / Linux / Windows)#20660Abd0r wants to merge 1 commit into
Conversation
…Windows) Three OS-specific tools — `computer_use_macos`, `computer_use_linux`, `computer_use_windows` — sharing one JSON schema and one set of action semantics, but with native backends per platform. Complementary to the containerised proposal in NousResearch#15876 (which targets the "Hermes-runs-in-Docker" deployment shape) and the macOS-Anthropic-protocol work in NousResearch#4562 / NousResearch#13308. This PR owns the "Hermes runs natively on the host desktop, control any of the three majors with consistent abstraction" shape. Architecture ============ * All three tools register at module top via `registry.register()` so the AST tool-discovery picks them up. `check_fn` returns False off the matching platform / when `HERMES_COMPUTER_USE_ENABLED` is unset / when required deps are missing — so on a given host the model only sees the one tool it can actually use. * `computer_use_common.py` — schema, `ActionRequest`, `ActionResult`, parameter validation, screen-bounds enforcement. * `computer_use_safety.py` — env gate, kill-switch flag, JSONL action log under `$HERMES_HOME/logs/computer_use.jsonl`, screenshot redaction (PIL). * `computer_use_grammar.py` — one parser, four targets. `Cmd+Shift+T` produces Quartz CGEvent flags+keycode on macOS, `xdotool key` string on X11, ydotool input event codes on Wayland, Win32 VK codes on Windows. * `computer_use_macos.py` — Quartz `CGEvent` for input, `screencapture` CLI for capture, `CGWindowListCopyWindowInfo` for active window. pyobjc-framework-Quartz is the only new dep. * `computer_use_linux.py` — runtime detection of X11 vs Wayland. X11 → `xdotool` + `scrot`/`import`. Wayland → `ydotool` + `grim` (wlroots) / `gnome-screenshot` / `spectacle`. Active-window queries via Sway IPC / hyprctl / xdotool depending on path. * `computer_use_windows.py` — `ctypes` over `user32.SendInput` (modern path; avoids legacy `keybd_event`). DPI-aware on import. Screenshot via `mss` if installed, falls back to ctypes BitBlt + PIL otherwise. Skills ====== Per-OS skill teaches the model what's actually different on each host: Cmd-vs-Ctrl, Spotlight vs Win+S, X11 vs Wayland detection, UAC/UIPI, accessibility / screen-recording perm setup, etc. The common skill covers when to reach for `computer_use_*` at all (vs `browser_tool` / `terminal`) and the screenshot-first discipline. Validation ========== * 56/56 unit tests passing (mocked Quartz / subprocess / user32 across all three backends + grammar + safety). * macOS backend integration-tested live on the author's MacBook: screen_size, cursor_position, get_active_window, screenshot (full), screenshot (region crop), screenshot (with redact), wait, off-screen click validation, type-without-text validation, unknown-action validation, env-off refusal — all 11/11 cases pass. * Linux + Windows are unit-test-only at the moment; author has no Linux or Windows host immediately available for end-to-end validation. Honest framing in the eventual PR body. Safety posture ============== * `HERMES_COMPUTER_USE_ENABLED=true` required. Default: refused. * Action allowlist + per-action validation (no off-screen, no >10K type strings, no >30s waits, no unknown actions). * Process-global kill-switch flag (`set_kill_switch()`) checked before every action — engaged once, all subsequent actions refuse until cleared. * JSONL audit log of every attempt (action, params minus image bytes, success bit, error if any). * `screenshot` action accepts `redact_regions` to blank rectangles (password manager, MFA codes) before the image reaches the model.
|
Tagging @f-trycua @ddupont808 @jamesmurdza for context — this PR proposes native per-OS desktop backends (macOS / Linux / Windows) inside Hermes, parallel to the cua-driver path we already integrate with (issue #24015, recently expanded for auxiliary.vision routing in #30126, and the bug-bundle cleanup in #24170). Two questions where your perspective would be useful before we decide direction here:
No urgency — just want to make sure we don't fork the desktop-control surface unnecessarily. Happy to wait on your read before this lands. |
The cua-driver backend was gated to macOS only:
# tools/computer_use/tool.py
def check_computer_use_requirements() -> bool:
if sys.platform != "darwin":
return False
...
But cua-driver itself has been Windows-feature-complete since cua-driver-rs
(the cross-platform Rust port) shipped its Windows backend. Every action
tool — click, type_text, hotkey, drag, scroll, screenshot, launch_app,
list_apps, list_windows, get_window_state, move_cursor, wait — is marked
VERIFIED on Windows in the cross-platform PARITY matrix:
https://github.com/trycua/cua/blob/main/libs/cua-driver-rs/PARITY.md
This PR widens the gate to `sys.platform in ("darwin", "win32")`. No new
code paths — the existing MCP stdio integration in cua_backend.py works
identically against cua-driver on Windows because cua-driver's tool
surface is uniform across OSes.
Linux is not in scope. cua-driver-rs Linux support exists in tree but is
alpha (most Linux rows in PARITY are OPEN, not VERIFIED) — keeping it gated
off here until upstream flips those to VERIFIED. The plumbing is
OS-agnostic so flipping the gate later is one-line.
Empirical verification on Windows 11 24H2 (2026-05-22 dogfood):
- Built-in Administrator (RID 500) at High IL via cua-driver-rs
RunLevel=Highest autostart task:
`cua-driver call get_window_state` for Calculator UWP
→ element_count: 41
- Regular admin (UAC-split, Medium IL primary token) running
`cua-driver call` directly from PowerShell:
`cua-driver call get_window_state` for Calculator UWP
→ element_count: 41
UWP / AppContainer UIA works at any IL for any user. No EV cert, no
uiAccess="true" manifest, no Program Files install requirement.
## Changes
- tools/computer_use/tool.py: replace `sys.platform != "darwin"`
early-return with `sys.platform not in ("darwin", "win32")`. Update
top-of-file docstring + vision-prompt phrasing ("macOS application" →
"desktop application") so the model isn't told to expect a Mac UI when
it's looking at a Windows screen.
- tools/computer_use/cua_backend.py: rewrite top-of-file docstring to
cover macOS + Windows + the Linux-alpha caveat. `is_available()`
matches the same `darwin/win32` allowlist. `cua_driver_install_hint()`
returns the Windows installer (irm | iex) on Windows, the bash
installer on macOS.
- tools/computer_use_tool.py: update registry description from "macOS
desktop control" to "desktop control (macOS, Windows; Linux alpha)".
The macOS-specific bits in `cua_backend.py` (the `_is_arm_mac` helper, the
"macOS reports localized app names" warning) stay as-is — they're macOS
runtime details that are conditionally taken when running on macOS, not
gates that block other OSes.
## Install
Same one-liner story, OS-specific installer:
macOS:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh)"
Windows (PowerShell):
irm https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1 | iex
After install, `cua-driver` is on $PATH and Hermes's check_fn sees it.
## Related
Replies to @teknium1's question on NousResearch#20660 about whether cua-driver-rs
ships Windows + Linux backends and whether @Abd0r's per-OS Python work
should be absorbed into cua-driver as a starting point. Short answer:
the cua-driver-rs Rust impl is months ahead of a fresh Python port on
Windows. Linux is alpha and will get there. Several pieces of NousResearch#20660
(kill-switch, JSONL audit log, screenshot redact_regions, the per-OS
SKILL.md docs) are worth absorbing into cua-driver as follow-up work —
separate from this PR.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks for the tag, @teknium1. Windows cua-driver has just shipped, pending some further tests in Hermes. Linux is still alpha.
Drafted what unified-backend looks like: #30660 - ~43 LOC across 3 files, widens the sys.platform != "darwin" gate to allow Windows. The existing cua_backend.py MCP stdio integration works identically on Windows because cua-driver's tool surface is uniform. Linux stays gated until cua-driver-rs Linux goes VERIFIED upstream (one-line flip when that happens). Re: absorbing the Python computer_use_windows.py - probably does not make sense as a starting point; the Rust impl in cua-driver-rs/crates/platform-windows is weeks ahead (Chromium/Electron click routing, UWP CoreWindow fallback, UIA cache batching, autostart at RunLevel=Highest). But @Abd0r's safety primitives are great and worth absorbing into cua-driver as follow-up: kill-switch, JSONL audit log, modifier-aliasing grammar (Cmd/Win/Super/Meta → canonical), screenshot redact_regions for password/MFA blanking. Thanks @Abd0r for the careful per-OS work! cc @ddupont808 |
The cua-driver backend was gated to macOS only:
# tools/computer_use/tool.py
def check_computer_use_requirements() -> bool:
if sys.platform != "darwin":
return False
...
But cua-driver itself has been Windows-feature-complete since cua-driver-rs
(the cross-platform Rust port) shipped its Windows backend. Every action
tool — click, type_text, hotkey, drag, scroll, screenshot, launch_app,
list_apps, list_windows, get_window_state, move_cursor, wait — is marked
VERIFIED on Windows in the cross-platform PARITY matrix:
https://github.com/trycua/cua/blob/main/libs/cua-driver-rs/PARITY.md
This PR widens the gate to `sys.platform in ("darwin", "win32")`. No new
code paths — the existing MCP stdio integration in cua_backend.py works
identically against cua-driver on Windows because cua-driver's tool
surface is uniform across OSes.
Linux is not in scope. cua-driver-rs Linux support exists in tree but is
alpha (most Linux rows in PARITY are OPEN, not VERIFIED) — keeping it gated
off here until upstream flips those to VERIFIED. The plumbing is
OS-agnostic so flipping the gate later is one-line.
Empirical verification on Windows 11 24H2 (2026-05-22 dogfood):
- Built-in Administrator (RID 500) at High IL via cua-driver-rs
RunLevel=Highest autostart task:
`cua-driver call get_window_state` for Calculator UWP
→ element_count: 41
- Regular admin (UAC-split, Medium IL primary token) running
`cua-driver call` directly from PowerShell:
`cua-driver call get_window_state` for Calculator UWP
→ element_count: 41
UWP / AppContainer UIA works at any IL for any user. No EV cert, no
uiAccess="true" manifest, no Program Files install requirement.
## Changes
- tools/computer_use/tool.py: replace `sys.platform != "darwin"`
early-return with `sys.platform not in ("darwin", "win32")`. Update
top-of-file docstring + vision-prompt phrasing ("macOS application" →
"desktop application") so the model isn't told to expect a Mac UI when
it's looking at a Windows screen.
- tools/computer_use/cua_backend.py: rewrite top-of-file docstring to
cover macOS + Windows + the Linux-alpha caveat. `is_available()`
matches the same `darwin/win32` allowlist. `cua_driver_install_hint()`
returns the Windows installer (irm | iex) on Windows, the bash
installer on macOS.
- tools/computer_use_tool.py: update registry description from "macOS
desktop control" to "desktop control (macOS, Windows; Linux alpha)".
The macOS-specific bits in `cua_backend.py` (the `_is_arm_mac` helper, the
"macOS reports localized app names" warning) stay as-is — they're macOS
runtime details that are conditionally taken when running on macOS, not
gates that block other OSes.
## Install
Same one-liner story, OS-specific installer:
macOS:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh)"
Windows (PowerShell):
irm https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.ps1 | iex
After install, `cua-driver` is on $PATH and Hermes's check_fn sees it.
## Related
Replies to @teknium1's question on #20660 about whether cua-driver-rs
ships Windows + Linux backends and whether @Abd0r's per-OS Python work
should be absorbed into cua-driver as a starting point. Short answer:
the cua-driver-rs Rust impl is months ahead of a fresh Python port on
Windows. Linux is alpha and will get there. Several pieces of #20660
(kill-switch, JSONL audit log, screenshot redact_regions, the per-OS
SKILL.md docs) are worth absorbing into cua-driver as follow-up work —
separate from this PR.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Superseded by #50552 — a comprehensive cross-platform cua-driver implementation (macOS/Windows/Linux) now merged to main. It covers the platform this PR targeted. Thanks for the contribution; closing as superseded. |
Summary
Adds three OS-specific desktop-control tools —
computer_use_macos,computer_use_linux,computer_use_windows— sharing one JSON schema and one set of action semantics, but with native backends per platform. Per-OS skills teach the model platform-specific shortcuts and idioms.This PR is complementary to the three existing computer-use efforts in flight; it owns a different deployment shape:
There's no overlap with #15876 (we're outside Docker, native to the host). There's surface overlap with #4562 / #13308 on macOS — but the value here is the consistent 3-OS surface using one schema and one grammar; the operator can run Hermes on any of the three majors and the model sees the same action vocabulary. Maintainers can land any combination of the four — they target genuinely different operator setups.
Problems this solves
Concretely, what does an operator gain by having
computer_use_*available?Drive native desktop apps that have no CLI or web surface — Adobe apps, Office desktop, native installers, system settings panels, finance/CAD/DAW apps. Today the agent has to give up or ask the user to do it manually. With this PR, the agent screenshots, clicks, types, and gets the job done.
Consistent abstraction across the three majors — same JSON schema, same action vocabulary, same grammar (
Cmd+Tabparses on every host; the parser routes to the correct OS primitive). An operator running Hermes on a personal Mac, a Linux workstation, and a Windows VM at work doesn't have to retrain prompts or learn three different tool surfaces.Native host control without a container — the containerised path in Proposal: Optional desktop computer-use module (noVNC + screenshot + mouse/keyboard control) #15876 is excellent for VPS / always-on / sandboxed deployments, but it's the wrong answer when the operator wants Hermes to drive their actual desktop: their logged-in browser sessions, their installed apps, their multi-monitor setup. This PR fills that gap.
Foundation for higher-level "GUI teaching" / record-and-replay efforts — issue [Feature]: GUI Teaching for Hermes Agent – Record mouse/keyboard workflows as executable skills (macOS) #19802 (Linka — record mouse/keyboard workflows on macOS, generate skills from demonstrations, replay them) needs a runtime primitive to execute recorded events. This PR provides exactly that primitive across all three OSes, so a Linka-style record-and-replay layer can sit on top without re-implementing per-OS native input.
Keeps Hermes provider-neutral — by emitting a Hermes-internal tool spec rather than the Anthropic computer-use-tool block, this works across every provider Hermes supports (OpenRouter, OpenAI, Anthropic, custom local) without protocol-specific plumbing in the tool layer.
Issues referenced
This PR doesn't auto-close any single issue (computer-use is a new capability, not a bug fix). Issues it relates to:
Architecture
tools/computer_use_common.py—ActionRequest/ActionResultdataclasses, shared JSON schema (mirrors Anthropic'scomputer_20251124action set with practical additions:get_active_window,screen_size,cursor_position), parameter validation, screen-bounds enforcement.tools/computer_use_safety.py—HERMES_COMPUTER_USE_ENABLEDenv gate (default off; tool refuses every action when unset), process-global kill-switch flag, append-only JSONL action log under\$HERMES_HOME/logs/computer_use.jsonl, screenshot redaction via PIL.tools/computer_use_grammar.py— one parser, four targets.Cmd+Shift+Tproduces QuartzCGEventflag mask + Carbon keycode on macOS, lowercasexdotool keyargument on X11, Linux input event-code sequence on Wayland, Win32 VK codes forSendInputon Windows. Modifier aliases collapse cross-platform (Cmd/Win/Super/Meta→ same canonical token), so a model trained against one OS's idiom routes correctly on the others.tools/computer_use_macos.py— QuartzCGEventCreateMouseEvent/CGEventCreateKeyboardEventfor input,screencaptureCLI for capture (always present on macOS),CGWindowListCopyWindowInfofor active-window queries. Only new dependency:pyobjc-framework-Quartz.tools/computer_use_linux.py— runtime detection of X11 vs Wayland from\$WAYLAND_DISPLAY/\$XDG_SESSION_TYPE. X11 path usesxdotool+scrot/ ImageMagickimport. Wayland path usesydotool+grim(wlroots) /gnome-screenshot/spectacle. Active-window queries: Sway IPC / hyprctl on Wayland,xdotool getactivewindowon X11.tools/computer_use_windows.py—ctypeswrapper overuser32.SendInput(modern path; legacykeybd_event/mouse_eventavoided). Per-monitor v2 DPI awareness set on import so click coordinates aren't scaled. Screenshot viamssif installed, falls back to a built-in ctypes BitBlt path so the tool works on a default Python install with no extra deps.All three OS tools call
registry.register()at module top so the AST tool-discovery picks them up. Each tool'scheck_fnreturnsFalseoff the matching platform / whenHERMES_COMPUTER_USE_ENABLEDis unset / when required deps are missing — so on any given host the model only sees the one tool it can actually use, never the other two.Skills
Per-OS skill teaches the model what's actually different on each host:
skills/computer-use/common/SKILL.md— when to reach forcomputer_use_*at all (vsbrowser_tool/terminal), screenshot-first discipline, cost discipline (token budget on base64 PNGs),redact_regionsusage.skills/computer-use/macos/SKILL.md— Cmd-vs-Ctrl, Spotlight (Cmd+Space) idiom, Mission Control, Accessibility + Screen Recording permission setup, multi-monitor caveats, things that won't work (Touch ID prompts, system password dialogs).skills/computer-use/linux/SKILL.md— X11 vs Wayland detection, ydotoold + uinput setup, DE-specific shortcuts (GNOME / KDE / wlroots / Xfce), polkit / pkexec lockout warnings.skills/computer-use/windows/SKILL.md— Win+S as Spotlight analogue, UAC / UIPI restrictions on synthetic input to elevated windows, DPI awareness, mss vs BitBlt screenshot tradeoff.Validation
Unit tests: 56 / 56 passing (
tests/tools/test_computer_use.py). Mocked Quartz, subprocess, and ctypes user32. Coverage:macOS integration on the author's MacBook (live, real Quartz, real
screencapture): 11 / 11 cases pass —```
✓ screen_size → {1470, 956}
✓ cursor_position → real cursor
✓ get_active_window → frontmost app correctly identified
✓ screenshot full → 575,589-byte real PNG round-trips through base64
✓ screenshot region → 6,171-byte cropped PNG (200×200)
✓ screenshot redact → redacted PNG (PIL fills rectangles black)
✓ wait → 50ms sleep returns clean result
✗ off-screen click (rejected) → validation error with screen bounds
✗ type empty (rejected) → validation error with field name
✗ unknown action (rejected) → validation error listing valid actions
✗ env-off refusal → 'refused: HERMES_COMPUTER_USE_ENABLED is not set'
```
Action log JSONL writes verified.
Linux + Windows: unit-test-only. Author has no Linux or Windows host immediately available for live integration validation. Mocked-subprocess and mocked-ctypes coverage exercises every code path, but I want to be honest that real-host validation hasn't happened yet. Happy to run live tests once a tester is available, or for maintainers to gate landing the Linux/Windows files on community validation.
Safety posture
Test plan
🤖 Generated with Claude Code