Feat(tools): add Windows UIA backend for computer_use - #43927
Conversation
Brings desktop control to Windows hosts: UI Automation element discovery with SOM overlays, SendInput mouse/keyboard (virtual- desktop-normalized absolute coords, Unicode typing), and focus-free set_value via UIA value/selection/range patterns. Backend selection is platform-aware (HERMES_COMPUTER_USE_BACKEND still overrides) and check_computer_use_requirements() now gates per platform. Windows session-killing key combos (win+l, ctrl+alt+del, alt+f4) are hard-blocked alongside the macOS list. Unlike cua-driver on macOS there is no background input injection on Windows: pointer/keyboard actions briefly foreground the target window, and the platform-aware tool schema tells the model so. Requires uiautomation (+comtypes) in the venv; windows_backend degrades to unavailable when imports fail. 118 computer_use tests pass incl. 21 new dependency-free Windows tests; verified live against Notepad (capture/SOM/type/set_value/key). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The injected guidance block hardcoded macOS background-control rules (do-not-steal-focus, do-not-raise-windows). On Windows that is backwards: pointer and keyboard actions foreground the target window. Select Windows-specific guidance on win32 - foreground behavior, set_value as the focus-free path, cmd to ctrl and win mapping, and the Windows blocked combos - so the model is told the truth about how its actions behave on this host. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Visible "PC use mode": a persistent banner pill while desktop control is active, the numbered SOM element boxes mirrored onto the real screen after each capture, click ripples / drag arrows where actions land, and short action flashes (typing, key combos, scroll). overlay.py runs as a subprocess: a fullscreen transparent click-through topmost tkinter window spanning the virtual desktop, driven over localhost UDP, excluded from screen capture via SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE) so the model's own screenshots never contain it (verified by pixel-sampling a capture taken while a box was on screen). The overlay returns foreground focus after spawning, and the backend never targets the overlay process as a capture subject. All overlay traffic is fire-and-forget: any failure disables the overlay without affecting actions. Disable with HERMES_COMPUTER_USE_OVERLAY=0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full-resolution desktop captures (1920x1032+) tokenize to thousands of vision tokens and overflow small local vision models' context windows - the aux call came back "the vision API rejected the image" and the model got no description at all. Cap the long side at 1456px before writing the temp image for vision_analyze: SOM badges stay legible, the request fits comfortably, and per-capture vision latency drops roughly in half. Also drop the hardcoded "macOS" from the describe prompt now that captures come from Windows hosts too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Win32-only marker with a <3 ceiling per dependency policy; comtypes arrives transitively. Non-Windows installs are unaffected - the backend availability check degrades gracefully when the import is absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract the capture downscale into _shrink_capture_for_vision so it is unit-testable without the aux-vision plumbing, and add dependency-free tests for it (oversize shrinks with aspect preserved, small and non-image bytes pass through untouched) plus the overlay client's fail-safe contract (env kill switch spawns nothing, sends before start or after death are silent no-ops). Also update the one capture-routing assertion that pinned the literal "macOS application screenshot" prompt wording, which became platform-neutral when Windows hosts started producing captures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Comment (high-surface-area, full review deferred)
Feature: Add Windows UIA backend for computer_use
This is a large PR (1,801 additions / 40 deletions, 2,137 diff lines) touching a new platform backend. In cron batch mode I cannot do a thorough deep review of this scope within available resources.
Surface area concerns (require human review):
- Windows UIA automation spans many files — inspect the integration with existing computer_use tool abstractions for regressions
- Error handling in the UIA interaction layer
- Whether existing tests cover the new platform path
This is a feature PR, not a security or critical fix. Please treat this as an informational comment — the PR may be mergeable but warrants a focused human review before merge.
Reviewed by Hermes Agent (batch mode — high-surface-area, full review deferred)
Three failure modes found preparing for real daytime use on a shared desktop: 1. Vision-node outage broke captures outright. When aux-vision routing is requested (the main model cannot consume images) and the aux call fails, the old fallthrough returned the multimodal envelope - putting a screenshot in front of a text-only model and erroring the capture. Degrade to the AX/SOM text payload instead (vision_unavailable flag set): element-index actions keep working blind until vision returns. 2. Stale coordinates after a window move. Element bounds are absolute screen coords frozen at capture time; dragging the window between capture and click landed clicks on whatever sat at the old position. Track the captured window rect and translate element centers by the origin delta; a resize (interior layout changed) fails with an explicit re-capture message instead of guessing. 3. Input collisions with an active user. Synthetic input lands in whatever has focus; injecting mid-keystroke sprays input across both parties' targets. All input actions now wait for a short user-idle window (HERMES_COMPUTER_USE_IDLE_WAIT, default 1.5s, 0 disables), capped at 8s so the agent yields but never deadlocks. Routing tests updated for the new degradation contract; new tests cover all three behaviors and run dependency-free off-Windows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed one more commit (569c4af) — hardening from the first day of real daily-driver use on a shared desktop, before this gets reviewed:
Routing tests updated for the new degradation contract (aux-failure fallback for non-vision mains is text, not multimodal) + 8 new tests, all dependency-free off-Windows. Full computer_use suites: 130 passed on Windows 11. Found all three the honest way: running it for real on the machine I work at. Still waiting on a workflow approval whenever a maintainer gets a chance 🙏 |
|
Nice work — I was building the same thing at #45976 (closed now as duplicate). During review I spotted a few things that might help here:
|
|
Thanks @Icather — all three confirmed and fixed in eba52edf5:
Added 7 dependency-free tests, including one that asserts capture and |
Addresses review feedback on the Windows computer_use backend (NousResearch#43927). 1. A failed click/drag/scroll left modifier keys - and, for drag, the mouse button - synthetically held down: the release ran after the action inside the same try block, so an injection error skipped it. Move the release into a finally so Ctrl/Alt/Shift and the button are always released and the cursor restored even when an injection raises. 2. capture (_walk_elements) and set_value (_control_at_index) each reimplemented the same BFS + interactability filter; if the two ever diverged, set_value would resolve an index to a different control than the capture advertised. Both now consume one _iter_interactable generator, so element #N is the same control in both paths. 3. That shared walk uses collections.deque.popleft() instead of the O(n) list.pop(0). 7 new dependency-free tests (modifier/button release on failure, capture/set_value index agreement, BFS order, Text-pattern filter); they pass off-Windows. Full computer_use suites: 137 passed on Windows 11. Thanks to @Icather for spotting all three and proposing the fixes (originally raised in NousResearch#45976). Suggested-by: ChengLong Han <97326386+Icather@users.noreply.github.com> Co-authored-by: ChengLong Han <97326386+Icather@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
eba52ed to
97ddb8d
Compare
|
Been running this daily on Windows 10 — the overlay (numbered element boxes, click ripples, action flashes) is the best part. The visual feedback makes computer_use actually usable for a human sharing the desktop, not just the agent. Everything works: screenshots, click/drag/scroll, Unicode typing, set_value. Tested against Notepad, Explorer, and the Windows shell. Happy to help test on different Windows configs if needed. |
|
yoooo lets get this in i need |
|
Found one more issue while daily-driving this: Fix: stop the overlay before switching → switch desktop → restart overlay on the new desktop. The overlay client already has Happy to share the code if you want to fold it in. |
|
Yes please — let's fold it in. You've been daily-driving this on configs I can't easily reach, and that Two easy paths, whichever's less friction for you:
Either way the stop → switch → restart approach sounds right — the overlay client's |
Stop the overlay subprocess before switching virtual desktops, then restart it on the new desktop — avoids the tkinter display context teardown that kills the overlay during SendInput-based Ctrl+Win+Left/Right. - _switch_desktop_via_keybd(): stop overlay → two-phase SendInput → restart overlay - switch_desktop() method on WindowsUIABackend - Schema and dispatch updated with 'switch_desktop' action and 'direction' parameter Co-authored-by: lEWFkRAD
Two-batch SendInput (press → sleep → release) crashes the embedded gateway (Dashboard Chat tab) because its single-channel event loop cannot handle multi-batch keyboard injection without disrupting the PTY pipeline. The full system gateway is unaffected because its multi-client dispatch loop handles concurrent channels. Switch to single-batch SendInput matching _press_combo semantics (hold modifiers → tap arrow → release). This works in both embedded and full gateway modes.
- Validate direction before key dispatch (unknown values return False) - Log exceptions instead of silently swallowing them - Add 150ms delay before overlay restart to avoid DWM race - Route switch_desktop through _maybe_follow_capture for consistency - Add JSON Schema if/then to require direction for switch_desktop
|
Submitted as a PR against your branch: lEWFkRAD#1 Single-commit addition — overlay-safe virtual desktop switching via SendInput. Went through several rounds of cross-gateway-mode testing (embedded Chat tab vs full system gateway) and one Copilot review pass. Should be ready to merge on your end whenever you're comfortable. |
…itch switch_desktop stops the overlay subprocess before a virtual-desktop transition and restarts it after, but it force-cleared _OverlayClient._dead before start() — resurrecting an overlay the user had disabled with HERMES_COMPUTER_USE_OVERLAY=0 (or one that had already failed itself off) on every switch. Gate the restart on whether the overlay was actually running before the switch, so a deliberately-killed overlay stays down. start() already no-ops while _dead is set, so the guard is the single source of truth. Adds dependency-free tests for the live-restart, disabled-stays-down, and invalid-direction paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Update — folded in a contributed action and a follow-up fix since the last review pass:
Totals now: 12 commits, computer_use suites 140 passed on Windows 11 (10.0.26200), |
|
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. |
What does this PR do?
Adds a Windows backend for the
computer_usetoolset, which is currently macOS-only (cua-driver). Windows hosts get the same model-agnostic desktop control through the existingComputerUseBackendABC: SOM captures with numbered element overlays, element-index clicking, drag/scroll, Unicode typing, key combos, and focus-freeset_value— driven by any tool-capable model through the existing universal schema, unchanged.The approach mirrors the repo's own architecture (
backend.pyexplicitly anticipates "future Linux/Windows" implementations): a sibling backend behind the same ABC, selected per-platform withHERMES_COMPUTER_USE_BACKENDstill overriding. Element discovery andset_valuego through UI Automation (the Windows analogue of the AX tree, via the pure-pythonuiautomationpackage); screenshots through Pillow (already a core dep); input synthesis through ctypesSendInput(no new dep). All coordinates are physical pixels with per-monitor-v2 DPI awareness.One honest platform difference, reflected in the platform-selected schema description and system-prompt guidance: Windows has no supported background input injection, so pointer/keyboard actions briefly foreground the target window.
set_valueis the exception (UIA patterns work unfocused).cmdmaps to Ctrl for cross-platform model habits; Windows session-killers (win+l,ctrl+alt+del,alt+f4) join the hard-blocked combo list.Also included:
SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE), strictly fire-and-forget (overlay failure can never affect actions), kill switchHERMES_COMPUTER_USE_OVERLAY=0;Related Issue
No existing issue; this implements the Windows direction anticipated in
tools/computer_use/backend.py's module docstring. Happy to open a tracking issue if preferred.Type of Change
Changes Made
tools/computer_use/windows_backend.py— new:WindowsUIABackend(UIA element walk with node/depth/time budgets; PIL screenshot + SOM overlay; ctypes SendInput layer with virtual-desktop-normalized absolute coords, UTF-16-aware Unicode typing, batched key combos; window enum/focus with foreground-lock workaround; UIA Value/SelectionItem/RangeValueset_valuewith COM-safe re-find — live COM pointers are never reused across tool calls), plus the_OverlayClientand availability check.tools/computer_use/overlay.py— new: optional overlay subprocess (tkinter, transparent/click-through/topmost, UDP-driven, capture-excluded).tools/computer_use/tool.py— platform-aware backend selection;check_computer_use_requirements()gates per platform; Windows blocked key combos +winaliases;_shrink_capture_for_vision()before aux-vision routing.tools/computer_use/schema.py,tools/computer_use_tool.py,toolsets.py— platform-selected descriptions (previously told every model "macOS only").agent/prompt_builder.py—COMPUTER_USE_GUIDANCEplatform-selected; the macOS text instructs the model it has background control and must not raise windows, which is exactly backwards on Windows.pyproject.toml+uv.lock—uiautomation>=2.0.29,<3; sys_platform == 'win32'(comtypes transitive; +23 lock lines).tests/tools/test_computer_use_windows.py— new: 27 tests, dependency-free (win32 modules stubbed; collects and passes on Linux).tests/tools/test_computer_use.py,tests/tools/test_computer_use_capture_routing.py— two assertions updated where they pinned macOS-only behavior (check_fn gate, prompt wording).How to Test
uv sync, thenhermes -z "Use computer_use: list_apps, then capture mode='som' and describe the foreground window and three elements by index."— requires no config; the toolset gates on viacheck_computer_use_requirements().hermes -z "focus_app 'notepad' raise_window=true, capture som, click the editor element, type a line, re-capture and quote the text back."pytest tests/tools/test_computer_use_windows.py -q— all tests collect and pass off-Windows (win32 imports are stubbed/guarded); macOS behavior is untouched (cuaremains the darwin default).Verified on Windows 11 (10.0.26200), Python 3.12.10:
pytest tests/tools/test_computer_use_windows.py tests/tools/test_computer_use.py tests/tools/test_computer_use_capture_routing.py -q→ 124 passed.scripts/check-windows-footguns.py --diff origin/main→ clean (9 files).hermes -z, qwen-class model on local vLLM): focus → SOM capture (26 elements) → element click → type → re-capture verified the typed text from the window title, tab label, and status bar. Capture exclusion of the overlay verified by pixel-sampling a capture taken while overlay boxes were on screen (0 overlay pixels found).Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass — fulltests/tools/run on Windows; one pre-existing, unrelated collection error intests/tools/test_search_hidden_dirs.py(missing external binary on this host, fails identically onorigin/main) excludedDocumentation & Housekeeping
cli-config.yaml.exampleif I added/changed config keys — N/A (no new config keys; env varsHERMES_COMPUTER_USE_BACKENDpre-existing,HERMES_COMPUTER_USE_OVERLAYdocumented in code)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AScreenshots / Logs
Agent-driven live verification (one-shot
hermes -z, local vLLM model):Capture-exclusion proof (overlay): screenshot taken while overlay boxes were drawn on screen; sampling the box edge found 0 overlay-colored pixels in the captured image while the boxes were visibly on the desktop.