agent-desktop-ffi
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.
Rank
62
Safety
84
Downloads
1.5k
Updated
Oct 10, 2026
Version
1.0.7
Source
CLAWHUB
About
What it does, and when to use it.
Capability contract not published. No trust telemetry is available yet. 1.5K downloads reported by the source. Last updated 10/10/2026.
Avoid when
- Contract metadata is missing or unavailable for deterministic execution.
Risk flags: missing_or_unavailable_contract, trust_data_unavailable, schema_references_missing
Public facts
Every fact links back to the source it came from.
- Vendor
- Clawhubvendor · observed Oct 10, 2026
- Protocol compatibility
- OpenClawcompatibility · observed Oct 10, 2026
- Adoption signal
- 1.5K downloadsadoption · observed Oct 10, 2026
- Latest release
- 1.0.7release · observed Aug 28, 2026
- Handshake status
- UNKNOWNsecurity
Install and run
Setup complexity: low.
clawhub skill install s1793830nng35p6y70jxm4ybt984y8ka:agent-desktop-ffi- Install using `clawhub skill install s1793830nng35p6y70jxm4ybt984y8ka:agent-desktop-ffi` in an isolated environment before connecting it to live workloads.
- No published capability contract is available yet, so validate auth and request/response behavior manually.
- Review the upstream CLAWHUB listing at https://clawhub.ai/lahfir/agent-desktop-ffi before using production credentials.
Contract: missing
curl -s "https://www.xpersona.co/api/v1/agents/clawhub-lahfir-agent-desktop-ffi/snapshot"
Documentation
CLAWHUB
144,194 characters of source documentation, loaded on request.
Extracted files
5 files captured from the source.
SKILL.md
---
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:
```sh
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](references/ownership.md) — who allocates / who frees,
for every `*mut T` the FFI hands back to the caller.
- [error-handling.md](references/error-handling.md) — errno-style
last-error contract, enum validation, panic boundary.
- [threading.md](references/threading.md) — host-thread contract,
cross-process mutation serialization, AXIsProcessTrusted inheritance,
and adapter-bound native handles.
- [build-and-link.md](references/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_RES_meta.json
{
"ownerId": "kn7a8wtv8q9jjhpxh4w601zsb5827bnx",
"slug": "agent-desktop-ffi",
"version": "1.0.7",
"publishedAt": 1787877342860
}references/build-and-link.md
# Build and link
## Building the cdylib
```sh
cargo build --profile release-ffi -p agent-desktop-ffi
```
Output:
- macOS: `target/release-ffi/libagent_desktop_ffi.dylib`
- Linux: `target/release-ffi/libagent_desktop_ffi.so`
- Windows: `target/release-ffi/agent_desktop_ffi.dll`
The generated header is at `crates/ffi/include/agent_desktop.h`. CI
validates that the committed header matches what `cargo build`
regenerates — if you change a type in `crates/ffi/src/`, rebuild
locally and commit the updated header.
`--profile release-ffi` keeps `panic = "unwind"`, which is required for
the `catch_unwind` traps inside every `extern "C"` entrypoint. The default
`release` profile uses `panic = "abort"` (for CLI binary-size reasons) and
silently defeats those traps.
## Prebuilt archives
Every GitHub release ships prebuilt archives for:
- macOS arm64 and x86_64
- Linux x64 and arm64 (glibc)
- Windows x64 MSVC
Each archive contains the dylib/so/dll, `include/agent_desktop.h`, and
`LICENSE`. Integrity: compare against `checksums.txt` in the release
assets. Supply-chain verification: each release is signed via Sigstore
attestation — verify with `cosign verify-blob` before deploying.
## ABI handshake (do this first)
After `dlopen` / `LoadLibrary`, compare the dylib major to the header you
compiled against before calling anything else:
```c
AdResult rc = ad_init(AD_ABI_VERSION_MAJOR);
if (rc != AD_RESULT_OK) {
// header and dylib have incompatible major versions
fprintf(stderr, "ABI mismatch: %s\n", ad_last_error_message());
return -1;
}
```
Alternatively, read the raw dylib major and compare yourself:
```c
uint32_t dylib_major = ad_abi_version();
if (dylib_major != AD_ABI_VERSION_MAJOR) {
fprintf(stderr, "ABI major: header=%u dylib=%u\n",
AD_ABI_VERSION_MAJOR, dylib_major);
abort();
}
```
`ad_init` returns `AD_RESULT_ERR_INVALID_ARGS` on mismatch (with a
diagnostic in `ad_last_error_message`). A mismatch means the header you
compiled against and the loaded dylib are incompatible — do not call
anything further.
## Struct size validation
Languages whose struct layout may diverge from C (Python ctypes, Go cgo,
JNI, etc.) must validate every size-pinned struct before passing it to
the library. The FFI exposes three validation layers:
1. **Header macros**: `AD_ACTION_SIZE`, `AD_WAIT_ARGS_SIZE`,
`AD_REF_ENTRY_SIZE`, `AD_DRAG_PARAMS_SIZE`, `AD_ACTION_RESULT_SIZE`,
`AD_ACTION_STEP_SIZE`, `AD_ELEMENT_STATE_SIZE`.
2. **Runtime getters**: `ad_action_size()`, `ad_wait_args_size()`,
`ad_ref_entry_size()`, `ad_drag_params_size()`, `ad_action_result_size()`,
`ad_action_step_size()`, `ad_element_state_size()` — each returns the
same value the macro encodes, compiled from the Rust side.
3. **C11 static asserts** in the header (`#ifndef AGENT_DESKTOP_ABI_ASSERTS`)
catch mismatches at C compile time.
Compare your binding's `sizeof` equivalent against the getter at load
time, before building or passing any of thesreferences/error-handling.md
# Error handling
The FFI uses an errno-style last-error pattern. Every `AdResult`-returning
function returns `AD_RESULT_OK` (= 0) on success or a negative error
code on failure. When a failure occurs, thread-local last-error state is
populated; read it with the `ad_last_error_*` accessors.
## Minimal pattern
```c
AdResult rc = ad_launch_app(adapter, "com.apple.finder", 5000, &win);
if (rc != AD_RESULT_OK) {
const char *msg = ad_last_error_message();
const char *sug = ad_last_error_suggestion(); // may be NULL
fprintf(stderr, "launch_app failed (%d): %s\n", (int)rc, msg ? msg : "(no message)");
if (sug) fprintf(stderr, " suggestion: %s\n", sug);
// no need to release the struct — out-param was zero-initialized
return -1;
}
// ...use win...
ad_release_window_fields(&win);
```
## Last-error accessors
Four accessors share the same per-thread lifetime contract:
| Accessor | Returns |
|---------------------------------|----------------------------------------------------------------|
| `ad_last_error_code()` | The `AdResult` code of the last failure, or `AD_RESULT_OK` |
| `ad_last_error_message()` | Human-readable description, or null |
| `ad_last_error_suggestion()` | Recovery hint, or null |
| `ad_last_error_platform_detail()` | OS-specific diagnostic (AX codes, HRESULTs, AT-SPI), or null |
| `ad_last_error_details()` | Structured JSON details, or null — **sensitive** (see below) |
`ad_last_error_details()` returns a JSON string with structured context:
the actionability check report on `ACTION_FAILED`, candidate element
summaries on `AMBIGUOUS_TARGET`, the last observed state on a `wait`
`TIMEOUT`, etc. The details may contain element names, values, and window
titles from the user's screen. Treat as sensitive diagnostics and avoid
routing to shared log surfaces.
## Lifetime contract
The pointer returned by any `ad_last_error_*` accessor remains valid
across any number of subsequent **successful** FFI calls. Only the next
**failing** call rotates the slot.
Consequence: you can cache the pointer right after a failure and keep
reading it until the next failure — equivalent to POSIX `errno` /
`strerror`.
```c
AdResult rc = ad_some_call(...);
const char *msg = ad_last_error_message(); // snapshot
ad_check_permissions(adapter); // success
ad_check_permissions(adapter); // success
printf("%s\n", msg); // still valid
```
Failure-path calls rotate: if a subsequent call fails, the prior
pointer may dangle. Read it before the next potentially-failing call.
Last-error is per-thread (thread-local storage) — Thread A's failure
does not affect Thread B's slot.
`ad_check_permissions` does not treat `Unknown` as success. Stub adapters
that cannot answer permission probes return
`AD_RESULT_ERR_PLATFreferences/ownership.md
# Pointer ownership Every `*mut T` / `*const T` returned by the FFI comes with a matching free function. Always call it; the allocator the FFI uses is Rust's `Box::from_raw` / `CString::from_raw`, which cannot be freed with C's `free()`. ## Allocation / release table ### Command-backed JSON strings These entrypoints write an owned, NUL-terminated JSON envelope into `*out`; free with `ad_free_string`. See error-handling.md for the dual-failure mode (command-level errors write `ok:false` JSON into `*out`; infrastructure errors leave `*out` null with no allocation). | Allocates | Frees with | |-----------------------------------------------------------------------------------|-------------------------| | `ad_version(&out)` | `ad_free_string(out)` | | `ad_status(adapter, &out)` | `ad_free_string(out)` | | `ad_snapshot(adapter, app, surface, max_depth, interactive_only, compact, &out)` | `ad_free_string(out)` | | `ad_execute_by_ref(adapter, ref_id, snapshot_id, action, policy, &out)` | `ad_free_string(out)` | | `ad_wait(adapter, args, &out)` | `ad_free_string(out)` | ### Adapter lifecycle | Allocates | Frees with | |------------------------------------------------------|-----------------------------------------| | `ad_adapter_create()` | `ad_adapter_destroy(adapter)` | | `ad_adapter_create_with_session(session)` | `ad_adapter_destroy(adapter)` | ### Opaque list handles | Allocates | Frees with | |--------------------------------------------------------------|-----------------------------------------| | `ad_list_apps(adapter, &list)` | `ad_app_list_free(list)` | | `ad_list_displays(adapter, &list)` | `ad_display_list_free(list)` | | `ad_list_windows(adapter, app, focused, &list)` | `ad_window_list_free(list)` | | `ad_list_windows_exact(adapter, app, focused, &list)` | `ad_exact_window_list_free(list)` | | `ad_list_surfaces(adapter, pid, &list)` | `ad_surface_list_free(list)` | | `ad_list_surfaces_exact(adapter, pid, &list)` | `ad_exact_surface_list_free(list)` | | `ad_list_notifications(adapter, filter, &list)` | `ad_notification_list_free(list)` | | `ad_dismiss_all_notifications(adapter, f, &ok, &fail)` | `ad_notification_list_free` on each, or `ad_dismiss_all_notifications_free(ok, fail)` | ### App / window lifecycle | Allocates
AionUi
Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!
activepieces
AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents
cherry-studio
AI productivity studio with smart chat, autonomous agents, and 300+ assistants.
CopilotKit
The Frontend for Agents & Generative UI. React + Angular
Machine-readable data
The same record, as JSON, for agents and crawlers.
{
"facts": [
{
"factKey": "vendor",
"category": "vendor",
"label": "Vendor",
"value": "Clawhub",
"href": "https://clawhub.ai/lahfir/skills/agent-desktop-ffi",
"sourceUrl": "https://clawhub.ai/lahfir/skills/agent-desktop-ffi",
"sourceType": "profile",
"confidence": "medium",
"observedAt": "2026-10-10T10:38:55.243Z",
"isPublic": true
},
{
"factKey": "protocols",
"category": "compatibility",
"label": "Protocol compatibility",
"value": "OpenClaw",
"href": "https://www.xpersona.co/api/v1/agents/clawhub-lahfir-agent-desktop-ffi/contract",
"sourceUrl": "https://www.xpersona.co/api/v1/agents/clawhub-lahfir-agent-desktop-ffi/contract",
"sourceType": "contract",
"confidence": "medium",
"observedAt": "2026-10-10T10:38:55.243Z",
"isPublic": true
},
{
"factKey": "traction",
"category": "adoption",
"label": "Adoption signal",
"value": "1.5K downloads",
"href": "https://clawhub.ai/lahfir/agent-desktop-ffi",
"sourceUrl": "https://clawhub.ai/lahfir/agent-desktop-ffi",
"sourceType": "profile",
"confidence": "medium",
"observedAt": "2026-10-10T10:38:55.243Z",
"isPublic": true
},
{
"factKey": "latest_release",
"category": "release",
"label": "Latest release",
"value": "1.0.7",
"href": "https://clawhub.ai/lahfir/agent-desktop-ffi",
"sourceUrl": "https://clawhub.ai/lahfir/agent-desktop-ffi",
"sourceType": "release",
"confidence": "medium",
"observedAt": "2026-08-28T00:35:42.860Z",
"isPublic": true
},
{
"factKey": "handshake_status",
"category": "security",
"label": "Handshake status",
"value": "UNKNOWN",
"href": "https://www.xpersona.co/api/v1/agents/clawhub-lahfir-agent-desktop-ffi/trust",
"sourceUrl": "https://www.xpersona.co/api/v1/agents/clawhub-lahfir-agent-desktop-ffi/trust",
"sourceType": "trust",
"confidence": "medium",
"observedAt": null,
"isPublic": true
}
],
"events": [
{
"eventType": "release",
"title": "Release 1.0.7",
"description": "Release v0.8.4",
"href": "https://clawhub.ai/lahfir/agent-desktop-ffi",
"sourceUrl": "https://clawhub.ai/lahfir/agent-desktop-ffi",
"sourceType": "release",
"confidence": "medium",
"observedAt": "2026-08-28T00:35:42.860Z",
"isPublic": true
}
]
}Record generated Oct 10, 2026.
