Commit graph

4 commits

Author SHA1 Message Date
Lahfir
3f322728b4
feat!: implement Playwright-grade foundation contract
Settle the Playwright-grade reliability contract in agent-desktop-core
before the Windows/Linux adapters are built, so they inherit it instead
of redesigning it. Every command now observes, waits, verifies, and
reports honestly instead of firing blindly.

Highlights: capability-supertrait split of PlatformAdapter with
not_supported() defaults; canonical role/state vocabulary with live
`is --property visible`; display enumeration (`list-displays`) and honest
`--screen` with scale factor; truthful Automation permission; `native_id`
identity spine; window-id-first resolution; serializable `LocatorQuery`
with live `find`; default-on auto-wait before every ref action; three-way
`hit_test` occlusion gate; `scroll_into_view` in core; core accessible-name
precedence; typed `ActionStep` delivery tier; `ProcessState` and
`APP_UNRESPONSIVE`; `LaunchOptions`; baseline-diff desktop signals
(`wait --event`); typed clipboard (`Text`/`Image`/`FileUrls`); mouse
modifier chords and `mouse-wheel`. Hardened through a 35-reviewer pass with
independent validation and a green live e2e gate (109/0), plus a
head-vs-main performance comparison harness.

BREAKING CHANGE: default-on auto-wait changes the timing of every
previously-untouched ref-action call (bounded 5000 ms default; `--timeout-ms 0`
restores single-shot). `ENVELOPE_VERSION` is now `2.1` (adds the
`APP_UNRESPONSIVE` code and process state in error details). FFI ABI major
is `3` (append-only struct evolution; `wait --event` is intentionally not
exposed over FFI). The legacy string clipboard API is removed in favor of
typed content. `key-down`/`key-up` fail closed until daemon-owned held input
exists. `close-app` verifies termination and the osascript fallback path is
removed. `--text` matching is subtree containment: `find --text X --first`
returns the outermost matching container.
2026-07-20 00:21:38 -07:00
Lahfir
dbb2be639e
refactor!: over-engineering audit cleanup (#64)
* refactor!: drop vestigial version --json flag and dead code

The tool emits a JSON envelope for every command and has no plain-text
mode, so the version --json flag toggled nothing. Remove it end-to-end:
CLI flag, core VersionArgs, batch path, dispatch/main call sites, and the
snapshot test. Correct the stale skill docs (they documented a non-existent
plain-string default and wrong platform/arch keys; actual keys are
version/target/os, always inside the JSON envelope).

Also: delete dead code (AppContext, WindowContext, Response::with_app and
their re-export; core BatchArgs); inline single-caller helpers
(strip_ref_bounds_when_hidden, helpers::find_window_for_pid,
permissions::render); use libc::kill instead of a hand-declared extern in
refs_lock.

BREAKING CHANGE: the version command no longer accepts --json; it always
emits the standard JSON envelope.

* refactor: inline single-caller ffi conversions, drop dead from_c

Inline direction_from_c and snapshot_surface_to_core into their sole
callers. Remove AdImageFormat::from_c (production converts Rust->C only;
the C->Rust direction was dead, #[allow(dead_code)]) together with its
unit test and fuzz line. Drop the ErrorCode/AdResult cardinality assert:
the exhaustive error_code_to_result match plus the per-discriminant pins
already enforce the ABI invariant.

* refactor: dedup keycode table, inline single-caller macos helpers

key_to_keycode delegated the same 52-entry table that
keyboard_map::key_name_to_code already owns; delegate to it, guarding the
modifier names so a modifier passed as the primary key still resolves to
None (preserving existing behavior). Inline windows_from_records,
is_window_candidate, make_output, and the normalized_role_for_element
wrapper (which let one caller classify the same element twice); collapse
accessibility_report_state to its one-line form. Swap four local
HashSet dedup sets to FxHashSet (rustc-hash, already a dependency).

* chore: mark internal crates publish = false

These crates are workspace-internal (consumed only via path deps; CI never runs cargo publish). Marking them unpublishable prevents accidental crates.io release and makes removing unreferenced pub items unambiguously safe.

* fix: finish version --json removal in ci smoke test and help text

The version --json flag was dropped earlier but two references survived:
the npm smoke-test in ci.yml (which now errored on the unknown flag and
failed the job) and the --help text that still advertised it. Point both
at plain version, which emits the same JSON envelope. Add a batch
regression test asserting version now rejects args, and drop the
misleading {"json":true} from the stop-on-error fixture.

* test: guard ErrorCode<->AdResult bijection after counter removal

Code review flagged that dropping the cardinality counters left reverse
drift (a new AdResult error variant without a matching ErrorCode)
unguarded. The counters only weakly approximated this anyway: they
compared two hand-maintained variant arrays to each other, decoupled
from the real enums.

Replace them with a stronger, leaner guard: an exhaustive reverse match
(error_code_origin) that fails to compile if an AdResult error variant
is added without declaring its ErrorCode origin, plus a round-trip test
asserting the forward and reverse maps agree -- a consistency check the
counters never performed. Document the bijection contract on
error_code_to_result.
2026-06-24 14:31:17 -07:00
Lahfir
1291a9cdbf
refactor!: unify command execution contracts
Unify CLI and batch dispatch around the typed command path, centralize command policy and ref resolution, harden macOS action verification, split command tests from implementation, and add package/release guardrails.

BREAKING CHANGE: CLI and batch execution now share the typed command path and current command argument contracts.

BREAKING CHANGE: Ref-consuming commands use snapshot-scoped refs; deterministic consumers should pass snapshot_id and handle SNAPSHOT_NOT_FOUND.

BREAKING CHANGE: permissions and status now return PermissionReport fields for accessibility, screen_recording, and automation instead of a single boolean status.

BREAKING CHANGE: PermissionState gains NotRequired; macOS automation now reports not_required instead of unknown.

BREAKING CHANGE: right-click now separates action success from menu verification; consumers should inspect menu or menu_probe instead of assuming every right-click returns an inline menu.

BREAKING CHANGE: focus-window now confirms OS focus and returns ACTION_FAILED when focus does not settle; data.focused.is_focused is true on success.

BREAKING CHANGE: PlatformAdapter::execute_action now takes ActionRequest, and permission probing uses permission_report/request_permissions.

BREAKING CHANGE: FFI ad_execute_action now defaults to headless policy. Consumers that need focus fallback or cursor-moving behavior must call ad_execute_action_with_policy with AD_POLICY_KIND_FOCUS_FALLBACK or AD_POLICY_KIND_PHYSICAL.

BREAKING CHANGE: FFI ad_check_permissions no longer treats unknown accessibility permission as success; stub-style unknown probes return ERR_PLATFORM_NOT_SUPPORTED and macOS ambiguous unknown returns ERR_INTERNAL with last-error detail.

BREAKING CHANGE: JSON response envelopes now report version 2.0; parsers pinned to 1.0 must branch or update.

BREAKING CHANGE: focus now uses accessibility focus without cursor movement; callers that need physical focus must use explicit mouse or physical-policy paths.

BREAKING CHANGE: chain execution deadlines now return TIMEOUT instead of ACTION_FAILED when the target app does not respond before the chain deadline.
2026-05-19 18:27:08 -07:00
Lahfir
a346f242c2 feat: Phase 1 foundation — workspace scaffold, core engine, macOS adapter, 31 commands
Implements the complete agent-desktop Phase 1 specification:

- Workspace: 5-crate layout (core, macos, windows/linux stubs, binary)
- Core: AccessibilityNode, Action, ErrorCode, PlatformAdapter trait, RefMap
  with atomic writes, SnapshotEngine with depth-first ref allocation
- macOS adapter: AXUIElement tree traversal, action execution, input
  synthesis via CGEvent, screenshot via CGWindowListCreateImage, clipboard
  via pbpaste/pbcopy, window listing and app management via osascript
- 31 CLI subcommands via clap derive: snapshot, find, screenshot, get, is,
  click, double-click, right-click, type, set-value, focus, select, toggle,
  expand, collapse, scroll, press, launch, close-app, list-windows,
  list-apps, focus-window, clipboard-get/set, wait, status, permissions,
  version, batch
- Windows/Linux: not-supported stubs ready for Phase 2 implementation
- JSON output contract: {version,ok,command,data} envelope with structured
  error payloads including SCREAMING_SNAKE_CASE error codes
- Ref system: @e{N} sequential refs for interactive elements, stored at
  ~/.agent-desktop/last_refmap.json with 0o600/0o700 permissions
- CI: GitHub Actions macOS runner with dependency isolation check, clippy,
  unit tests, release build, and 15MB binary size gate
2026-02-19 10:44:38 -08:00