@bastani/atomic 0.9.56-alpha.1 → 0.9.57-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +24 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/agent-desktop/SKILL.md +83 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +12 -0
- package/dist/builtin/workflows/builtin/adversarial-verification.js +3 -3
- package/dist/builtin/workflows/builtin/{chunk-49kkbdpr.js → chunk-2f1shjah.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-dcxscfnb.js → chunk-37gm3bzk.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-2jfst7e4.js → chunk-45fn5q5p.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-hx8v4p97.js → chunk-4ek3v2wm.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-z3pajrfs.js → chunk-8d40nyz9.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-qhm35ntx.js → chunk-g3b2zf8r.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-6jpwc09k.js → chunk-h6bt0r0g.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-9cm2ckq2.js → chunk-x25rjw4y.js} +4 -4
- package/dist/builtin/workflows/builtin/{chunk-apagcs4r.js → chunk-xqcy5nve.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-w4xp1w1f.js → chunk-yb55w6me.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-k5q3t7je.js → chunk-yjgfnbgd.js} +1 -1
- package/dist/builtin/workflows/builtin/classify-and-act.js +3 -3
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize.js +3 -3
- package/dist/builtin/workflows/builtin/generate-and-filter.js +3 -3
- package/dist/builtin/workflows/builtin/goal.js +3 -3
- package/dist/builtin/workflows/builtin/index.js +11 -11
- package/dist/builtin/workflows/builtin/loop-until-done.js +3 -3
- package/dist/builtin/workflows/builtin/open-claude-design.js +3 -3
- package/dist/builtin/workflows/builtin/ralph.js +3 -3
- package/dist/builtin/workflows/builtin/steering-context.js +1 -1
- package/dist/builtin/workflows/builtin/tournament.js +3 -3
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +7 -7
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +4 -4
- package/dist/builtin/workflows/src/index.js +4 -4
- package/dist/builtin/workflows/src/local-worker-entry.mjs +4 -4
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +2 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/docs/computer-use.md +119 -98
- package/docs/getting-started/first-session.md +1 -1
- package/docs/skills.md +2 -2
- package/docs/usage.md +1 -1
- package/docs/workflows/authoring.md +105 -141
- package/docs/workflows/verification.md +14 -13
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
- package/dist/builtin/subagents/skills/cua-driver/BROWSER.md +0 -562
- package/dist/builtin/subagents/skills/cua-driver/EMBEDDING.md +0 -685
- package/dist/builtin/subagents/skills/cua-driver/LINUX.md +0 -412
- package/dist/builtin/subagents/skills/cua-driver/MACOS.md +0 -996
- package/dist/builtin/subagents/skills/cua-driver/README.md +0 -123
- package/dist/builtin/subagents/skills/cua-driver/RECORDING.md +0 -132
- package/dist/builtin/subagents/skills/cua-driver/RUNTIME.md +0 -115
- package/dist/builtin/subagents/skills/cua-driver/SKILL.md +0 -108
- package/dist/builtin/subagents/skills/cua-driver/VISUAL.md +0 -125
- package/dist/builtin/subagents/skills/cua-driver/WINDOWS.md +0 -832
- package/dist/builtin/subagents/skills/cua-driver/WORKFLOW.md +0 -226
|
@@ -1,832 +0,0 @@
|
|
|
1
|
-
# cua-driver: Windows
|
|
2
|
-
|
|
3
|
-
Orchestrates Windows app automation via the `cua-driver` binary (`cua-driver.exe`). Whenever a user
|
|
4
|
-
asks to drive a native Windows app, follow the loop in this doc
|
|
5
|
-
rather than calling tools ad-hoc: the snapshot-before-action
|
|
6
|
-
invariant is not optional and silently breaks if you skip it.
|
|
7
|
-
|
|
8
|
-
`SKILL.md` in this directory describes the cross-platform core;
|
|
9
|
-
this file is the Windows-specific extension. Read both:
|
|
10
|
-
the snapshot invariant, MCP-vs-CLI choice, agent cursor overlay, and
|
|
11
|
-
recording flow are identical. The launch, click, and accessibility-
|
|
12
|
-
tree mechanics in this file replace the macOS ones.
|
|
13
|
-
|
|
14
|
-
## Background window delivery
|
|
15
|
-
|
|
16
|
-
**Background window actions must preserve the user's frontmost app.**
|
|
17
|
-
Foreground and desktop actions follow the authorization boundary in
|
|
18
|
-
[RUNTIME.md](RUNTIME.md#foreground-boundary). Users pay for the right
|
|
19
|
-
to keep typing in their editor while an agent drives another app in
|
|
20
|
-
the background. Violate this rule and every other nice property the
|
|
21
|
-
driver gives you (no cursor warp, no taskbar flash, no window
|
|
22
|
-
restore-and-raise) stops mattering: you just shipped a `SendInput`
|
|
23
|
-
wrapper with extra steps.
|
|
24
|
-
|
|
25
|
-
### How the contract is enforced per call: the `delivery_mode` field
|
|
26
|
-
|
|
27
|
-
Every Windows input tool (`click`, `double_click`, `right_click`,
|
|
28
|
-
`drag`, `scroll`, `press_key`, `hotkey`, `type_text`) accepts an
|
|
29
|
-
optional `delivery_mode` field: this mirrors the macOS `delivery_mode`
|
|
30
|
-
surface (same name, same two values). The default is `"background"`:
|
|
31
|
-
strict no-foreground:
|
|
32
|
-
|
|
33
|
-
| `delivery_mode` | Behavior on Windows |
|
|
34
|
-
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
-
| `"background"` (DEFAULT) | Never fronts and **never raises/restacks** the target: macOS-aligned (mirrors CGEvent-to-pid). **Pixel clicks**: a UIA hit-test at the point first (accessibility-channel Invoke, works on UWP / WinUI3 / Win11 packaged apps, no flash); if that misses, coordinate-injected pen/touch, **but only when the target is the _visible_ window at that point**; PostMessage for plain Win32. It returns a structured `background_unavailable` error (rather than raising or fronting) when the target is **occluded** at the point, or the event kind is known-dropped (Chromium DOM mouse + key-combos, GTK buttons, VCL/LibreOffice accelerators, terminal / WPF text with no `element_token`). **No foreground swap and no z-order raise, ever.** |
|
|
36
|
-
| `"foreground"` | SendInput with brief `SetForegroundWindow(target)` → restore. The explicit, agent-chosen rung where fronting IS allowed: required to reach occluded targets, Chromium DOM content, GTK buttons, VCL accelerators, WPF drag, terminals, and canvas / custom-drawn surfaces with no UIA peer. Implemented for **every** input tool: `type_text` (SendInput Unicode via `send_text_synthesized`) and `scroll` (SendInput wheel via `send_wheel_synthesized`) included. The activation and restoration are scoped to that action. |
|
|
37
|
-
|
|
38
|
-
> **macOS is the source of truth: `background` never alters the screen.**
|
|
39
|
-
> Earlier Windows builds "cheated" in background with three tricks that this
|
|
40
|
-
> pass **removed**: (1) a z-order raise (`ZorderGuard`) to win the pointer
|
|
41
|
-
> hit-test on occluded windows, (2) a full focus-activate for WPF drags, and
|
|
42
|
-
> (3) a _cloaked_ (hidden) focus-grab for keystrokes/text the target would
|
|
43
|
-
> otherwise drop. macOS does none of these (pure CGEvent-to-pid + focus
|
|
44
|
-
> suppression), so Windows now does none either: when strict no-front /
|
|
45
|
-
> no-raise delivery can't land, the tool returns `background_unavailable` and
|
|
46
|
-
> **the agent (not the driver) decides** whether to escalate to
|
|
47
|
-
> `delivery_mode:"foreground"` (the rung where fronting is explicitly opted
|
|
48
|
-
> into). A `background` call will never raise, restack, flash, or steal focus.
|
|
49
|
-
|
|
50
|
-
> **Removed: the legacy `"auto"` mode.** Earlier builds had a third
|
|
51
|
-
> Windows-only `dispatch:"auto"` mode (silent SendInput fallback on
|
|
52
|
-
> known-problematic targets). It was removed in the macOS-alignment pass
|
|
53
|
-
> because it could front the target _without the caller opting in_:
|
|
54
|
-
> breaking the no-foreground contract macOS guarantees. Any unrecognised
|
|
55
|
-
> value (including a stray `"auto"`) now resolves to `"background"`. If you
|
|
56
|
-
> have notes/snippets that pass `dispatch:"auto"`, switch to an explicit
|
|
57
|
-
> `delivery_mode:"foreground"` for the cases that need fronting.
|
|
58
|
-
|
|
59
|
-
### Always try `delivery_mode:"background"` first
|
|
60
|
-
|
|
61
|
-
The hit-test fallback above means **`delivery_mode:"background"` is the correct
|
|
62
|
-
default even when the target is a XAML host whose input stack drops raw
|
|
63
|
-
PostMessage** (UWP Calculator, Win11 Notepad, WinUI3 apps, etc.). cua-driver
|
|
64
|
-
turns the pixel coord into a UIA Invoke at that point and delivers
|
|
65
|
-
through the accessibility channel: no flash, no focus steal. Only
|
|
66
|
-
escalate to `delivery_mode:"foreground"` when you actually see a
|
|
67
|
-
`background_unavailable` structured error. Retry only that action with
|
|
68
|
-
`delivery_mode:"foreground"`; cua-driver briefly activates the target, delivers
|
|
69
|
-
the input, and restores the previous foreground.
|
|
70
|
-
|
|
71
|
-
Empirical: pixel-clicks via `delivery_mode:"background"` against the UWP
|
|
72
|
-
Calculator on Win11 (Number-pad buttons + operators) consistently
|
|
73
|
-
resolve through `try_invoke_in_window_at_point` and produce
|
|
74
|
-
`"✅ Performed UIA Invoke at (sx,sy) for pid X."` with zero visible
|
|
75
|
-
flash. `delivery_mode:"foreground"` on the same coords also works but
|
|
76
|
-
flashes the Calculator window foreground for ~40 ms: it's the
|
|
77
|
-
costlier path; only use it for surfaces with no UIA peer.
|
|
78
|
-
|
|
79
|
-
`background_unavailable` error shape:
|
|
80
|
-
|
|
81
|
-
```json
|
|
82
|
-
{
|
|
83
|
-
"isError": true,
|
|
84
|
-
"structuredContent": {
|
|
85
|
-
"code": "background_unavailable",
|
|
86
|
-
"target_class": "Chrome_WidgetWin_1",
|
|
87
|
-
"event_kind": "mouse_click",
|
|
88
|
-
"escalation": { "recommended": "foreground", "reason": "occluded / known-dropped event kind" },
|
|
89
|
-
"suggestion": "Retry this action with delivery_mode:\"foreground\"."
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Errors retain their diagnostic `escalation.recommended` hint. Successful
|
|
95
|
-
action results use the narrower `escalation.target` contract instead (see
|
|
96
|
-
`SKILL.md` → behavior matrix). On Windows this error recommendation is
|
|
97
|
-
`"foreground"` because the dropped event needs the fronting rung. (Contrast
|
|
98
|
-
macOS / X11, where a background pixel click can still land in the background.)
|
|
99
|
-
|
|
100
|
-
The normal flow when an agent gets that error:
|
|
101
|
-
|
|
102
|
-
1. Confirm the user authorized visible control, then reissue only the refused action with `delivery_mode:"foreground"`.
|
|
103
|
-
2. cua-driver activates the target, delivers through SendInput, and restores
|
|
104
|
-
the previous foreground.
|
|
105
|
-
3. Continue with `delivery_mode:"background"` for later actions unless they
|
|
106
|
-
are also refused.
|
|
107
|
-
|
|
108
|
-
### Persistent focus-proxy exception
|
|
109
|
-
|
|
110
|
-
`bring_to_front` is not part of the normal input ladder. Use it only when a
|
|
111
|
-
focus-proxy surface must remain foreground across multiple calls, such as an
|
|
112
|
-
RDP or Windows App session, or when repeated action-scoped activation prevents
|
|
113
|
-
the remote surface from accepting input.
|
|
114
|
-
|
|
115
|
-
The `bring_to_front` tool uses an `AttachThreadInput` trick to _attempt_
|
|
116
|
-
the foreground swap even when the daemon isn't at UIAccess integrity (the
|
|
117
|
-
same trick that powers `send_key_synthesized`). Returns
|
|
118
|
-
`{previous_fg_hwnd, now_fg_hwnd, landed_on_target}`: **check
|
|
119
|
-
`landed_on_target`**. Without UIAccess, Windows' foreground-lock can still
|
|
120
|
-
reject the swap (and a subsequent `delivery_mode:"foreground"` call will
|
|
121
|
-
bail with the "Foreground swap … was rejected by Windows" diagnostic
|
|
122
|
-
rather than landing input on the wrong window). When that happens the
|
|
123
|
-
target genuinely cannot be driven by SendInput/keystrokes in this session:
|
|
124
|
-
use an interactively launched High-IL daemon. The reserved `cua-driver-uia`
|
|
125
|
-
worker is a daemon-internal, default-off service boundary and public clients
|
|
126
|
-
must never connect to its pipe directly. Alternatively, for tasks
|
|
127
|
-
that produce a file: generate the document and `launch_app` it instead of
|
|
128
|
-
driving the GUI (e.g. building a spreadsheet and opening it in LibreOffice
|
|
129
|
-
Calc rather than typing into the grid, which is dropped on the VCL
|
|
130
|
-
background path).
|
|
131
|
-
|
|
132
|
-
Before running any shell command, ask: **"does this raise, activate,
|
|
133
|
-
foreground, or steal focus from any app?"** If yes, don't run it.
|
|
134
|
-
Every one of the commands below activates the target on Windows and
|
|
135
|
-
is therefore forbidden unless the user **explicitly** asked for
|
|
136
|
-
frontmost state:
|
|
137
|
-
|
|
138
|
-
- **`Start-Process <exe>` / `Start-Process <url>` / `Start-Process
|
|
139
|
-
-FilePath ...`**: defaults to launching with `SW_SHOWNORMAL` which
|
|
140
|
-
_activates_ the new window. Windows treats new processes as
|
|
141
|
-
user-initiated foreground apps. The CmdLine flag `-WindowStyle Hidden`
|
|
142
|
-
helps but does not block activation for apps that call
|
|
143
|
-
`SetForegroundWindow` themselves on startup (Edge, most browsers,
|
|
144
|
-
Office, most installers). **Never use `Start-Process` to launch a
|
|
145
|
-
visible app.** Use `launch_app`: it goes through
|
|
146
|
-
`SW_SHOWNOACTIVATE` and wraps an internal `VK_NONAME keybd_event`
|
|
147
|
-
trick that the OS treats as "user activity from another window," so
|
|
148
|
-
the target's own `SetForegroundWindow` call is denied per Windows
|
|
149
|
-
focus-stealing prevention rules.
|
|
150
|
-
- **`& "C:\path\to\app.exe"` from PowerShell**: same as
|
|
151
|
-
`Start-Process` on activation. PowerShell's `&` invocation operator
|
|
152
|
-
spawns the process with foreground intent.
|
|
153
|
-
- **`cmd /c start <thing>` and `start /b <thing>`**: `start` on
|
|
154
|
-
Windows is the equivalent of macOS's `open`: it goes through the
|
|
155
|
-
shell's protocol-handler / file-association lookup and activates
|
|
156
|
-
the resulting process. `start /min` helps for taskbar minimization
|
|
157
|
-
but still activates the new window before minimizing it (flash
|
|
158
|
-
visible to the user). Forbidden for the same reason.
|
|
159
|
-
- **`explorer.exe shell:AppsFolder\<AUMID>` / `explorer.exe ms-edge:
|
|
160
|
-
<url>`**: these are the Windows-shell equivalents of `open -a` /
|
|
161
|
-
`open <url>` on macOS. They go through `IApplicationActivationManager`
|
|
162
|
-
with the wrong activation kind and foreground the target. Use
|
|
163
|
-
`launch_app({aumid})` or `launch_app({urls})` instead: those route
|
|
164
|
-
through `IApplicationActivationManager::ActivateApplication` with
|
|
165
|
-
the correct flag combination that respects `SW_SHOWNOACTIVATE`.
|
|
166
|
-
- **`SetForegroundWindow(hwnd)` / `BringWindowToTop(hwnd)` /
|
|
167
|
-
`SwitchToThisWindow(hwnd, fAltTab)` / `ShowWindow(hwnd, SW_SHOW)` /
|
|
168
|
-
`SetActiveWindow(hwnd)`**: all foreground the named window by
|
|
169
|
-
definition. Never call these from Bash via PowerShell add-types or
|
|
170
|
-
C# inline. If you find yourself doing this to "make a click land,"
|
|
171
|
-
the click is already wrong: re-read "Click semantics" below.
|
|
172
|
-
- **`AttachThreadInput` + `SetForegroundWindow` trickery**: the
|
|
173
|
-
classic Windows focus-bypass hack. Even when it works it's a
|
|
174
|
-
visible focus pop and a UIPI/UIAccess violation. Forbidden.
|
|
175
|
-
- **`SendInput(MOUSEEVENTF_*)` over the user's real cursor**: moves
|
|
176
|
-
the cursor and synthesizes input. The cursor warps to coordinates,
|
|
177
|
-
any handler in the topmost window at those coordinates fires, and
|
|
178
|
-
for most apps the receiving window activates because input arrived
|
|
179
|
-
from the OS-trusted pipeline. Use `click({pid, x, y})` or
|
|
180
|
-
`click({pid, element_token})` instead: both route per-pid and
|
|
181
|
-
never touch the OS cursor.
|
|
182
|
-
- **`SendInput(KEYBDINPUT)` with no target HWND**: same idea, it goes
|
|
183
|
-
to the focused window, not your target. Use `hotkey({pid, keys:
|
|
184
|
-
[...]})` which uses `PostMessage(WM_KEYDOWN/UP)` to the named pid's
|
|
185
|
-
focused window.
|
|
186
|
-
- **Keyboard shortcuts that semantically mean "focus here":
|
|
187
|
-
Chromium / Edge / Firefox `Ctrl+L` (focus address bar),
|
|
188
|
-
Explorer's `Ctrl+L` / `Alt+D` (focus path bar), `F6` (focus
|
|
189
|
-
cycle).** These aren't pure key events: the receiving app
|
|
190
|
-
interprets "user wants to type here" as activation intent and
|
|
191
|
-
raises its window to be key. Even when delivered to a backgrounded
|
|
192
|
-
pid via `hotkey`, the downstream app pulls focus. **For omnibox
|
|
193
|
-
navigation specifically**, the correct path is `launch_app({path:
|
|
194
|
-
"...msedge.exe", additional_arguments: ["https://example.com/"]})`.
|
|
195
|
-
This hands the URL to the selected browser process; no omnibox dance
|
|
196
|
-
or `Ctrl+L` is needed. Do not combine `path` or `aumid` with `urls`
|
|
197
|
-
for selected-browser navigation: `urls` opens separately in the default
|
|
198
|
-
browser. For the default browser, use `urls` only: `launch_app({urls: [url]})`.
|
|
199
|
-
- **Tab-switching shortcuts in browsers (`Ctrl+1..9`, `Ctrl+Tab`,
|
|
200
|
-
`Ctrl+Shift+Tab`, `Ctrl+PageUp/Down`)** are visibly disruptive
|
|
201
|
-
even when delivered to a backgrounded pid. The app's key handler
|
|
202
|
-
processes the shortcut, the window re-renders the new tab's
|
|
203
|
-
content, the user sees their tabs flipping. Same as macOS:
|
|
204
|
-
there is no UIA-only workaround: page content (HTML, DOM,
|
|
205
|
-
WebView2's accessible tree) populates only for the focused tab;
|
|
206
|
-
inspecting a background tab requires activating it, which is the
|
|
207
|
-
visible flip.
|
|
208
|
-
|
|
209
|
-
**Prefer the windows-over-tabs pattern**: for each URL you need to
|
|
210
|
-
drive backgrounded, use `launch_app({urls: [url]})`. Chromium-
|
|
211
|
-
family browsers open each URL in a new **window**. Each window has
|
|
212
|
-
its own `window_id`, its own UIA tree, and can be inspected /
|
|
213
|
-
interacted with via `element_token` without activating or switching
|
|
214
|
-
anything. Tabs are a UX grouping for humans; cua-driver-rs
|
|
215
|
-
workflows should default to windows.
|
|
216
|
-
|
|
217
|
-
- **Win+key shortcuts owned by the shell**: `Win+E` (Explorer),
|
|
218
|
-
`Win+R` (Run), `Win+S` / `Win+Q` (Search), `Win+number` (taskbar
|
|
219
|
-
pinned-app activation), `Win+Tab` (Task View), `Alt+Tab` (window
|
|
220
|
-
switcher), `Win+D` (show desktop), `Win+L` (lock), `Win+M` /
|
|
221
|
-
`Win+Up/Down` (minimize / maximize). All hard-coded to the shell
|
|
222
|
-
and visibly disruptive: they bypass any per-pid routing. Forbidden
|
|
223
|
-
in `hotkey` calls regardless of target pid.
|
|
224
|
-
- **`taskkill /F /IM <exe>` to close an app the user is using**:
|
|
225
|
-
not a focus-steal but a data-loss vector. Use
|
|
226
|
-
`hotkey({pid, keys:["alt","f4"]})` and let the app close cleanly,
|
|
227
|
-
or ask the user first.
|
|
228
|
-
|
|
229
|
-
Reading state is fine. Listing windows, reading registry, querying
|
|
230
|
-
process info via `Get-Process`, calling `tasklist`, walking UIA trees
|
|
231
|
-
via `cua-driver get_window_state`: none of these change focus.
|
|
232
|
-
**Mutating state via shell shims is the line.**
|
|
233
|
-
|
|
234
|
-
**Corollary: the Win+Search rule.** Don't use Win+S/Win+Q "open Start
|
|
235
|
-
search, type query, press Enter" patterns to launch apps. They (a)
|
|
236
|
-
foreground the Search UI, (b) leave the Search UI populated with the
|
|
237
|
-
agent's typed text, (c) trigger the new app via the Start Menu's own
|
|
238
|
-
activation path which `SW_SHOWNORMAL`s it. Use `launch_app({name})` /
|
|
239
|
-
`{aumid}` / `{path}` instead.
|
|
240
|
-
|
|
241
|
-
**"Open \<app\>" in user speech means launch, not activate.**
|
|
242
|
-
`cua-driver launch_app` is the one correct path for process startup:
|
|
243
|
-
it's idempotent (no-op on a running app), returns the pid, and
|
|
244
|
-
internally uses `SW_SHOWNOACTIVATE` plus the AppX-broker activation
|
|
245
|
-
flow for packaged apps so the target's window comes up without
|
|
246
|
-
becoming foreground. The macOS `FocusRestoreGuard` is replaced on
|
|
247
|
-
Windows by the activation-deny dance: the launcher pumps a
|
|
248
|
-
`VK_NONAME` keystroke before activating, which Windows interprets as
|
|
249
|
-
"another app just had user activity" and rejects the target's own
|
|
250
|
-
`SetForegroundWindow` call per [Windows focus-stealing prevention
|
|
251
|
-
rules](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setforegroundwindow).
|
|
252
|
-
|
|
253
|
-
**`launch_app` foreground-restore behaviour (Windows).** In addition to
|
|
254
|
-
`SW_SHOWNOACTIVATE`, `launch_app` captures `GetForegroundWindow()` before
|
|
255
|
-
the spawn and schedules a post-spawn polling restore (every 100 ms for up
|
|
256
|
-
to 3 s) that flips the foreground back to the prior window once the
|
|
257
|
-
spawned app has actually activated. This mirrors the macOS
|
|
258
|
-
`FocusRestoreGuard`. For `urls`-only invocations (open these links in the
|
|
259
|
-
default browser, no app-identifying field) the restore is **skipped**,
|
|
260
|
-
because the user explicitly asked for the page to come up. The restore is
|
|
261
|
-
best-effort: `SetForegroundWindow` from non-UIAccess processes is subject
|
|
262
|
-
to Windows' foreground-lock and may silently no-op: failures are logged
|
|
263
|
-
at `tracing::trace!` and not surfaced as errors.
|
|
264
|
-
|
|
265
|
-
**`hotkey` modifier dispatch on Chromium/Electron (Windows).** When
|
|
266
|
-
the target is detected as a Chromium-family window (`Chrome_WidgetWin_*`
|
|
267
|
-
class, covering every Chromium browser AND every Electron app: Chrome,
|
|
268
|
-
Edge, Brave, Arc, Vivaldi, Slack, VS Code, Discord, Teams, Notion),
|
|
269
|
-
`hotkey` with modifiers routes through `PostMessage(WM_KEYDOWN/UP)`
|
|
270
|
-
instead of `SendInput`: no foreground swap. Chromium's
|
|
271
|
-
`Browser::HandleKeyboardEvent` reads modifier state from the WM_KEYDOWN
|
|
272
|
-
LPARAM bits, NOT from `GetKeyState`, so PostMessage delivery works for
|
|
273
|
-
real accelerators (Ctrl+T, Ctrl+W, Ctrl+L, Ctrl+Shift+B, etc.). The
|
|
274
|
-
SendInput-swap path (`send_key_synthesized`) remains the dispatch for
|
|
275
|
-
**non-Chromium Win32 + modifiers**: classic apps (LibreOffice, FAR,
|
|
276
|
-
classic Notepad) use `TranslateAccelerator` which requires the system
|
|
277
|
-
modifier state updated, and PostMessage can't do that.
|
|
278
|
-
|
|
279
|
-
**`modifier` on a _background_ click is a Windows residual.** A
|
|
280
|
-
backgrounded click delivers through UIA `Invoke` or `PostMessage`, and
|
|
281
|
-
neither carries live keyboard state, so a `modifier` (Ctrl/Shift/etc.)
|
|
282
|
-
passed alongside a `delivery_mode:"background"` click **is not honored**
|
|
283
|
-
on Windows. The `modifier` parameter is part of the shared schema and is
|
|
284
|
-
accepted everywhere; it only takes effect on the SendInput rung, i.e. a
|
|
285
|
-
`delivery_mode:"foreground"` click, where SendInput sets real modifier state.
|
|
286
|
-
If you need a modifier-click on Windows, escalate that one action to
|
|
287
|
-
`foreground`. Reserve `bring_to_front` for the persistent focus-proxy exception
|
|
288
|
-
described above.
|
|
289
|
-
|
|
290
|
-
### Cross-platform schema residuals (Windows)
|
|
291
|
-
|
|
292
|
-
The capture/dispatch/addressing params are a shared cross-platform
|
|
293
|
-
contract (see `SKILL.md` → _Cross-platform parameter contract_). Three
|
|
294
|
-
Windows-relevant notes:
|
|
295
|
-
|
|
296
|
-
- **`session` is now accepted on every action/cursor tool.** Earlier
|
|
297
|
-
Windows builds rejected `session` via `additionalProperties:false` (it
|
|
298
|
-
was effectively a macOS-only key); the shared contract makes it
|
|
299
|
-
uniformly schema-accepted: cursor-wired where a cursor glides,
|
|
300
|
-
accepted elsewhere.
|
|
301
|
-
- **`debug_window_info` is a Windows-only tool** (window-handle / class /
|
|
302
|
-
rect / z-order diagnostics for triaging the click chain). It is
|
|
303
|
-
deliberately not part of the cross-platform surface: there is no macOS
|
|
304
|
-
or Linux counterpart.
|
|
305
|
-
- **`launch_app` identifiers are platform-specific.** Windows takes
|
|
306
|
-
`aumid` / `launch_path` / `path` / `start_minimized` (plus `bundle_id`
|
|
307
|
-
overloaded for AUMIDs); macOS takes `bundle_id` / `urls`. `name` is the
|
|
308
|
-
portable fallback. See the AUMID section below.
|
|
309
|
-
|
|
310
|
-
**Chromium pixel-click foreground polling restore.** `click({pid, x, y})`
|
|
311
|
-
on a Chromium target falls through to `send_click_synthesized`
|
|
312
|
-
(SendInput + brief foreground swap) because Chromium's input thread filters by
|
|
313
|
-
queue-origin and PostMessage-delivered clicks don't fire DOM events. The
|
|
314
|
-
synchronous restore inside `send_click_synthesized` covers the
|
|
315
|
-
immediate swap; an additional polling guard (same shape as `launch_app`'s
|
|
316
|
-
`FocusRestoreGuard`) catches the **asynchronous** Chromium re-activation
|
|
317
|
-
that can happen as the renderer's input handler processes the click
|
|
318
|
-
(focus().activate() / WebContents::Activate(), 100-500 ms later). The
|
|
319
|
-
guard is gated on `GetWindowThreadProcessId(fg_now) == pid` so user
|
|
320
|
-
Alt-Tabs are respected. The polling guard is asynchronous and best-effort,
|
|
321
|
-
so the tool response is not proof that the previous foreground has already
|
|
322
|
-
been restored.
|
|
323
|
-
|
|
324
|
-
## Transport
|
|
325
|
-
|
|
326
|
-
Follow [RUNTIME.md](RUNTIME.md) for CLI and MCP lifecycle ownership.
|
|
327
|
-
On PowerShell, prefer piping JSON to the CLI to avoid argument-quoting errors:
|
|
328
|
-
|
|
329
|
-
```powershell
|
|
330
|
-
'{"pid":6004,"session":"run-1"}' | cua-driver list_windows
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Reuse the same explicit label for a multi-call CLI run and end it afterward.
|
|
334
|
-
For recording or browser preparation, use one persistent MCP connection.
|
|
335
|
-
|
|
336
|
-
### Intent → tool mapping
|
|
337
|
-
|
|
338
|
-
If you find yourself reaching for the right column, something has
|
|
339
|
-
gone wrong: re-read "The no-foreground contract" above.
|
|
340
|
-
|
|
341
|
-
| Intent | Use | Don't use |
|
|
342
|
-
| ---------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
343
|
-
| Open / launch a Win32 app | `launch_app({path: "C:\\Program Files\\…\\foo.exe"})` or `{name: "foo"}` | `Start-Process`, `cmd /c start`, `& "C:\\path\\foo.exe"` |
|
|
344
|
-
| Open / launch a UWP / packaged app | `launch_app({aumid: "Microsoft.Foo_8wekyb3d8bbwe!App"})` | `explorer.exe shell:AppsFolder\\<AUMID>`, Start Menu typing |
|
|
345
|
-
| Open a URL in the default browser | `launch_app({urls: ["https://example.com"]})` | `Start-Process "https://…"`, `explorer.exe ms-edge:…`, `cmd /c start "" "https://…"` |
|
|
346
|
-
| Find a pid | `list_apps` or `launch_app`'s return | `Get-Process`, `tasklist`, Win+S typing |
|
|
347
|
-
| Enumerate an app's windows | `list_windows({pid})`, or read the `windows` array `launch_app` already returns | `Get-Process \| Where-Object { $_.MainWindowHandle }` |
|
|
348
|
-
| Move or resize one exact window | `set_window_frame({pid, window_id, x, y, width, height})` | PowerShell Add-Type wrappers, Win+Arrow, or title-bar dragging |
|
|
349
|
-
| Click / type / scroll / keys | `click`, `type_text`, `scroll`, `press_key`, `hotkey` | `SendInput`, `cliclick`-style C# add-types, AutoHotkey scripts |
|
|
350
|
-
| Drag / drag-and-drop | `drag({pid, from_x, from_y, to_x, to_y})` | `SendInput` with `MOUSEEVENTF_MOVE`, mouse_event |
|
|
351
|
-
| Screenshot | `get_window_state` (window) or authorized `get_desktop_state` (desktop) | `[System.Windows.Forms.Screen]::CopyFromScreen`, `nircmd savescreenshot` |
|
|
352
|
-
| Quit an app | ask the user first, then `hotkey({pid, keys:["alt","f4"]})` | `taskkill /F`, `Stop-Process -Force`, `Get-Process \| Stop-Process` |
|
|
353
|
-
| Hand a file/URL to an app | `launch_app({urls:[<path>]})` (default app) or `{path: "...exe", additional_arguments:[<file>]}` (specific app) | `& "app.exe" "file"`, `Invoke-Item`, shell associations |
|
|
354
|
-
|
|
355
|
-
### The narrow carve-out
|
|
356
|
-
|
|
357
|
-
The **only** legitimate use of `SetForegroundWindow` or
|
|
358
|
-
`Start-Process` with a foreground app is when the user **explicitly**
|
|
359
|
-
asked for frontmost state ("bring Edge to the front", "make
|
|
360
|
-
Calculator visible", "I want to see it"). Reaching for it because a
|
|
361
|
-
tool call returned something confusing is wrong: diagnose first.
|
|
362
|
-
|
|
363
|
-
When a cua-driver surprises you, diagnose cua-driver first:
|
|
364
|
-
|
|
365
|
-
- **`Posted click to pid X` instead of `Performed UIA Invoke ...`?**
|
|
366
|
-
The (x,y) UIA hit-test didn't find an `InvokePattern`-bearing
|
|
367
|
-
element at that pixel inside the target HWND, so it fell through
|
|
368
|
-
to `PostMessage(WM_LBUTTONDOWN)`. For UWP / WebView2 surfaces,
|
|
369
|
-
PostMessage silently no-ops: re-snapshot via
|
|
370
|
-
`get_window_state(pid, window_id)` and use `element_token` so the
|
|
371
|
-
daemon can invoke the cached UIA element by identity instead of by
|
|
372
|
-
point.
|
|
373
|
-
- **`stale_element_token` / `No cached UIA state`?** You either
|
|
374
|
-
skipped `get_window_state` this turn or used a token from an older
|
|
375
|
-
snapshot. Snapshots are keyed on `(pid, window_id)`: tokens don't carry across windows of
|
|
376
|
-
the same app. Re-snapshot with the same window_id you're about to
|
|
377
|
-
click in.
|
|
378
|
-
- **`Invalid window handle (0x80070578)`?** The HWND you passed is
|
|
379
|
-
stale (window closed, recreated). Re-resolve via `list_windows`.
|
|
380
|
-
- **Large tree or repeated reads?** `get_window_state` returns one compact
|
|
381
|
-
markdown tree capped at 250 nodes (`full_output:true` restores 5 000 nodes
|
|
382
|
-
and both forms). Pass `since:<snapshot_id>` for added/changed/removed rows;
|
|
383
|
-
the focused element is not reported on Windows yet, so an unchanged read
|
|
384
|
-
says only `no change since …`.
|
|
385
|
-
- **Empty `tree_markdown` / sparse UIA tree?** Some apps populate
|
|
386
|
-
their UIA tree lazily on first call; retry `get_window_state`
|
|
387
|
-
once. If still empty, the app has no UIA provider: fall back to an
|
|
388
|
-
element px action (x,y clicks off the screenshot) on visible content
|
|
389
|
-
(acceptable for exploration; pair with screenshots).
|
|
390
|
-
- **Empty tree, or a snapshot with no image?** `get_window_state`
|
|
391
|
-
returns **both** the UIA tree and a screenshot by default: there is no
|
|
392
|
-
capture mode to pick. If the tree came back empty, the response is
|
|
393
|
-
`degraded` (no UIA provider: retry once for lazy trees, see the note
|
|
394
|
-
above); act by **px** off the screenshot in the same response. The
|
|
395
|
-
`capture_mode` param is **deprecated and ignored**: it's still accepted
|
|
396
|
-
so old callers don't error, but both the tree and the image come back
|
|
397
|
-
regardless of what you pass.
|
|
398
|
-
- **`list_windows` returns Win32 windows but misses UWP / WebView2
|
|
399
|
-
windows?** UIA desktop enumeration may be degraded because a provider
|
|
400
|
-
is unresponsive. `list_windows` falls back to Win32-only output instead
|
|
401
|
-
of hanging; run `cua-driver doctor` and retry after the provider
|
|
402
|
-
recovers.
|
|
403
|
-
- **Need full-display capture?** Use `get_desktop_state` only for a desktop
|
|
404
|
-
coordinate loop. To verify a specific window, use
|
|
405
|
-
`get_window_state(pid, window_id)` instead; it works while backgrounded.
|
|
406
|
-
Desktop actions use
|
|
407
|
-
`target:{"kind":"desktop","display_id":"primary"}`. Don't reach for
|
|
408
|
-
`get_desktop_state` as a casual screenshot: it is the desktop capture
|
|
409
|
-
surface, not window inspection.
|
|
410
|
-
- **`Calc display stuck at 0 after my clicks`?** Almost always
|
|
411
|
-
means UWP and you're on the PostMessage path. UWP processes
|
|
412
|
-
pointer input via `Windows.UI.Input`, NOT through HWND message
|
|
413
|
-
queues: PostMessage(WM_LBUTTONDOWN) gets ignored. Use
|
|
414
|
-
`element_token` instead of (x,y) for UWP targets.
|
|
415
|
-
|
|
416
|
-
Only after those are ruled out should you fall through to the
|
|
417
|
-
activate fallback. Always name the focus steal in your response
|
|
418
|
-
("I'll briefly bring Edge to the front because …").
|
|
419
|
-
|
|
420
|
-
### Self-check pattern
|
|
421
|
-
|
|
422
|
-
Before every `Bash` call whose command line touches any Windows app
|
|
423
|
-
(launching, opening, clicking, typing, scripting, screenshotting),
|
|
424
|
-
run the self-check:
|
|
425
|
-
|
|
426
|
-
1. **Does this command foreground the target?** If yes, stop and
|
|
427
|
-
translate to the cua-driver equivalent from the mapping table.
|
|
428
|
-
2. **Does this command move the user's real cursor?** (`SendInput`,
|
|
429
|
-
`SetCursorPos` from inline C#, AutoHotkey scripts, `nircmd
|
|
430
|
-
sendmouse`.) If yes, stop; use `click({pid, x, y})` which routes
|
|
431
|
-
per-HWND via PostMessage / per-element via UIA Invoke and never
|
|
432
|
-
warps the cursor.
|
|
433
|
-
3. **Does this command bypass cua-driver entirely?** (PowerShell
|
|
434
|
-
GUI scripts, AutoHotkey, `SendKeys`, AppleScript-equivalent
|
|
435
|
-
automation tools.) If yes, stop; find the cua-driver tool that
|
|
436
|
-
does the intent.
|
|
437
|
-
|
|
438
|
-
If all three are "no," the command is safe. If you can't answer,
|
|
439
|
-
default to stop and ask rather than proceed. A single `Start-Process`
|
|
440
|
-
run by accident steals the user's foreground and kills the trust
|
|
441
|
-
your prior tool calls earned.
|
|
442
|
-
|
|
443
|
-
## Prerequisites: check before starting
|
|
444
|
-
|
|
445
|
-
1. **`cua-driver` is on `$PATH`**: `Get-Command cua-driver` or
|
|
446
|
-
`where.exe cua-driver`. Install location:
|
|
447
|
-
`%LOCALAPPDATA%\Programs\trycua\cua-driver-rs\bin\cua-driver.exe`,
|
|
448
|
-
added to the user PATH by the install script.
|
|
449
|
-
If missing, point the user at:
|
|
450
|
-
```powershell
|
|
451
|
-
irm https://cua.ai/driver/install.ps1 | iex
|
|
452
|
-
```
|
|
453
|
-
and stop.
|
|
454
|
-
2. **The runtime owner must run in an interactive session (Session 1+),
|
|
455
|
-
NOT Session 0.** This is the daemon for one-shot CLI/service mode and the
|
|
456
|
-
MCP process for bare stdio MCP. Windows isolates services into Session 0 with no
|
|
457
|
-
desktop. UIA enumeration, screenshot via PrintWindow, and
|
|
458
|
-
`IApplicationActivationManager` all silently return empty /
|
|
459
|
-
timeout in Session 0. Check:
|
|
460
|
-
```powershell
|
|
461
|
-
Get-Process cua-driver | Select Id,SessionId
|
|
462
|
-
```
|
|
463
|
-
`SessionId == 0` is refused before runtime actions. The autostart Scheduled Task uses
|
|
464
|
-
`LogonType=Interactive` so the daemon lands in the user's logon
|
|
465
|
-
session. If you started the daemon via SSH-into-Windows, that
|
|
466
|
-
session is usually Session 0: kick the autostart task instead:
|
|
467
|
-
```powershell
|
|
468
|
-
schtasks /Run /TN cua-driver-serve
|
|
469
|
-
```
|
|
470
|
-
3. **Run `cua-driver doctor`**: reports session ID, COM apartment
|
|
471
|
-
status, UIA desktop-enumeration reachability, install paths,
|
|
472
|
-
version. If anything reads `false` / `error`, fix that before
|
|
473
|
-
tool-calling.
|
|
474
|
-
4. **Permissions**: Windows has no TCC equivalent. cua-driver-rs
|
|
475
|
-
needs:
|
|
476
|
-
- No admin elevation for normal use (UIA, PostMessage, UWP
|
|
477
|
-
activation all work from a standard user token).
|
|
478
|
-
- UAC elevation **only** for `autostart` registration if a
|
|
479
|
-
system-wide scheduled task is requested (per-user task is
|
|
480
|
-
non-elevated and the default).
|
|
481
|
-
- SmartScreen: on a fresh install, Windows Defender SmartScreen
|
|
482
|
-
may flag the unsigned binary on first run. Click "More info →
|
|
483
|
-
Run anyway" once.
|
|
484
|
-
|
|
485
|
-
## Using cua-driver from the shell
|
|
486
|
-
|
|
487
|
-
Tool names are `snake_case`, management subcommands are
|
|
488
|
-
`kebab-case`: no ambiguity. Tools invoked as `cua-driver
|
|
489
|
-
<tool-name>` with JSON via stdin or positional arg. Management
|
|
490
|
-
subcommands:
|
|
491
|
-
|
|
492
|
-
- **`cua-driver serve`**: start the persistent daemon used by one-shot CLI
|
|
493
|
-
calls or by MCP clients that explicitly select it with `--socket`. Bare
|
|
494
|
-
`cua-driver mcp` owns its runtime directly on Windows.
|
|
495
|
-
Normally not run manually: the autostart Scheduled Task fires it
|
|
496
|
-
at every interactive logon. If you stopped it (`Stop-Process`),
|
|
497
|
-
re-run with `schtasks /Run /TN cua-driver-serve`, not by spawning
|
|
498
|
-
`cua-driver serve` from SSH (Session 0 problem).
|
|
499
|
-
- **`cua-driver stop`** / **`status`**: daemon lifecycle.
|
|
500
|
-
- **`cua-driver doctor`**: full diagnostics.
|
|
501
|
-
- **`cua-driver list-tools`** / **`describe <tool>`**: tool surface
|
|
502
|
-
discovery.
|
|
503
|
-
- **`cua-driver autostart {enable|disable|status|kick}`**: manage the
|
|
504
|
-
Scheduled Task that auto-starts the daemon at logon. `enable`
|
|
505
|
-
registers it (idempotent: replaces existing). `kick` runs it
|
|
506
|
-
immediately without waiting for a fresh logon.
|
|
507
|
-
- **`cua-driver recording start|stop|status`**: see `RECORDING.md`.
|
|
508
|
-
Windows video uses ffmpeg with `gdigrab`; trajectory evidence continues
|
|
509
|
-
without video when ffmpeg is unavailable.
|
|
510
|
-
|
|
511
|
-
Over SSH, never use bare `cua-driver mcp`: the direct runtime rejects Session 0. Start the daemon in the interactive user session and run `cua-driver mcp
|
|
512
|
-
--socket \\.\pipe\cua-driver` from SSH.
|
|
513
|
-
|
|
514
|
-
For an ordered action loop, use the named CLI or persistent MCP examples in
|
|
515
|
-
[WORKFLOW.md](WORKFLOW.md), with an observed AUMID, PID, HWND, and fresh token.
|
|
516
|
-
Do not assume the window array is populated immediately on a cold launch.
|
|
517
|
-
|
|
518
|
-
## The core invariant: snapshot before AND after every action
|
|
519
|
-
|
|
520
|
-
**Every action MUST be bracketed by `get_window_state(pid, window_id)`**:
|
|
521
|
-
|
|
522
|
-
- **Before**: the pre-action snapshot resolves the `element_token`
|
|
523
|
-
you're about to use. Targets from previous turns are stale; the
|
|
524
|
-
server replaces the snapshot on every read, keyed on
|
|
525
|
-
`(pid, window_id)`, and lists the replaced ids in
|
|
526
|
-
`invalidated_snapshot_ids`. Tokens from turn N don't resolve in turn
|
|
527
|
-
N+1, and targets from window A don't resolve against window B of
|
|
528
|
-
the same app. Skip this and element actions fail with
|
|
529
|
-
`stale_element_token`, which names the current snapshots.
|
|
530
|
-
- **After**: the post-action snapshot verifies the action actually
|
|
531
|
-
landed. Without it you can't tell a silent no-op from a real
|
|
532
|
-
effect. The UIA tree change (new value, new window, disappeared
|
|
533
|
-
menu, disabled button, etc.) is your evidence that the action
|
|
534
|
-
registered. **Especially important on Windows** because the
|
|
535
|
-
layered click path can return `effect:"unverifiable"` after
|
|
536
|
-
PostMessage even when the click did nothing (UWP silently no-ops):
|
|
537
|
-
the action result reports the route, not the task outcome. Only
|
|
538
|
-
the re-snapshot tells you if the state changed.
|
|
539
|
-
|
|
540
|
-
## Click semantics on Windows
|
|
541
|
-
|
|
542
|
-
Two click addressing modes, both gated by `pid`:
|
|
543
|
-
|
|
544
|
-
### Snapshot-bound element mode (preferred)
|
|
545
|
-
|
|
546
|
-
```json
|
|
547
|
-
{ "pid": 6004, "element_token": "s0000002a:22" }
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
Looks up the exact cached UIA element from the named snapshot,
|
|
551
|
-
fires `IUIAutomationInvokePattern::Invoke()` on it directly.
|
|
552
|
-
|
|
553
|
-
Properties:
|
|
554
|
-
|
|
555
|
-
- **No mouse cursor moves.** The click is a UIA RPC, not an input
|
|
556
|
-
event. The user's cursor stays where it is.
|
|
557
|
-
- **No window activates.** UIA Invoke does not foreground the
|
|
558
|
-
target.
|
|
559
|
-
- **Z-order is irrelevant.** Works on backgrounded, occluded,
|
|
560
|
-
minimized-but-not-iconic, and cross-virtual-desktop windows.
|
|
561
|
-
- **Cross-process boundaries work.** UIA marshals across the
|
|
562
|
-
`ApplicationFrameHost.exe` → inner UWP process boundary
|
|
563
|
-
(CalculatorApp.exe, Notepad-Win11.exe, etc.): this is how it can
|
|
564
|
-
click Win11 Calc buttons even though they live in a different
|
|
565
|
-
process from the AppFrame HWND.
|
|
566
|
-
- **Falls back to `PostMessage(WM_LBUTTONDOWN/UP)` to the deepest
|
|
567
|
-
child HWND** when the cached element doesn't expose
|
|
568
|
-
`InvokePattern` (most edit fields, custom-drawn widgets,
|
|
569
|
-
non-actionable elements). The fallback works for plain Win32 but
|
|
570
|
-
silently no-ops on UWP. Read the closed action `route`:
|
|
571
|
-
`accessibility` means UIA/MSAA and `synthetic_events` means the
|
|
572
|
-
targeted event fallback. Do not parse the human-readable text.
|
|
573
|
-
|
|
574
|
-
This is the right path for **any** "click button N" or "click checkbox Y"
|
|
575
|
-
intent. For a known application-menu hierarchy, prefer `invoke_menu`:
|
|
576
|
-
|
|
577
|
-
```json
|
|
578
|
-
{ "pid": 6004, "window_id": 459672, "path": ["Window", "Arrange", "Left"] }
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
It uses `ExpandCollapsePattern` at intermediate hops and `InvokePattern` or
|
|
582
|
-
`SelectionItemPattern` at the leaf, resolving the live UIA hierarchy again
|
|
583
|
-
after every expansion. It refuses ambiguous, missing, or disabled segments and
|
|
584
|
-
never falls back to pixels. Verify the command's semantic effect afterward.
|
|
585
|
-
|
|
586
|
-
### `(x, y)` mode (element px action / pixel)
|
|
587
|
-
|
|
588
|
-
```json
|
|
589
|
-
{ "pid": 6004, "window_id": 459672, "x": 446, "y": 671 }
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
Window-client coordinates (origin at the top-left of the screenshot
|
|
593
|
-
the agent saw). The driver:
|
|
594
|
-
|
|
595
|
-
1. Converts to screen coords via `ClientToScreen(hwnd, ...)`.
|
|
596
|
-
2. **UIA hit-test in target HWND's subtree**: `ElementFromHandle`
|
|
597
|
-
resolves the root, `FindAll(TreeScope_Subtree)` enumerates
|
|
598
|
-
descendants, picks the smallest-area `InvokePattern`-bearing
|
|
599
|
-
element whose `CurrentBoundingRectangle` contains the screen
|
|
600
|
-
point, calls `Invoke()`. This is the only path that lands on UWP
|
|
601
|
-
/ WebView2 / DirectComposition surfaces.
|
|
602
|
-
3. **PostMessage fallback**: if step 2 returned false (no Invokable
|
|
603
|
-
element under the pixel inside `hwnd`, or `hwnd` has no useful
|
|
604
|
-
UIA tree at all), fires `PostMessage(WM_LBUTTONDOWN/UP)` against
|
|
605
|
-
the deepest child HWND at the screen point. Covers plain Win32
|
|
606
|
-
native controls.
|
|
607
|
-
|
|
608
|
-
Properties:
|
|
609
|
-
|
|
610
|
-
- **No real cursor movement.** The agent overlay glides + pulses
|
|
611
|
-
for visual confirmation; the OS cursor is untouched.
|
|
612
|
-
- **No focus steal.** Both UIA Invoke and PostMessage are async per-
|
|
613
|
-
pid / per-element; the target's window does not activate.
|
|
614
|
-
- **Z-order independent.** UIA hit-test honors the HWND-rooted
|
|
615
|
-
subtree regardless of what's covering it on screen. PostMessage
|
|
616
|
-
goes directly to the message queue.
|
|
617
|
-
|
|
618
|
-
Use this when the agent doesn't have a UIA snapshot in scope (zero-
|
|
619
|
-
shot from a screenshot), or when the element it wants doesn't appear
|
|
620
|
-
in the UIA tree (custom-drawn elements, canvas content, browser
|
|
621
|
-
DOM nodes inside a WebView2 viewport).
|
|
622
|
-
|
|
623
|
-
### What `(x, y)` mode does NOT solve
|
|
624
|
-
|
|
625
|
-
Apps with **no useful UIA tree** AND that **ignore `WM_LBUTTONDOWN`**
|
|
626
|
-
on the HWND queue: primarily DirectX / OpenGL / Vulkan-rendered
|
|
627
|
-
surfaces (games, custom renderers). The click chain falls all the
|
|
628
|
-
way through and the click no-ops. For those, the only options are:
|
|
629
|
-
|
|
630
|
-
- Bring the window to top first (focus steal: ask the user before
|
|
631
|
-
doing this, and document why), then synthesize input
|
|
632
|
-
- Use the app's keyboard interface via `hotkey` if available
|
|
633
|
-
|
|
634
|
-
`SendInput` is **not** a silent-fallback option here: it would
|
|
635
|
-
steal focus from whatever the user is doing.
|
|
636
|
-
|
|
637
|
-
### Right-click and multi-click
|
|
638
|
-
|
|
639
|
-
`button: "right"` and `count > 1` **skip the UIA Invoke step** and
|
|
640
|
-
go directly through the PostMessage path. Reason: UIA has no clean
|
|
641
|
-
by-coord equivalent of `ShowContextMenu`, and `Invoke()` is single-
|
|
642
|
-
fire by definition. The action result reports `route:"synthetic_events"`
|
|
643
|
-
regardless of the target's UWP-ness: this is expected and
|
|
644
|
-
**will not work for UWP context menus**. To open a UWP context menu,
|
|
645
|
-
prefer `hotkey({pid, keys: ["shift", "f10"]})` against the focused
|
|
646
|
-
UWP element.
|
|
647
|
-
|
|
648
|
-
## UWP / packaged apps: the AUMID layer
|
|
649
|
-
|
|
650
|
-
Modern Win11 apps (Calculator, Notepad, Settings, Photos, Edge UI
|
|
651
|
-
chrome, Microsoft Store) are **packaged apps** with an `App User
|
|
652
|
-
Model ID` (AUMID) rather than a plain `.exe` path. The AUMID looks
|
|
653
|
-
like `Microsoft.WindowsCalculator_8wekyb3d8bbwe!App`: package family
|
|
654
|
-
name + `!` + AppId.
|
|
655
|
-
|
|
656
|
-
Windows architectural quirks that matter:
|
|
657
|
-
|
|
658
|
-
1. **The `.exe` in `C:\Windows\System32\notepad.exe` is a 7 KB stub
|
|
659
|
-
that exits immediately on Win11.** It exists for backward
|
|
660
|
-
compatibility but the real Notepad lives in the
|
|
661
|
-
`Microsoft.WindowsNotepad` AppX package. `Start-Process notepad`
|
|
662
|
-
spawns the stub, which exits, which redirects through the AppX
|
|
663
|
-
broker, which spawns the real process with a different pid. You
|
|
664
|
-
end up holding a pid that's already gone. `launch_app` handles
|
|
665
|
-
this transparently: it detects AUMID-looking strings and routes
|
|
666
|
-
through `IApplicationActivationManager::ActivateApplication`,
|
|
667
|
-
which returns the **real** packaged-process pid.
|
|
668
|
-
2. **UWP windows are hosted by `ApplicationFrameHost.exe`.** The
|
|
669
|
-
outer top-level HWND (the one with the title bar, the one
|
|
670
|
-
`EnumWindows` enumerates) is owned by `ApplicationFrameHost.exe`,
|
|
671
|
-
pid varies, not the same as the UWP's own pid. The actual UWP
|
|
672
|
-
content runs in a separate process (e.g. `CalculatorApp.exe`).
|
|
673
|
-
`list_windows` reports the AppFrame's HWND because that's what
|
|
674
|
-
`GetWindowRect`, `PostMessage`, `BitBlt` all target. UIA
|
|
675
|
-
transparently crosses the boundary into the inner process when
|
|
676
|
-
you walk the tree.
|
|
677
|
-
3. **AUMID resolution by name**: `launch_app({name: "calc"})` will
|
|
678
|
-
first try a `shell:AppsFolder` lookup, matching against
|
|
679
|
-
installed-package display names. On a hit it goes through the
|
|
680
|
-
packaged-app path (real pid). Otherwise it falls back to
|
|
681
|
-
`ShellExecuteEx`'s PATH search (which hits the stub `.exe`).
|
|
682
|
-
**Prefer explicit `aumid` when you know it.**
|
|
683
|
-
|
|
684
|
-
### Known AUMIDs for common Win11 apps
|
|
685
|
-
|
|
686
|
-
```
|
|
687
|
-
Microsoft.WindowsCalculator_8wekyb3d8bbwe!App
|
|
688
|
-
Microsoft.WindowsNotepad_8wekyb3d8bbwe!App
|
|
689
|
-
Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe!App
|
|
690
|
-
Microsoft.WindowsTerminal_8wekyb3d8bbwe!App
|
|
691
|
-
Microsoft.Paint_8wekyb3d8bbwe!App
|
|
692
|
-
Microsoft.WindowsCamera_8wekyb3d8bbwe!App
|
|
693
|
-
Microsoft.WindowsAlarms_8wekyb3d8bbwe!App
|
|
694
|
-
Microsoft.MicrosoftStickyNotes_8wekyb3d8bbwe!App
|
|
695
|
-
windows.immersivecontrolpanel_cw5n1h2txyewy!Microsoft.Windows.ImmersiveControlPanel # Settings
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
To find an AUMID at runtime:
|
|
699
|
-
|
|
700
|
-
```powershell
|
|
701
|
-
Get-StartApps | Where-Object Name -like "*Calculator*"
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
## Browsers and embedded webviews on Windows
|
|
705
|
-
|
|
706
|
-
Use the typed, exact-binding workflow in `BROWSER.md` for Chrome and Edge page
|
|
707
|
-
content. Windows has validated trusted background browser clicks when the
|
|
708
|
-
driver runs in an interactive user desktop. A daemon in Session 0 cannot
|
|
709
|
-
provide representative UIA, capture, focus, or browser evidence.
|
|
710
|
-
|
|
711
|
-
Ref- and coordinate-targeted browser mutations also drive the declared
|
|
712
|
-
session's agent cursor. The adapter converts the live CDP page point into the
|
|
713
|
-
bound window's DPI-aware screen coordinates, pins the overlay above that
|
|
714
|
-
window, and keeps only the selected tab's session cursor visible. This visual
|
|
715
|
-
feedback never moves the physical pointer or changes browser input delivery.
|
|
716
|
-
|
|
717
|
-
Keep browser chrome and native dialogs on the normal UIA/PX ladder in this
|
|
718
|
-
file. This includes tabs, the address bar, menus, permission prompts,
|
|
719
|
-
downloads, file pickers, and authentication windows. Avoid `Ctrl+L`, tab
|
|
720
|
-
switching shortcuts, `Start-Process`, and shell activation paths when the user
|
|
721
|
-
expects background operation.
|
|
722
|
-
|
|
723
|
-
WebView2 inside a non-browser host is not automatically equivalent to a
|
|
724
|
-
standalone Edge target. Use typed browser mutation only when
|
|
725
|
-
`get_browser_state` returns an exact binding and `mutation_allowed:true` for
|
|
726
|
-
the selected `(pid, window_id)`. Otherwise use the native UIA/PX route or
|
|
727
|
-
accept the structured refusal. Firefox page mutation is not supported by the
|
|
728
|
-
typed browser tools yet.
|
|
729
|
-
|
|
730
|
-
## Common failure modes (Windows-specific)
|
|
731
|
-
|
|
732
|
-
- **`Session 0` daemon**: `cua-driver doctor` reports
|
|
733
|
-
`SessionId: 0`. UIA enumeration returns empty, screenshot
|
|
734
|
-
returns blank. Fix: stop the daemon, kick the autostart task with
|
|
735
|
-
`schtasks /Run /TN cua-driver-serve`.
|
|
736
|
-
- **Stale HWND** (`Invalid window handle 0x80070578`): the window
|
|
737
|
-
was closed, re-created (e.g. UWP shutdown-on-idle), or moved to
|
|
738
|
-
a different desktop session. Re-resolve via `list_windows`.
|
|
739
|
-
- **Calc display stuck at "0" after pixel clicks**: the (x,y) UIA
|
|
740
|
-
hit-test missed and PostMessage fell through (PostMessage is a
|
|
741
|
-
silent no-op on UWP). Switch to `element_token` mode. Symptom:
|
|
742
|
-
the action result reports `route:"synthetic_events"` instead of
|
|
743
|
-
`route:"accessibility"`.
|
|
744
|
-
- **LibreOffice (VCL) `type_text` / `hotkey` reported success but
|
|
745
|
-
nothing happened**: VCL/SAL apps route accelerators through
|
|
746
|
-
`TranslateAccelerator` (reads `GetKeyState`, which PostMessage doesn't
|
|
747
|
-
update) and the Calc/Writer document grid only takes real keystrokes
|
|
748
|
-
when a cell is in edit mode, so background `WM_CHAR` / key-combos are
|
|
749
|
-
silently dropped. Two honesty mechanisms now cover this instead of a
|
|
750
|
-
blind success:
|
|
751
|
-
- **`hotkey` / `press_key`** (keystroke + key-combo): `delivery_mode:"background"`
|
|
752
|
-
surfaces a `background_unavailable` error for VCL.
|
|
753
|
-
- **`type_text`** does a **UIA read-back** and returns the shared
|
|
754
|
-
`ActionResult`. With an `element_token`, the ValuePattern path returns
|
|
755
|
-
`effect:"confirmed"` with `evidence:[{"kind":"value_readback"}]` only
|
|
756
|
-
when the complete expected value is synchronously visible and differs from
|
|
757
|
-
the prior value. If SetValue succeeded but the provider still exposes the
|
|
758
|
-
old or no value, the result is `effect:"unverifiable"` with no escalation.
|
|
759
|
-
This is common for deferred providers such as AccessKit: take a fresh
|
|
760
|
-
snapshot before retrying because the value may publish only after
|
|
761
|
-
`type_text` returns and an immediate retry can duplicate text. A pixel
|
|
762
|
-
escalation is reserved for an Electron/web accessibility echo that does
|
|
763
|
-
not prove the renderer observed the write. The PostMessage fallback keeps
|
|
764
|
-
its separate delivery/read-back behavior. Even when the value changes and
|
|
765
|
-
contains the requested text, the result stays `effect:"unverifiable"` because
|
|
766
|
-
WM_CHAR does not expose the insertion point. Take a fresh snapshot before
|
|
767
|
-
retrying. It may recommend foreground when a background insert appears
|
|
768
|
-
dropped. Passing an `element_token` makes the
|
|
769
|
-
read-back target that exact element by handle (ValuePattern → TextPattern),
|
|
770
|
-
independent of foreground focus. Without one, PostMessage verification
|
|
771
|
-
falls back to system-wide `GetFocusedElement`, which normally resolves only
|
|
772
|
-
for the foreground app; an unreadable result is therefore not proof of
|
|
773
|
-
failure.
|
|
774
|
-
Escalate to `delivery_mode:"foreground"` for both (SendInput Unicode /
|
|
775
|
-
accelerator). **But** foreground needs the swap to actually land: if the
|
|
776
|
-
daemon lacks UIAccess and `bring_to_front` returns `landed_on_target:false`
|
|
777
|
-
(or it reverts before the next call), you can't drive it by input at all:
|
|
778
|
-
produce the artifact and `launch_app` it (build the `.xlsx` / `.docx` and
|
|
779
|
-
open it) rather than typing into the GUI.
|
|
780
|
-
- **Edge / Chrome shows tab switching even though I used pid-scoped
|
|
781
|
-
hotkey**: `Ctrl+Tab` / `Ctrl+1..9` aren't pid-scopable; the
|
|
782
|
-
receiver activates. Use the windows-per-URL pattern.
|
|
783
|
-
- **`Get-StartApps` returns no AUMID for an app I see in Start
|
|
784
|
-
Menu**: the app might be a Win32 desktop app, not a packaged
|
|
785
|
-
app. Use `{path: "..."}` instead of `{aumid}`. (Win11 Calculator
|
|
786
|
-
IS packaged; Win10 classic Notepad was not.)
|
|
787
|
-
- **`launch_app` returns pid N but `list_windows({pid: N})` returns
|
|
788
|
-
empty**: UWP cold-launch race: the AppFrame HWND hasn't
|
|
789
|
-
materialized yet. Re-call `list_windows({pid: N})` after 500ms;
|
|
790
|
-
for chronic cases, key off the app name in `list_windows({})`
|
|
791
|
-
output.
|
|
792
|
-
- **Need a screenshot file**: use `get_window_state` with
|
|
793
|
-
`screenshot_out_file`; the result is PNG. The removed standalone screenshot
|
|
794
|
-
compatibility inputs (`format` and `quality`) are rejected by the live tool
|
|
795
|
-
schema. Downsample or convert the saved PNG outside cua-driver if needed.
|
|
796
|
-
|
|
797
|
-
## Diagnostics
|
|
798
|
-
|
|
799
|
-
`cua-driver doctor` reports:
|
|
800
|
-
|
|
801
|
-
- Daemon version and install paths
|
|
802
|
-
- Current session ID (must be ≥1)
|
|
803
|
-
- COM apartment status (STA / MTA / uninitialized)
|
|
804
|
-
- UIA reachability (can we create `CUIAutomation` and enumerate desktop children?)
|
|
805
|
-
- AppX broker reachability (for packaged-app activation)
|
|
806
|
-
- PATH state (is `cua-driver` actually on PATH?)
|
|
807
|
-
- Autostart Scheduled Task status
|
|
808
|
-
|
|
809
|
-
Run it whenever a tool call returns unexpectedly. Most failures
|
|
810
|
-
trace back to one of these checks reading "false."
|
|
811
|
-
|
|
812
|
-
`cua-driver autostart status` reports whether the daemon is
|
|
813
|
-
registered to auto-start at logon AND whether it's currently running:
|
|
814
|
-
|
|
815
|
-
- `not-registered`: Task Scheduler explicitly reported that the named task
|
|
816
|
-
does not exist. Re-register via `cua-driver autostart enable`.
|
|
817
|
-
- `registered (not running)`: autostart task exists but no daemon
|
|
818
|
-
process. Kick it with `cua-driver autostart kick`.
|
|
819
|
-
- `registered (running)`: happy path.
|
|
820
|
-
- `permission-denied`: the current process cannot inspect Task Scheduler;
|
|
821
|
-
registration is unknown. Re-run the status check from a context that can
|
|
822
|
-
read the task rather than re-registering it blindly.
|
|
823
|
-
- `unknown`: the query failed for another reason. The command exits non-zero
|
|
824
|
-
and includes the original `schtasks` diagnostic; do not treat this as an
|
|
825
|
-
absent registration.
|
|
826
|
-
|
|
827
|
-
## Recording
|
|
828
|
-
|
|
829
|
-
Windows recording uses ffmpeg with `gdigrab` and writes the same trajectory
|
|
830
|
-
shape described in `RECORDING.md`. Install ffmpeg on `PATH` for MP4 capture.
|
|
831
|
-
When it is unavailable, per-turn state and screenshots continue and
|
|
832
|
-
`last_error` reports the missing dependency.
|