Agent Desktop Ffi

Other

C-ABI bindings over agent-desktop's PlatformAdapter. Consumers (Python ctypes, Swift, Node ffi-napi, Go cgo, C++, Ruby fiddle) link libagent_desktop_ffi.{dylib,so,dll} and call `ad_*` functions directly instead of spawning the CLI binary per call.

Install

openclaw skills install agent-desktop-ffi

agent-desktop-ffi

Direct C-ABI access to every PlatformAdapter operation. Build the cdylib with the workspace's release-ffi profile:

cargo build --profile release-ffi -p agent-desktop-ffi

The output is target/release-ffi/libagent_desktop_ffi.dylib (.so on Linux, .dll on Windows) plus a committed C header at crates/ffi/include/agent_desktop.h.

Four reference topics, loaded as needed:

  • ownership.md — who allocates / who frees, for every *mut T the FFI hands back to the caller.
  • error-handling.md — errno-style last-error contract, enum validation, panic boundary.
  • threading.md — macOS main-thread rule, AXIsProcessTrusted inheritance when Python/Node dlopens the cdylib, and the single-owner handle invariant.
  • build-and-link.md — minimum working example for Python ctypes and a C program that links the dylib.

⚠ Core constraints before you integrate

  • Main thread only (macOS). Call every adapter-touching entrypoint (ad_get_tree, ad_resolve_element, ad_execute_action, ad_screenshot, clipboard, launch/close, window ops, observation, notifications, etc.) from the process's main thread. The FFI enforces this at runtime in every build profile — a worker-thread call returns AD_RESULT_ERR_INTERNAL with a diagnostic last-error. On non-macOS platforms the check is a compile-time true; there is no runtime cost.

  • Release profile. cargo build --release produces panic = "abort" — any Rust panic inside an extern "C" fn will SIGABRT the host. Use --profile release-ffi to get the correct panic = "unwind" profile. CI enforces this.

  • Last-error lifetime. Pointers returned by ad_last_error_* remain valid across any number of subsequent successful FFI calls on the same thread. Only the next failing call rotates them. Cache the pointer once, read it as many times as you need.

  • Handle release. Every ad_resolve_element result must be released with ad_free_handle(adapter, handle) on the same adapter that produced it. On macOS this balances the internal CFRetain; on Windows/Linux the call is a no-op but safe to issue.

  • Action policy. ad_execute_action uses the headless policy by default, matching CLI ref commands: no focus stealing and no cursor movement. Use ad_execute_action_with_policy(..., AD_POLICY_KIND_FOCUS_FALLBACK, ...) only when focus-changing behavior is intended, and AD_POLICY_KIND_PHYSICAL only for explicit physical/headed input semantics.

  • Text input privacy. On macOS, explicit focus/physical policy can use the clipboard briefly for non-ASCII text insertion. Keep the default headless policy or set values directly for sensitive text when the target supports it.

  • Enum discriminants. Every #[repr(i32)] enum field is validated at the C boundary — invalid discriminants return AD_RESULT_ERR_INVALID_ARGS instead of undefined behavior.

  • ABI is unstable before 1.0. The header lists the exact current shapes. Anything added or reordered in a later patch is a breaking change; pin the version of libagent_desktop_ffi you link against.

  • ad_get_tree returns a raw adapter tree, not the CLI snapshot. Ref IDs are always null, no skeleton/drill-down pipeline is wired through, and interactive_only / compact follow adapter semantics which may diverge slightly from the CLI's post-processed shape. Use ad_find + ad_get / ad_is for point lookups, or invoke the CLI if you need CLI-parity JSON snapshots.