| name | agent-desktop-ffi |
| version | 0.4.1 |
| tags | ffi, c-bindings, cdylib, python, swift, node, go, rust-ffi |
| requirements | ["agent-desktop-ffi"] |
| description | 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. The canonical observe-act workflow is: ad_init → ad_adapter_create[_with_session] → ad_snapshot → parse @e refs → ad_execute_by_ref → ad_free_string → ad_adapter_destroy.
|
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.
A Python ctypes smoke harness lives at tests/ffi-python/smoke.py and
serves as a worked end-to-end example covering the ABI handshake, struct
size validation, ad_version, and the snapshot pipeline leg. See
tests/ffi-python/README.md for usage.
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 — host-thread contract,
cross-process mutation serialization, AXIsProcessTrusted inheritance,
and adapter-bound native handles.
- build-and-link.md — ABI handshake,
struct size validation, minimal C and Python examples, observe-act
workflow, and prebuilt archive locations.
Observe-act workflow (canonical path)
ad_init(AD_ABI_VERSION_MAJOR) // verify header ↔ dylib match
adapter = ad_adapter_create_with_session("s1") // or ad_adapter_create()
rc = ad_snapshot(adapter, "Finder", 0, 10, false, false, &json_out)
// parse json_out: locate snapshot-qualified refs in data.tree
ad_free_string(json_out)
// build action:
AdAction act = {0}; act.kind = AD_ACTION_KIND_CLICK;
rc = ad_execute_by_ref(adapter, "@s8f3k2p9:e5", NULL, &act, 0, &result_out)
ad_free_string(result_out)
ad_adapter_destroy(adapter)
ad_snapshot returns a {version, ok, command, data} JSON envelope
identical to the CLI output. The data.tree field contains snapshot-qualified
ref IDs for interactive elements. Pass a qualified ref, or a legacy bare ref
plus its explicit snapshot_id, to ad_execute_by_ref to drive the pipeline
(RefStore load → strict resolution → actionability preflight → dispatch).
Core constraints
-
ABI handshake. Call ad_init(AD_ABI_VERSION_MAJOR) once after loading the
dylib. A mismatch between the compiled-in constant and the loaded dylib returns
AD_RESULT_ERR_INVALID_ARGS — abort rather than proceed. You can also read the
raw dylib major via ad_abi_version() for diagnostic display. New ad_* symbols
and new error codes are additive (no bump required); removed or layout-changed
symbols increment the major.
-
Session adapters. ad_adapter_create_with_session("session-id") associates
the adapter with a session namespace for refmap persistence — the same as CLI
--session <id>. A null snapshot_id is valid only for a qualified ref;
legacy bare @eN refs require an explicit snapshot ID. Session IDs: 1–64
chars, ASCII alphanumeric / - / _.
Invalid IDs return null (check ad_last_error_*).
-
Structured session trace (no ABI change). File-based JSONL tracing activates
only when the session has a manifest with trace: on from session start
(CLI) or equivalent on-disk setup. ad_adapter_create_with_session alone does
not create trace files. When tracing is active, command_context()-backed
commands append to one segment per OS process under
~/.agent-desktop/sessions/<id>/trace/<pid>-<procTs>.jsonl. A long-lived host
reuses the same segment filename for all calls in that process. For unstructured
diagnostics regardless of session manifest, use ad_set_log_callback (below).
-
Threading and mutation leases. Adapter entrypoints may be called from any
host thread. Native handles remain bound to their creating adapter and thread.
Desktop mutations acquire the same canonical cross-process interaction lease
as the CLI; reads carry finite deadlines without taking the mutation lock. See
threading.md for the Apple documentation basis and
the read/read, read/mutation, and mutation/mutation ordering matrix.
-
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 profile. CI
enforces this.