a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
150 lines
6.7 KiB
Markdown
150 lines
6.7 KiB
Markdown
# Proposal: native `computer` tool for macOS computer use
|
|
|
|
## Summary
|
|
|
|
Add a single native tool, **`computer`**, that lets the agent observe and control
|
|
the macOS GUI — screenshots, the accessibility (AX) tree, mouse/keyboard input,
|
|
window/app management, and clipboard — through one `action`-dispatched interface.
|
|
|
|
This mirrors the existing **`browser`** tool (`crates/jcode-app-core/src/tool/browser.rs`):
|
|
one registered tool, an `action: String` that selects a sub-operation, with optional
|
|
typed params. It gives jcode a closed control loop (*see screen → decide → act*)
|
|
without depending on a browser or external automation tooling.
|
|
|
|
## Motivation
|
|
|
|
- The agent can already drive a browser; it cannot drive native macOS apps, system
|
|
UI, or anything outside the browser sandbox.
|
|
- "Computer use" agents need exactly three primitives: **read the screen**, **read
|
|
UI structure**, and **synthesize input**. macOS exposes all three through the
|
|
Accessibility / Quartz Event Services / ScreenCaptureKit stack.
|
|
- A single, well-scoped tool keeps the tool surface small and the permission story
|
|
in one place.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
crates/jcode-macos-control/ (new) cfg(target_os = "macos") platform crate
|
|
└─ AX (accessibility-sys), CGEvent (core-graphics),
|
|
CoreFoundation (core-foundation), screenshots (ScreenCaptureKit / CGDisplay),
|
|
app/window control (objc2 + objc2-app-kit), clipboard (objc2 NSPasteboard)
|
|
|
|
crates/jcode-app-core/src/tool/computer.rs (new) ComputerTool
|
|
└─ thin dispatch layer: parse input -> call jcode-macos-control -> ToolOutput
|
|
└─ registered in crates/jcode-app-core/src/tool/mod.rs base_tools()
|
|
```
|
|
|
|
- All native APIs are reached through existing Rust bindings (`objc2`,
|
|
`accessibility-sys`, `core-graphics`, `core-foundation`) — **no Swift/ObjC build
|
|
step**.
|
|
- On non-macOS targets the tool still registers but every action returns a clean
|
|
`unsupported on this platform` error, so the tool list stays stable across OSes.
|
|
- `screenshot` returns its image via `ToolOutput::with_image` (base64), matching how
|
|
`browser` returns screenshots today.
|
|
|
|
## Permissions (the important part)
|
|
|
|
macOS splits this across **four** TCC permissions. Programmatic *request* support
|
|
differs per permission:
|
|
|
|
| Permission | Used for | Programmatic request |
|
|
|---|---|---|
|
|
| **Accessibility** | drive other apps' UI, inject `CGEvent` input | ⚠️ prompt + deep-link only; user must toggle |
|
|
| **Screen Recording** | screenshots / `get_ui_tree` of some apps | ✅ `CGRequestScreenCaptureAccess()` |
|
|
| **Input Monitoring** | reading the global input stream | ✅ `IOHIDRequestAccess(...)` |
|
|
| **Automation** (Apple Events) | scripting cooperating apps | ✅ prompts on first send, per target app |
|
|
|
|
**Accessibility is the one that cannot be auto-granted** (Apple's anti-malware
|
|
boundary), and it is required for input injection. Best achievable flow, exposed via
|
|
the `request_permissions` action:
|
|
|
|
1. `AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt: true])` — shows the
|
|
system dialog *and auto-adds jcode to the Accessibility list* (toggled off).
|
|
2. Deep-link to the exact pane:
|
|
`open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"`.
|
|
3. Poll `AXIsProcessTrusted()` until granted, then report ready.
|
|
|
|
So the experience becomes **one prompt + one toggle**, not "go hunt in Settings" —
|
|
but never zero-touch for Accessibility.
|
|
|
|
`check_permissions` reports the current state of all four so the agent can tell the
|
|
user precisely what is missing before attempting control.
|
|
|
|
## Actions
|
|
|
|
`action` (required) selects the operation. Params below are optional and validated
|
|
per action.
|
|
|
|
**Permissions**
|
|
- `check_permissions` → status of accessibility / screen-recording / input-monitoring
|
|
- `request_permissions` → prompt + deep-link flow above
|
|
|
|
**Observe**
|
|
- `screenshot` — `{ display?, window_id?, region? }` → image
|
|
- `get_ui_tree` — `{ pid? | frontmost, max_depth? }` → serialized AX tree (role, title, value, position, size, actions)
|
|
- `find_element` — `{ role?, title?, value?, pid? }` → matching elements + their identifiers
|
|
- `element_at` — `{ x, y }` → element under the point
|
|
|
|
**Mouse**
|
|
- `move` — `{ x, y }`
|
|
- `click` / `double_click` / `right_click` — `{ x?, y? }` (current position if omitted)
|
|
- `drag` — `{ from: [x,y], to: [x,y] }`
|
|
- `scroll` — `{ x?, y?, dx, dy }`
|
|
|
|
**Keyboard**
|
|
- `type` — `{ text }`
|
|
- `key` — `{ keys: "cmd+shift+4" }` (chord)
|
|
- `key_down` / `key_up` — `{ key }`
|
|
|
|
**Semantic AX (preferred over raw input when available)**
|
|
- `press` — `{ element }` (AXPress)
|
|
- `set_value` — `{ element, value }`
|
|
- `get_value` — `{ element }`
|
|
- `perform_action` — `{ element, ax_action }`
|
|
- `select_menu` — `{ app, path: ["File", "Export…"] }`
|
|
|
|
**Window / app**
|
|
- `list_apps`, `activate_app` `{ app }`
|
|
- `list_windows` `{ pid? }`, `focus_window` `{ window }`
|
|
- `move_window` `{ window, x, y }`, `resize_window` `{ window, w, h }`
|
|
- `minimize_window` / `close_window` `{ window }`
|
|
|
|
**Clipboard**
|
|
- `get_clipboard`, `set_clipboard` `{ text }`
|
|
|
|
> Element identifiers: `find_element` / `get_ui_tree` return stable-enough handles
|
|
> (e.g. `pid` + AX path or a session-scoped element id) that semantic actions accept,
|
|
> so the agent can act structurally instead of by pixel coordinates when possible.
|
|
|
|
## Safety
|
|
|
|
Computer use is high-blast-radius, so:
|
|
|
|
- **Permission-gated** like other powerful tools; refuses early with a clear message
|
|
if Accessibility/Screen Recording is missing.
|
|
- **`dry_run` param** on mutating actions — resolves and reports the target without
|
|
acting.
|
|
- **Screenshot-assisted confirmation** for destructive coordinate clicks (return the
|
|
region/element being targeted).
|
|
- **No global input *capture*** in v1 (we synthesize input but do not log the user's
|
|
keystrokes), keeping us out of Input Monitoring unless a future feature needs it.
|
|
- Prefer **semantic AX actions** over blind coordinate input wherever the element is
|
|
resolvable — more robust and more auditable.
|
|
|
|
## Implementation plan
|
|
|
|
1. `jcode-macos-control` crate: permissions, screenshot, AX read, AX action,
|
|
CGEvent input, window/app control, clipboard. Unit-test the pure parts
|
|
(input parsing, chord parsing, tree serialization).
|
|
2. `ComputerTool` in `tool/computer.rs`: input struct + `action` dispatch +
|
|
schema + description; register `"computer"` in `tool/mod.rs` `base_tools()`.
|
|
3. Default-off / gated rollout + docs in `docs/`.
|
|
4. Follow-up: Windows/Linux backends behind the same tool surface.
|
|
|
|
## Open questions
|
|
|
|
- Element handle format — `pid`+AX-path vs an opaque session-scoped id cache?
|
|
- Should `request_permissions` block-and-poll, or return immediately with status and
|
|
let the agent re-check?
|
|
- Default enablement: opt-in flag vs always-registered-but-gated?
|