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 before that adapter is destroyed. 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 headless policy by default.
ad_execute_ref_action_with_policy should also use headless for semantic
ref actions that must fail closed, except AD_ACTION_KIND_TYPE_TEXT: use
AD_POLICY_KIND_FOCUS_FALLBACK for CLI type parity because typing needs
focus but not cursor movement. Use AD_POLICY_KIND_HEADED only for explicit
headed input semantics.
Ref-action preflight. ad_execute_ref_action_with_policy resolves the
ref strictly and runs the live actionability preflight (visible, stable,
enabled, supported action, policy, editable) before dispatching — a disabled
or unsupported target fails before any platform call. On
AD_RESULT_ERR_ACTION_FAILED, the structured check report is available as
JSON via ad_last_error_details(). Details may carry element names, values,
and window titles from the user's screen — treat them as sensitive
diagnostics and keep them out of shared log surfaces.
Action result steps. AdActionResult.steps mirrors the CLI steps
array for activation-chain actions. Each entry has label and outcome
strings and is owned by the result; release it with
ad_free_action_result(&out).
Tracing. CLI --trace is not inherited by the C ABI; FFI hosts should
record AdResult, ad_last_error_*, action results, and host correlation IDs
in their own logs.
No wait surface. The CLI's wait command (element predicates including
--predicate actionable --action ..., window/text/menu/notification waits)
is not exposed over the C ABI. FFI hosts own their own polling loops; the
actionability preflight inside ad_execute_ref_action_with_policy is the
equivalent per-call readiness check.
Text input privacy. On macOS, focus-fallback or headed text insertion may
briefly use the clipboard for non-ASCII text. For sensitive text, prefer
AD_ACTION_KIND_SET_VALUE with AD_POLICY_KIND_HEADLESS when the target
supports settable values. Do not use headless AD_ACTION_KIND_TYPE_TEXT as
CLI-parity ref input; actionability rejects it before dispatch.
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.
Source: lahfir/agent-desktop — distributed by TomeVault.
1---2name: lahfir-agent-desktop-agent-desktop3description: agent-desktop-ffi4---56# agent-desktop-ffi78Direct C-ABI access to every PlatformAdapter operation. Build the9cdylib with the workspace's `release-ffi` profile:1011```sh12cargo build --profile release-ffi -p agent-desktop-ffi13```1415The output is `target/release-ffi/libagent_desktop_ffi.dylib`16(`.so` on Linux, `.dll` on Windows) plus a committed C header at17`crates/ffi/include/agent_desktop.h`.1819Four reference topics, loaded as needed:2021- [ownership.md](references/ownership.md) — who allocates / who frees,22 for every `*mut T` the FFI hands back to the caller.23- [error-handling.md](references/error-handling.md) — errno-style24 last-error contract, enum validation, panic boundary.25- [threading.md](references/threading.md) — macOS main-thread rule,26 AXIsProcessTrusted inheritance when Python/Node dlopens the cdylib,27 and the single-owner handle invariant.28- [build-and-link.md](references/build-and-link.md) — minimum working29 example for Python ctypes and a C program that links the dylib.3031## ⚠ Core constraints before you integrate3233- **Main thread only (macOS).** Call every adapter-touching entrypoint34 (`ad_get_tree`, `ad_resolve_element`, `ad_execute_action`,35 `ad_screenshot`, clipboard, launch/close, window ops, observation,36 notifications, etc.) from the process's main thread. The FFI enforces37 this at runtime in **every build profile** — a worker-thread call38 returns `AD_RESULT_ERR_INTERNAL` with a diagnostic last-error. On39 non-macOS platforms the check is a compile-time true; there is no40 runtime cost.4142- **Release profile.** `cargo build --release` produces43 `panic = "abort"` — any Rust panic inside an `extern "C"` fn will44 `SIGABRT` the host. Use `--profile release-ffi` to get the correct45 `panic = "unwind"` profile. CI enforces this.4647- **Last-error lifetime.** Pointers returned by `ad_last_error_*`48 remain valid across any number of subsequent *successful* FFI calls49 on the same thread. Only the next failing call rotates them. Cache50 the pointer once, read it as many times as you need.5152- **Handle release.** Every `ad_resolve_element` result must be53 released with `ad_free_handle(adapter, handle)` on the same adapter54 that produced it before that adapter is destroyed. On macOS this55 balances the internal `CFRetain`; on Windows/Linux the call is a no-op56 but safe to issue.5758- **Action policy.** `ad_execute_action` uses headless policy by default.59 `ad_execute_ref_action_with_policy` should also use headless for semantic60 ref actions that must fail closed, except `AD_ACTION_KIND_TYPE_TEXT`: use61 `AD_POLICY_KIND_FOCUS_FALLBACK` for CLI `type` parity because typing needs62 focus but not cursor movement. Use `AD_POLICY_KIND_HEADED` only for explicit63 headed input semantics.6465- **Ref-action preflight.** `ad_execute_ref_action_with_policy` resolves the66 ref strictly and runs the live actionability preflight (visible, stable,67 enabled, supported action, policy, editable) before dispatching — a disabled68 or unsupported target fails before any platform call. On69 `AD_RESULT_ERR_ACTION_FAILED`, the structured check report is available as70 JSON via `ad_last_error_details()`. Details may carry element names, values,71 and window titles from the user's screen — treat them as sensitive72 diagnostics and keep them out of shared log surfaces.7374- **Action result steps.** `AdActionResult.steps` mirrors the CLI `steps`75 array for activation-chain actions. Each entry has `label` and `outcome`76 strings and is owned by the result; release it with77 `ad_free_action_result(&out)`.7879- **Tracing.** CLI `--trace` is not inherited by the C ABI; FFI hosts should80 record `AdResult`, `ad_last_error_*`, action results, and host correlation IDs81 in their own logs.8283- **No wait surface.** The CLI's `wait` command (element predicates including84 `--predicate actionable --action ...`, window/text/menu/notification waits)85 is not exposed over the C ABI. FFI hosts own their own polling loops; the86 actionability preflight inside `ad_execute_ref_action_with_policy` is the87 equivalent per-call readiness check.8889- **Text input privacy.** On macOS, focus-fallback or headed text insertion may90 briefly use the clipboard for non-ASCII text. For sensitive text, prefer91 `AD_ACTION_KIND_SET_VALUE` with `AD_POLICY_KIND_HEADLESS` when the target92 supports settable values. Do not use headless `AD_ACTION_KIND_TYPE_TEXT` as93 CLI-parity ref input; actionability rejects it before dispatch.9495- **Enum discriminants.** Every `#[repr(i32)]` enum field is validated96 at the C boundary — invalid discriminants return97 `AD_RESULT_ERR_INVALID_ARGS` instead of undefined behavior.9899- **ABI is unstable before 1.0.** The header lists the exact current100 shapes. Anything added or reordered in a later patch is a breaking101 change; pin the version of libagent_desktop_ffi you link against.102103- **`ad_get_tree` returns a raw adapter tree, not the CLI snapshot.**104 Ref IDs are always null, no skeleton/drill-down pipeline is wired105 through, and `interactive_only` / `compact` follow adapter106 semantics which may diverge slightly from the CLI's post-processed107 shape. Use `ad_find` + `ad_get` / `ad_is` for point lookups, or108 invoke the CLI if you need CLI-parity JSON snapshots.109110---111> Source: [lahfir/agent-desktop](https://github.com/lahfir/agent-desktop) — distributed by [TomeVault](https://tomevault.io).112<!-- tomevault:4.0:skill_md:2026-06-26 -->