Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

6.7 KiB

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?