Status: Draft v0.1 — additive to PROTOCOL.md. All ops in this document gate on the
computer_usecapability being granted.
"Computer use" in the shim means: the platform LLM (Anthropic
computer_20251124, OpenAI computer-use tool, or anything that emits the
same family of atomic GUI actions) emits high-level tool calls; the platform
translates them to the wire ops below; the shim runs them on the host with
the user's full OS-level identity (their cookies, their SSH keys, their
locally-installed apps) and returns observable results. Every op writes an
audit record per AUDIT_LOG.md so the user can post-hoc
inspect what the agent did. Today's surface — SCREENSHOT_REQUEST +
seven INPUT_ACTION verbs — is enough for a screen-and-click loop but
falls short of "fill a form across three apps, drag a file from Finder into
a chat window, take a meeting note from a video call." This document
specifies what we add to close that gap.
The v1 surface is deliberately op-shaped, not macro-shaped: each wire op is one observable host action with one audit record and one error mode. The platform composes them into multi-step agent loops; the shim does not own the loop.
These are additive to PROTOCOL.md §Computer Use. The transport, envelope,
correlation, error shape, and CAPABILITY_NOT_GRANTED semantics are
unchanged — only the message types and fields below are new or extended.
The computer_use capability stays a single flag (shim either advertises
it or doesn't, per Wayland / TCC rules in CROSS_PLATFORM.md).
Inside HELLO.payload, add a new field. Two granularities are supported
side by side, both populated by the shim from a live capability probe:
computer_use_features(legacy, fine-grained): the per-op feature flags the platform consults before sending a wire op. Each entry corresponds to one v1 op or one extension to an existing op.computer_use_features_coarse: a small fixed set of broad buckets (screenshot,input,windows,apps,clipboard,permissions) that the platform can use for at-a-glance UX ("clipboard not available" vs enumerating eight clipboard sub-features).
{
"computer_use_features": [
"screenshot.region",
"screenshot.window",
"input.click.modifiers",
"input.click.button",
"input.drag.path",
"input.key.hold",
"input.mouse.down_up",
"input.scroll.amount",
"input.wait",
"cursor.position",
"display.info",
"window.list",
"window.focus",
"app.list",
"app.launch",
"clipboard.read",
"clipboard.write",
"permissions.check"
],
"computer_use_features_coarse": [
"screenshot",
"input",
"windows",
"apps",
"clipboard",
"permissions"
]
}The coarse buckets per OS (when computer-use is available at all):
| OS / session | Coarse buckets advertised |
|---|---|
| macOS (AX + Screen Recording granted) | ["screenshot", "input", "windows", "apps", "clipboard", "permissions"] |
| Windows | ["screenshot", "input", "windows", "apps", "clipboard", "permissions"] |
| Linux X11 | ["screenshot", "input", "apps", "clipboard", "permissions"] (windows omitted unless wmctrl or AT-SPI is present) |
| Linux Wayland | ["screenshot"] (input + window listing blocked; clipboard via portal is deferred) |
| WSL2 | [] (no display server reachable to the Windows host) |
clipboard only appears when the shim was started with
--enable-clipboard (per Q3 below); without the flag, clipboard sub-ops
return FEATURE_NOT_SUPPORTED even on capable OSes.
Rationale: cua-driver's PARITY.md shows per-OS gaps even for VERIFIED tools
(e.g. Windows has drag, macOS doesn't yet). The platform needs to know
per-shim what works without sniffing the OS string. Each feature flag
corresponds to one v1 op or one extension to an existing op; see the per-OS
matrix below for what each shim advertises.
The platform must not call any feature not in computer_use_features; the
shim returns FEATURE_NOT_SUPPORTED (new error code) if it does.
Backwards-compatible additions to payload:
{
"type": "SCREENSHOT_REQUEST",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"monitor": 0,
"quality": 75,
"region": [100, 200, 900, 700], // optional [x1, y1, x2, y2] — Anthropic zoom-equivalent
"window_id": null, // optional opaque ID from WINDOW_LIST_RESPONSE
"format": "jpeg", // "jpeg" | "png" — png for OCR/diff workflows
"include_cursor": true // overlay cursor in the capture
}
}SCREENSHOT_RESPONSE gains a region echo, a display_scale field so
the platform can map model-space coordinates back to physical pixels on
HiDPI displays (this is the same trap that bit the cua-driver Linux build —
hardcoded scale_factor: 1.0), and a meta block carrying the crop's
origin and display_id (see Q1 below):
{
"payload": {
"image_base64": "...",
"mime_type": "image/jpeg",
"width": 800,
"height": 500,
"monitor": 0,
"region": [100, 200, 900, 700],
"display_scale": 2.0,
"logical_size": [1440, 900],
"meta": {
"origin": [100, 200],
"display_id": 0
}
}
}Semantics:
regionandwindow_idare mutually exclusive.window_idis opaque — the platform got it fromWINDOW_LIST_RESPONSEand passes it back as-is. The shim is free to compose pid+window-handle or AX-ref inside.- If a window is occluded or off-screen, the shim returns the image of the
window's bounds anyway (the OS reports cached bitmap on macOS/Windows;
on Linux X11 we surface
WINDOW_NOT_VISIBLEbecause XComposite isn't guaranteed).
Coordinates on the wire are always display-global, unscaled, top-left-origin virtual-display pixels. Always. There is no region-local or window-local coordinate mode.
regionon a screenshot request only crops the image returned; it does not shift any subsequentINPUT_ACTION.coordinate. The shim never owns coordinate state. If the platform asks for a 800×500 crop starting at(100, 200)and then sendsINPUT_ACTION.coordinate = [150, 250], the click lands at(150, 250)in display-global pixels — i.e. 50 px right and 50 px down from the crop's top-left, but expressed in the same coordinate space as a full-screen click.SCREENSHOT_RESPONSE.meta.originechoes the[x, y]top-left of the returned crop (=[region[0], region[1]]whenregionwas set, or[0, 0]for a full display) so the platform can map model output back if it needs to.SCREENSHOT_RESPONSE.meta.display_idechoes which display the crop came from (matchesDISPLAY_INFO_RESPONSE.monitors[i].index). Same display IDs used inINPUT_ACTIONvalidation.INPUT_ACTIONrejects anycoordinateoutside the union of connected display bounds withINPUT_OUT_OF_BOUNDS(new error code). The error payload echoes the valid display rects so the platform can self-correct on the next turn instead of guessing what happened.
Example INPUT_OUT_OF_BOUNDS error for a (10000, 10000) click on a
single-display 1920×1080 setup:
{
"type": "ERROR",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"code": "INPUT_OUT_OF_BOUNDS",
"message": "coordinate (10000, 10000) is outside the union of connected display bounds",
"fatal": false,
"details": {
"requested_coordinate": [10000, 10000],
"displays": [
{"index": 0, "origin": [0, 0], "size": [1920, 1080]}
]
}
}
}The existing seven actions stay. We expand the action vocabulary and add
modifier-key + button + duration support so the surface matches Anthropic's
computer_20251124 and OpenAI's computer-use action set without forcing
the platform to chunk a single agent intent across multiple wire ops:
{
"type": "INPUT_ACTION",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"action": "left_click",
"coordinate": [500, 300],
// New: button selector (default = action-implied)
"button": "left", // "left" | "middle" | "right"
// New: modifiers held during click/scroll (mirrors Anthropic's "text" field)
"modifiers": ["shift"], // subset of: "shift" | "ctrl" | "alt" | "super"
// New: scroll amount instead of clicks (matches model semantics)
"scroll_amount": 3,
"scroll_direction": "down",
// New: hold duration for hold_key
"duration_ms": 1500,
// Existing
"text": null,
"key": null,
"direction": null,
"clicks": null
}
}New action verbs (additive — old ones keep working):
| Action | Required fields | Notes |
|---|---|---|
middle_click |
coordinate |
Same shape as left/right. |
triple_click |
coordinate |
One wire op, not three (the OS distinguishes triple-click for word/line select). |
mouse_down |
coordinate, button |
Press without release; pairs with mouse_up. |
mouse_up |
coordinate, button |
Release. The pair lets the platform build drags the shim hasn't anticipated (rubber-band select, drawing). |
drag |
path: [[x,y], ...], button |
Single-op drag along a polyline. The shim does press → move-through-each-point → release as one host gesture. Replaces the platform having to thread mouse_down/mouse_move/mouse_up. |
hold_key |
key, duration_ms |
Press a key (or chord) and hold for duration_ms. Distinct from key, which presses-and-releases. |
wait |
duration_ms |
Block the request for duration_ms then ACK. Lets the model insert a settling pause (animations, page loads) without round-tripping a no-op screenshot. Capped at 5000 ms server-side. |
The click/scroll actions get modifiers (Shift+click for range-select,
Cmd+click for tab-open, Shift+scroll for horizontal). This is exactly how
Anthropic's tool surfaces it in computer_20251124: a text field on
click/scroll holding the modifier name. We call ours modifiers because
"text" is overloaded with the type action's text.
The existing key action accepts ctrl+s-style chords. Two clarifications:
- Modifier vocabulary:
shift,ctrl,alt,super,meta(alias for super on POSIX, win on Windows). The shim normalizes per-OS. - Single keys:
enter,tab,esc,space,up,down,left,right,pageup,pagedown,home,end,backspace,delete,f1-f24. Lowercase ASCII letters and digits are accepted as-is.
Today _pyautogui.write is character-by-character. We add an optional
paste: true field on the type action that the shim implements by
stashing the text on the OS clipboard, sending Cmd/Ctrl+V, and (only when
the platform asks) restoring the previous clipboard. Why: pasting a 4 KB
form value char-by-char is ~80 seconds at default 20 ms interval and
trips IME/autocomplete on web forms.
Behavior — locked:
- Threshold.
paste: trueis only honored whenlen(text) >= 200. Under that threshold the shim falls through to per-key typing, which avoids the clipboard race for short strings that don't actually need it. - Default = clobber-and-document.
paste: trueoverwrites the clipboard, sends the OS paste hotkey, and does not restore. This is the safe-by-default for password-manager + paste interactions: if the agent intentionally pasted something, the user's clipboard now contains it, period. No surprise reversal. - Opt-in restore via
preserve_clipboard: true. When the platform explicitly asks, the shim snapshots the clipboard before pasting and restores it afterwards — but skips the restore if the OSchangeCount/ clipboard-sequence-number advanced between snapshot and restore. A bumped sequence number means the user (or another app) copied something during the paste, and clobbering that with the pre-paste snapshot would lose their action. - Race window. Snapshot → write → hotkey → wait-for-paste → restore
is 50–500 ms wall-time depending on OS and target app. During that
window other apps see the pasted text on the clipboard. This is not
papered over: it's a security-relevant trade-off and the user
accepts it by enabling
--enable-clipboard. Thepreserve_clipboardpath adds a slim "user-action-during-paste cancels restore" protection but does not close the snooping window itself.
{
"action": "type",
"text": "long form value...",
"paste": true,
"preserve_clipboard": false
}InputActionPayload.paste and InputActionPayload.preserve_clipboard
both default to false. With paste: false (default), preserve_clipboard
is ignored.
Cheap. Lets the model verify a mouse_move landed without paying a
screenshot's bytes.
{
"type": "CURSOR_POSITION_REQUEST",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {}
}{
"type": "CURSOR_POSITION_RESPONSE",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"x": 512,
"y": 384,
"monitor": 0
}
}Replaces the platform having to read screen_resolution from HELLO and
hope it's still correct (users plug in monitors, rotate iPads as displays,
switch Spaces). Also surfaces per-monitor scale, which HELLO doesn't.
{
"type": "DISPLAY_INFO_REQUEST",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {}
}{
"type": "DISPLAY_INFO_RESPONSE",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"monitors": [
{
"index": 0,
"primary": true,
"physical_size": [3840, 2160],
"logical_size": [1920, 1080],
"scale": 2.0,
"origin": [0, 0]
},
{
"index": 1,
"primary": false,
"physical_size": [2560, 1440],
"logical_size": [2560, 1440],
"scale": 1.0,
"origin": [1920, 0]
}
]
}
}A multi-app workflow ("copy this row from the spreadsheet into the email
draft") needs to know what windows exist. Per cua-driver's PARITY.md,
list_windows is VERIFIED on Windows and "code ready / audit pending" on
the others — same posture for us.
{
"type": "WINDOW_LIST_REQUEST",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"app_bundle_id": null, // optional: filter by app
"include_minimized": false,
"include_offscreen": false
}
}{
"type": "WINDOW_LIST_RESPONSE",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"windows": [
{
"window_id": "opaque-shim-token",
"pid": 12345,
"app_name": "Safari",
"app_bundle_id": "com.apple.Safari",
"title": "AutoGPT — Dashboard",
"bounds": [100, 100, 1820, 1080],
"monitor": 0,
"is_focused": true,
"is_minimized": false,
"is_fullscreen": false
}
],
"truncated": false
}
}window_id is a shim-minted opaque UUID with a win_ prefix (e.g.
win_3f9a8c12-...), never reused. The mapping table
{uuid: native_handle} is wiped on every HELLO (process restart and
reconnect both invalidate IDs).
The shim maintains the mapping for its process lifetime. At USE time
(WINDOW_FOCUS, SCREENSHOT_REQUEST.window_id, anything that takes a
window_id), the shim re-verifies the native handle by checking the
window's (pid, class_name, creation_timestamp) triple against what was
captured at LIST time. If any of those changed, the window was destroyed
and the OS reassigned the handle; the shim returns WINDOW_STALE
never silently re-binds. The platform must re-issue WINDOW_LIST_REQUEST
to get a fresh mapping.
Example WINDOW_LIST_RESPONSE:
{
"payload": {
"windows": [
{
"window_id": "win_3f9a8c12-4b1d-4e8a-9c2a-0f7b8e9d6c5f",
"pid": 12345,
"app_name": "Safari",
"app_bundle_id": "com.apple.Safari",
"title": "AutoGPT — Dashboard",
"bounds": [100, 100, 1820, 1080],
"monitor": 0,
"is_focused": true,
"is_minimized": false,
"is_fullscreen": false
}
],
"truncated": false
}
}Example WINDOW_STALE error:
{
"type": "ERROR",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"code": "WINDOW_STALE",
"message": "window_id win_3f9a8c12-... no longer maps to a live window",
"fatal": false,
"details": {
"window_id": "win_3f9a8c12-4b1d-4e8a-9c2a-0f7b8e9d6c5f",
"hint": "re-list windows"
}
}
}{
"type": "WINDOW_FOCUS",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"window_id": "opaque-shim-token",
"raise": true // bring to front; false = focus without raising (X11 only)
}
}Responds with ACK. On macOS this is AXRaise on the AXUIElementRef; on
Windows, SetForegroundWindow (best-effort per Win32's foreground-lock
rules — cua-driver flagged this as a known caveat); on Linux X11,
XRaiseWindow + _NET_ACTIVE_WINDOW.
get_window_state— covered byWINDOW_LIST_RESPONSEalready.move_window/resize_window— wait for v2 once we see real demand.close_window— too destructive for v1; the agent can Cmd+W viakey.
Lists what's running, not what's installed. Cheaper and more useful for "is Slack open already?".
{
"type": "APP_LIST_REQUEST",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": { "include_background": false }
}{
"type": "APP_LIST_RESPONSE",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"apps": [
{
"pid": 12345,
"name": "Safari",
"bundle_id": "com.apple.Safari",
"executable_path": "/Applications/Safari.app/Contents/MacOS/Safari",
"is_frontmost": true,
"window_count": 3
}
]
}
}{
"type": "APP_LAUNCH",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"bundle_id": "com.apple.Safari", // macOS preferred
"executable_path": null, // Windows/Linux preferred — full path
"args": [],
"activate": true // bring to front after launch
}
}Responds with ACK containing the launched pid, e.g.
{ "ok": true, "pid": 67890 }. The shim uses, in order of preference:
- macOS:
NSWorkspace.openApplicationvia PyObjC (no shell, no focus steal during background launches — matches cua-driver's design). - Windows:
ShellExecuteviapywin32. - Linux:
subprocess.Popenwithstart_new_session=TrueandXDG_OPENfallback ifexecutable_pathis a.desktopfile.
We deliberately do not fall through to EXECUTE_COMMAND — the audit
log distinguishes "agent launched an app" from "agent ran a shell command",
and conflating them costs us reviewability.
list_appsfor installed-but-not-running apps (/Applicationsscan,Start Menuenumeration). Big per-OS surface for a niche need; defer.- AppleScript / DDE / D-Bus app control. v2.
Real workflows need to move data between apps without re-typing it. A multi-app workflow is half-blind without clipboard access.
{
"type": "CLIPBOARD_READ",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"format": "text" // "text" | "image" — v1 only
}
}{
"type": "CLIPBOARD_READ_RESPONSE",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"format": "text",
"content": "hello",
"size_bytes": 5
}
}{
"type": "CLIPBOARD_WRITE",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"format": "text",
"content": "hello"
}
}Responds with ACK.
Both clipboard directions are bounded by
HELLO_ACK.max_file_size_bytes. CLIPBOARD_READ rejects an oversized backend
result, and CLIPBOARD_WRITE measures UTF-8 bytes before touching the OS
clipboard; either returns FILE_TOO_LARGE.
Security note: clipboard content is redacted in the audit log (size +
SHA-256 prefix only, never the value — same rule as INPUT_ACTION.text
per AUDIT_LOG.md). A CLIPBOARD_READ always emits a
user-visible audit event regardless of redaction.
Default-deny. Clipboard ops are off unless the user opts in at install
time. Two CLI flags on autogpt-shim start:
| Flag | Default | Effect |
|---|---|---|
--enable-clipboard |
off | Enables CLIPBOARD_WRITE and a writeback-only flavor of CLIPBOARD_READ: CLIPBOARD_READ returns content only if the shim itself wrote it within the last 30 seconds. Anything the user (or any other app) put on the clipboard returns CLIPBOARD_CONCEALED with reason: "writeback_only". |
--enable-clipboard-read-foreign |
off (requires --enable-clipboard) |
Lets CLIPBOARD_READ return any clipboard content except when the current pasteboard carries an org.nspasteboard.ConcealedType marker (macOS, the de-facto password-manager convention) or CF_PRIVATE clipboard format (Windows). Concealed contents → CLIPBOARD_CONCEALED error. |
Without --enable-clipboard, all clipboard sub-ops return
FEATURE_NOT_SUPPORTED and clipboard is omitted from
computer_use_features_coarse. Without
--enable-clipboard-read-foreign, CLIPBOARD_READ is the strict
"only what we just wrote" variant.
The 30-second writeback window is tracked by the shim by stashing the
SHA-256 + changeCount/sequence-number at write time; a subsequent
CLIPBOARD_READ checks that the stashed value still matches the live
clipboard contents (defeats the "shim wrote X, password manager
overwrote with Y" race) before returning. A mismatch returns
CLIPBOARD_CONCEALED with reason: "writeback_overwritten".
Example CLIPBOARD_CONCEALED error (concealed-type case):
{
"type": "ERROR",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"code": "CLIPBOARD_CONCEALED",
"message": "clipboard contents marked concealed by source application",
"fatal": false,
"details": {
"reason": "concealed_type",
"marker": "org.nspasteboard.ConcealedType"
}
}
}Example for the writeback-only case (default --enable-clipboard,
agent tries to read user-copied text):
{
"payload": {
"code": "CLIPBOARD_CONCEALED",
"message": "clipboard contents not written by the shim within the last 30s",
"fatal": false,
"details": {
"reason": "writeback_only",
"writeback_age_seconds": null
}
}
}- Image clipboard (need PNG round-trip; defer with
screenshot.regionwhich covers most use cases). - File-list clipboard (Finder copy → paste-as-files in another app); maps poorly to a string payload.
- Clipboard sync with the platform — the platform isn't running on the user's machine, so there's no shared clipboard to sync to; defer.
Lets the platform pre-flight a workflow ("do I have Screen Recording?
do I have Accessibility?") before issuing a doomed INPUT_ACTION that
silently does nothing on macOS.
{
"type": "PERMISSIONS_CHECK_REQUEST",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"permissions": ["screen_recording", "accessibility", "input_monitoring"]
}
}{
"type": "PERMISSIONS_CHECK_RESPONSE",
"id": "req-uuid",
"ts": 1712345678.0,
"payload": {
"permissions": {
"screen_recording": "granted", // "granted" | "denied" | "unknown" | "not_applicable"
"accessibility": "denied",
"input_monitoring": "unknown"
}
}
}not_applicable is the answer on Linux for "screen_recording" (X11
doesn't have a TCC equivalent — it's allowed-by-default or
blocked-by-compositor). The platform should treat unknown as "try and
see"; the shim returns unknown rather than guessing.
This op does not open System Settings or prompt the user. Triggering permission prompts is a PLATFORM_HOOKS.md install-time concern; mid-session prompts would interrupt the agent loop in a way the platform can't recover from.
Layered, so the user only ever hits the prompt at a moment they're expecting to ("I just ran the installer") and not in the middle of an agent loop:
-
autogpt-shim doctorsubcommand. Run at install time, manually, or by the install script. On macOS, callsAXIsProcessTrustedWithOptions({"AXTrustedCheckOptionPrompt": True})to proactively surface the consent dialog the first time it runs; subsequent runs are silent. Also probesCGPreflightScreenCaptureAccess()and reports both states. On Linux, detects X11 vs Wayland. On Windows, reports UAC elevation level.Example output (macOS, first run, AX not yet granted):
$ autogpt-shim doctor ✓ allowed_root: ~/Documents/autogpt-workspace exists, writable ✓ keychain: keyring available (macOS Keychain Services) ⚠ Accessibility: not granted — System Settings → Privacy & Security → Accessibility, enable autogpt-shim ✓ Screen Recording: granted ✓ display: 2 displays detected (3024×1964 + 2560×1440) ○ computer_use: degraded (Accessibility missing) exit 78Exit code is
0when all requested capabilities are healthy,78(config error) when computer-use was requested but a prerequisite permission is missing. -
Daemon refuses to bind WS when consent is missing. If
computer_useis in the shim config's requested capabilities ANDAXIsProcessTrusted()returns false, the daemon writes a structured record to the audit log (code: PERMISSION_PENDING, the missing permissions, a hint that the user should runautogpt-shim doctor), logs to stderr, and exits with code78. Withoutcomputer_usein the requested set, the daemon binds normally. -
launchd
KeepAlivetriggers re-launch on TCC change. Theautogpt-shim installtemplate includes:<key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict>
This means: any non-zero exit relaunches the daemon. When the user grants Accessibility in System Settings, the OS modifies the TCC.db, which (a) doesn't notify the running process directly but (b) the next time
AXIsProcessTrusted()is called, the new state is visible. Combined withKeepAlive: SuccessfulExit=false, the chain is: daemon exits 78 → launchd re-spawns → new process sees fresh TCC state → binds WS. Documented in CROSS_PLATFORM.md. -
PERMISSION_PENDINGerror on revocation mid-session. If the user revokes Accessibility while the daemon is running, the nextINPUT_ACTIONor AX-dependent op catches the OS-level denial and returnsPERMISSION_PENDING(new error code, distinct fromCAPABILITY_NOT_GRANTEDbecause the granting context here is the OS, not the platform). The platform LLM retries on the next turn; the user, after seeing the error in their UI, can re-grant.Example
PERMISSION_PENDINGerror:{ "type": "ERROR", "id": "req-uuid", "ts": 1712345678.0, "payload": { "code": "PERMISSION_PENDING", "message": "Accessibility permission revoked or not yet granted", "fatal": false, "details": { "permission": "accessibility", "platform": "darwin", "hint": "Open System Settings → Privacy & Security → Accessibility and enable autogpt-shim" } } } -
Linux / Windows
doctor. Returns0with platform-specific advice: on Linux, distinguishes X11 (OK) vs Wayland (computer-use degraded by default), and warns on missingxclip/wl-clipboard. On Windows, reports UAC level and warns that injecting into elevated windows from a non-elevated shim is impossible.
New message types added in v1:
CURSOR_POSITION_REQUEST/CURSOR_POSITION_RESPONSEDISPLAY_INFO_REQUEST/DISPLAY_INFO_RESPONSEWINDOW_LIST_REQUEST/WINDOW_LIST_RESPONSEWINDOW_FOCUS(responds withACK)APP_LIST_REQUEST/APP_LIST_RESPONSEAPP_LAUNCH(responds withACKcarryingpid)CLIPBOARD_READ/CLIPBOARD_READ_RESPONSECLIPBOARD_WRITE(responds withACK)PERMISSIONS_CHECK_REQUEST/PERMISSIONS_CHECK_RESPONSE
Existing message types extended:
SCREENSHOT_REQUEST— addsregion,window_id,format,include_cursorSCREENSHOT_RESPONSE— addsregion,display_scale,logical_sizeINPUT_ACTION— addsbutton,modifiers,scroll_amount,duration_ms,path,paste, and new action verbs:middle_click,triple_click,mouse_down,mouse_up,drag,hold_key,wait
New error codes introduced by the computer-use surface:
FEATURE_NOT_SUPPORTED— feature not inHELLO.computer_use_features, or attempted on an unsupported OS / session (e.g. Wayland input).WINDOW_STALE—window_idno longer maps to a live window. Caller must re-list (Q2).INPUT_OUT_OF_BOUNDS—INPUT_ACTION.coordinateis outside the union of connected display rects. Error carries the valid display rects (Q1).CLIPBOARD_CONCEALED— clipboard contents are not readable under the current sandbox policy (Q3): either the active flag set doesn't permit reading foreign contents, the writeback window has expired or been overwritten, or the source app marked the contents concealed.PERMISSION_PENDING— an OS permission required for the op was not granted (or was revoked mid-session). Q5; distinct fromCAPABILITY_NOT_GRANTED, which means the platform didn't grant the shim the capability.
These are the bigger swings that need a real design pass and probably their own RFCs. Listed so we don't accidentally reinvent them in v1.
cua-driver exposes get_accessibility_tree (Swift-only today, Rust has
parity). Returns the OS-native a11y hierarchy: roles, labels, values,
frames. This is the right answer for grounding ("click the Submit
button") without pixel-coordinate guesswork — OSWorld papers consistently
show a11y-augmented agents outperform vision-only.
Why deferred: macOS AX requires pyobjc-framework-ApplicationServices
and an AXObserver per app, Windows wants pywinauto/uiautomation
(big trees, slow walks), Linux X11 wants AT-SPI + D-Bus. Each OS has its
own role taxonomy. v2 should normalize to a single cross-OS schema —
roles like button, text_field, link, image, with actions listing
what's invokable. Don't ship until we've prototyped on all three OSes.
OSWorld's strongest agents annotate the screenshot with numbered bounding boxes the model refers to by ID ("click element 7"). This needs an OCR backend (Tesseract, Apple Vision, Windows OCR), an element detector, and a render step that returns the marked-up screenshot. We don't want to build that in the shim (heavy deps, model quality matters) — it lives better on the platform side, with the shim providing raw screenshots + a11y trees as input.
AppleScript on macOS, PowerShell + COM on Windows, gdbus/AT-SPI on
Linux. Direct API access into apps (set Safari's URL field, query
Calendar's events, drive Excel) skips the whole screenshot-and-click
dance. Huge power, huge per-app surface — defer to v2 once we know which
3-5 apps are in the critical path.
cua-driver has set_recording + replay_trajectory. Lets a user demo a
workflow once, then the agent replays it later. Useful for cron-style
"every Monday morning, do this" automation. The interesting research
question is how the agent decides what's parameterizable in a recorded
trace — pure replay is fragile (timestamps, dialog placement). Defer
until we have the demand and a story on robustness.
"Drag this file from the workspace into the chat window." Native drag
sources need OS-level pasteboard handoff (NSPasteboardItem on macOS,
DoDragDrop on Windows, XDND on Linux X11). Different per OS, and the
target app has to accept it. INPUT_ACTION.drag covers visual drags
that originate from a screen position; v2's DRAG_FILE covers drags
that originate from a file and need to look to the target app like a
Finder drag.
v1 keeps coordinate in primary-monitor logical pixels with origin at
top-left. Multi-monitor setups need per-monitor coordinates or a global
virtual coordinate space. The right call depends on how the model emits
coordinates after seeing a multi-monitor screenshot.region — likely
v2 adds coordinate_space: "global"|"monitor"|"window" and a
monitor_index / window_id qualifier.
get_notifications, get_active_input_method, is_screen_locked,
get_focused_text_field_value — useful, OS-specific, not on the
critical path for "drive a form across three apps." Punt.
Legend: ✅ supported, 🟡 supported with caveat (named), ❌ not supported (reason).
| Wire op / feature | macOS | Linux (X11) | Linux (Wayland) | Windows | WSL2 |
|---|---|---|---|---|---|
SCREENSHOT_REQUEST (full display) |
✅ via mss + Quartz |
✅ via mss |
🟡 portal+pipewire only; default ❌ | ✅ via mss |
🟡 WSLg via X server only |
SCREENSHOT_REQUEST.region |
✅ | ✅ | ❌ (same Wayland block) | ✅ | 🟡 same as full |
SCREENSHOT_REQUEST.window_id |
🟡 via CGWindowListCreateImage (needs Screen Recording) |
🟡 X11 XComposite-dependent; off-screen windows may return stale bitmaps | ❌ | ✅ via PrintWindow / BitBlt |
❌ no Win32 access from inside the Linux VM |
SCREENSHOT_REQUEST.include_cursor |
✅ | ✅ | ❌ | ✅ | 🟡 |
INPUT_ACTION (existing 7 verbs) |
✅ pyautogui → Quartz | ✅ pyautogui → Xlib | ❌ blocked | ✅ pyautogui → SendInput | 🟡 only into X server, not Win32 |
INPUT_ACTION.middle_click |
✅ | ✅ | ❌ | ✅ | 🟡 |
INPUT_ACTION.triple_click |
✅ native triple-click event | ✅ via three click events ~50ms apart (X11 has no native triple) | ❌ | ✅ | 🟡 |
INPUT_ACTION.modifiers on click/scroll |
✅ | ✅ | ❌ | ✅ | 🟡 |
INPUT_ACTION.mouse_down/mouse_up |
✅ | ✅ | ❌ | ✅ | 🟡 |
INPUT_ACTION.drag (path) |
✅ | 🟡 X11 may glitch if path is > 100 pts; we sub-sample | ❌ | ✅ | 🟡 |
INPUT_ACTION.hold_key |
✅ | ✅ | ❌ | ✅ | 🟡 |
INPUT_ACTION.wait |
✅ | ✅ | ✅ | ✅ | ✅ |
INPUT_ACTION.type.paste |
✅ Cmd+V via NSPasteboard | ✅ Ctrl+V via xclip/wl-clipboard | 🟡 Wayland: clipboard works, paste keystroke blocked | ✅ Ctrl+V | 🟡 X-server clipboard only |
CURSOR_POSITION_REQUEST |
✅ via Quartz | ✅ via XQueryPointer |
🟡 Wayland: returns last-known from pyautogui, may lag |
✅ via GetCursorPos |
🟡 |
DISPLAY_INFO_REQUEST |
✅ via Quartz CGGetActiveDisplayList |
✅ via xrandr |
🟡 via wlr-randr if present, else single-display fallback |
✅ via EnumDisplayMonitors |
🟡 reports the X server's view |
WINDOW_LIST_REQUEST |
✅ via CGWindowListCopyWindowInfo |
🟡 via wmctrl / EWMH; needs window manager that exposes _NET_CLIENT_LIST |
❌ no equivalent in Wayland | ✅ via EnumWindows |
🟡 X server windows only |
WINDOW_FOCUS |
✅ via AXRaise (needs Accessibility) |
✅ via _NET_ACTIVE_WINDOW |
❌ | 🟡 SetForegroundWindow honors foreground-lock rules (best-effort) |
🟡 |
APP_LIST_REQUEST |
✅ via NSWorkspace.runningApplications |
✅ via /proc + .desktop heuristics |
✅ same as X11 (procfs is compositor-agnostic) | ✅ via EnumProcesses + window-owner |
🟡 |
APP_LAUNCH |
✅ via NSWorkspace.openApplication (PyObjC) |
✅ via Popen + .desktop resolution | ✅ same as X11 | ✅ via ShellExecute (pywin32) |
🟡 launches in WSL2, not on Windows host |
CLIPBOARD_READ (text) |
✅ via NSPasteboard |
✅ via xclip if installed, else dependency-missing |
🟡 via wl-paste if installed |
✅ via OpenClipboard |
🟡 X server clipboard only |
CLIPBOARD_WRITE (text) |
✅ | ✅ via xclip |
🟡 via wl-copy |
✅ | 🟡 |
PERMISSIONS_CHECK_REQUEST |
✅ TCC: ScreenCapture / Accessibility / InputMonitoring | ✅ returns not_applicable for TCC keys; granted if display server is X11 |
✅ returns denied for screen_recording unless portal session live |
✅ checks process elevation + UIAccess | ✅ |
Across the board: Wayland is the largest single capability cliff. On
a Wayland session the shim should advertise computer_use_features: ["wait", "display.info", "app.list", "app.launch", "clipboard.read", "clipboard.write", "permissions.check"] and omit everything that needs
input injection or screen capture. The platform should then degrade to
a "talk through bash + files only" loop. This is per
CROSS_PLATFORM.md → Linux which already gates the
computer_use capability on XDG_SESSION_TYPE=x11; with the
fine-grained computer_use_features list we can additionally allow a
"clipboard + app launch only" Wayland mode that today is wholesale
blocked.
WSL2's column is consistently 🟡 because input/screen capture only reaches
the X server inside the VM, not the Windows host — useful for testing
the shim but rarely what the user actually wants. The shim advertises
computer_use_features honestly; the platform decides whether to use it.
The shim populates HELLO.payload.computer_use_features at startup by
running a lightweight probe per feature:
- Dependencies present? —
pyautogui,mss,Pillowfor v0; per-OS adds (pyobjc-framework-Quartzon macOS,pywin32on Windows,python-xlib/wmctrlon Linux X11,wl-clipboardfor Wayland clipboard). - OS API reachable? — try
mss.mss().monitorsonce for display probe; tryNSWorkspace.sharedWorkspace()once for macOS app probe; etc. Failure → drop the feature, don't crash startup. - Compositor / session type? —
XDG_SESSION_TYPE,WAYLAND_DISPLAY,DISPLAYenv vars on Linux;sys.platformeverywhere else. - Permissions cached? — on macOS, run a no-op
CGEventCreateand catch the TCC failure; if it fails, drop input features. Don't trigger the prompt mid-session (see PERMISSIONS_CHECK_REQUEST contract above).
The list is sticky for the session — we don't re-probe mid-session.
Reconnect → new HELLO → re-probe.
| cua-driver tool | Why we skipped it |
|---|---|
set_value (direct AX-set on text fields) |
Big security surface — agent can stuff text into a focused password field without user-visible keystrokes or audit trail. cua-driver's MCP-stdio model trusts the operator; our threat model has the platform behind a token over WebSocket. Force type + paste: visible cursor activity, observable in audit. |
get_accessibility_tree |
v2. See above — needs per-OS schema normalization we haven't done. |
page tool (CDP / AppleScript page introspection) |
Overlaps with platform's existing browser tooling. Don't duplicate. |
agent_cursor.* (5 tools) |
This is a visual animated cursor overlay so end-users can see the agent acting. Genuinely cool UX but pure presentation; doesn't affect what the agent can do. Belongs in a v2 polish pass. |
set_recording / replay_trajectory |
v2. Story on parameterization of recorded traces is unsolved. |
launch_app via NSWorkspace as the only mechanism |
We use NSWorkspace on macOS (great) but fall back to ShellExecute / Popen elsewhere rather than insisting on the "no shell" purity. APP_LAUNCH is a distinct audit event from EXECUTE_COMMAND, which gives us the observability cua-driver was chasing without requiring per-OS native API parity. |
get_config / set_config |
cua-driver's are about runtime config of the driver itself (cursor animation, etc.). Our shim is configured at install via PLATFORM_HOOKS.md and we don't want a remote config-write surface. |
zoom as a separate tool |
Folded into SCREENSHOT_REQUEST.region. Anthropic's computer_20251124 zoom action maps 1:1 to a region screenshot; no need for a separate wire op. |
The five questions that were open at the v0.1 draft are now decided inline in the spec above. Quick index:
| Question | Section | Summary |
|---|---|---|
Q1 — coordinate space with region |
Extended: SCREENSHOT_REQUEST → Q1 |
Display-global, unscaled, top-left-origin virtual-display pixels always. region only crops the image. INPUT_OUT_OF_BOUNDS echoes display rects. |
Q2 — window_id lifetime |
New: WINDOW_LIST_RESPONSE → Q2 |
Shim-minted win_<uuid>, never reused, wiped on HELLO. Re-verify (pid, class_name, creation_timestamp) at USE time; mismatch → WINDOW_STALE. |
| Q3 — clipboard sandbox | New: CLIPBOARD_READ / CLIPBOARD_WRITE → Q3 |
Default-deny. --enable-clipboard + optional --enable-clipboard-read-foreign. 30 s writeback-only window. CLIPBOARD_CONCEALED. |
Q4 — paste:true and password managers |
type action — input method (Q4) |
Threshold ≥200 chars. Clobber-and-document by default; opt-in preserve_clipboard: true with changeCount-skip-restore. Race window honestly documented. |
| Q5 — macOS TCC first-prompt UX | PERMISSIONS_CHECK_REQUEST → Q5 |
autogpt-shim doctor + daemon refuses to bind when computer-use requested but ungranted + launchd KeepAlive: SuccessfulExit=false + PERMISSION_PENDING on mid-session revocation. |