mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
feat/flow-script-drop-logs
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4d34a689b5 |
feat(tool-server): add ARGENT_SIMULATOR_NO_WINDOW to force headless iOS boots (#483)
## What Adds `ARGENT_SIMULATOR_NO_WINDOW` — the iOS analog of the existing Android `ARGENT_EMULATOR_NO_WINDOW` env var — to force local iOS simulator boots to run headless. ## Why `ARGENT_EMULATOR_NO_WINDOW=1` already makes Android emulators boot headless host-wide (appends `-no-window`). iOS had no env-var equivalent: it could only boot headless via the per-call `headless: true` parameter on the `boot-device` tool, which is awkward for CI/containers and Argent Lens hosts that stream every device through `simulator-server` and never want a window. Note: `simctl boot` is already headless at the core — the only thing that pops a window is the `open -a Simulator.app` GUI attach at the end of `bootIos`. This env var gates exactly that step. ## Changes - `boot-device.ts` - New `iosHeadlessFromEnv()` helper next to Android's `selectExtraEmulatorArgs()`, with identical truthy parsing (`1`/`true`/`yes`, case-insensitive). - `bootIos` GUI-attach gate changed from `if (!headless)` → `if (!headless && !iosHeadlessFromEnv())`. - Updated the `headless` param docs to mention the env var. - `boot-device.test.ts` — new test asserting `Simulator.app` is not opened when `ARGENT_SIMULATOR_NO_WINDOW` is set (env saved/restored around the test). ## Scope Applies to the local iOS path (`bootIos`). The remote path (`bootIosRemote` via `sim-remote`) has no local `Simulator.app` step, so it's unaffected. ## Testing - `npx tsc --noEmit` — clean - `npx vitest run test/boot-device.test.ts` — 23/23 pass (incl. the new test) |
||
|
|
a92c859d07 |
feat(argent-lens): argent lens — bind a claude session to the preview window (#416)
## What Adds `argent lens` — open Argent Lens bound to a fresh `claude` session. ## Why The Lens preview window and the agent communicate through a shared, process-wide tool-server that carries **no per-agent identity** (only a coarse telemetry header). So when the user requests changes in the window, there's no reliable way to route them to the *specific* agent that opened it. `argent lens` sidesteps the problem by inverting ownership: the human launches Lens, and Lens spawns (and therefore owns) exactly one `claude` terminal. The binding is 1:1 by construction — no identity plumbing required. ## How it works 1. `argent lens` ensures a tool-server and **begins a CLI Lens session**, which opens the preview window up front (no `await_user_selection` needed) and keeps it open across rounds. 2. It spawns a tracked `claude` terminal in the current directory — a TypeScript port of the macOS applet's `osascript` spawn/track mechanism — seeded to propose variants without blocking. 3. A watcher polls `GET /preview/outcome`; on each submitted round it types a one-line feedback summary into that terminal, queuing it to the agent as its next prompt. Requesting changes therefore "talks back" to the bound agent instead of closing the window. macOS only (drives Terminal/iTerm via `osascript`); off-darwin it exits with a clear message. ## Changes - **CLI** `lens-terminal.ts` — `osascript` spawn/track + a new write-into-running-session path (the "outputting" channel; injects input into the running TUI, which a tty write cannot do). - **CLI** `lens.ts` — the command + feedback formatter. - **tool-server** — a `cliSession` flag in the variant store (opens the window, suppresses auto-close on submit); `POST /preview/cli-session` and `GET /preview/outcome` routes. - **UI** — relabels the submit action to "Request changes" while a CLI session is active. Behind the existing `argent-lens` feature flag. ## Verification - Build + typecheck across the workspace; ESLint (`--max-warnings 0`) + Prettier clean. - 34 new unit tests; full `@argent/cli` suite **118 passing**, tool-server variant/preview suites **46 passing**. - **E2E (observed on this machine):** - **tool-server HTTP** — `cli-session` toggles `cliSession` true/false; submitted feedback round-trips through `/preview/outcome`. - **terminal injection** — spawned a window running `cat`, confirmed `writeToSession` lands text in the running program's **stdin** (both iTerm and Terminal), with embedded newlines flattened. - **full relay** — the real `watchAndRelay` loop against a real tool-server typed two distinct submission rounds' feedback into a stand-in agent terminal (dedup on `completedAt` verified). - `argent lens --help` and the feature-flag gate (exit 1 with guidance, no spawn). Note: a real end-to-end with a live `claude` agent was not run (it would drive a paid interactive session); the agent-facing channel is proven by injecting into a stand-in process's stdin via the exact same code path. --------- Co-authored-by: Filip131311 <contact@filipkaminski.com> |
||
|
|
b8afbed183 |
feat(tv): Apple TV (tvOS) and Android TV support (#374)
Adds end-to-end support for driving **Apple TV (tvOS)** and **Android TV (leanback)** targets through argent, using a focus-driven interaction model (TVs have no touchscreen — interaction is moving a focus highlight with the remote). This PR was reworked after review to **reuse the existing cross-platform tool surface and the `tv-remote` tool from the Vega/Fire TV work (#366)** rather than ship a separate family of `tv-*` tools. ## What this delivers TV targets are driven by the **same cross-platform tools as every other platform**, routed through the existing `dispatchByPlatform` fork mechanism — there are no dedicated `tv-describe`/`tv-navigate`/`tv-type`/`tv-set-focus` tools: - **`describe`** — on a `runtimeKind: "tv"` target, returns the focus-driven view (focused + focusable elements) instead of a tap tree. On Android TV it auto-falls-back to the full `uiautomator` tree when the RN focus engine exposes nothing. - **`tv-remote`** — the cross-platform remote/D-pad tool from #366, now extended with `ios` (Apple TV HID daemon) and `android` (Android TV adb keyevents) branches alongside the existing `vega` branch. Single button, a `repeat`, or a whole path (`["up","right","select"]`) in one call. - **`keyboard`** — types into the focused field on a TV target (named keys are rejected there — they're navigation, which belongs to `tv-remote`). - **`button`** stays hardware-only (phones/tablets); it is not a TV tool. **Apple TV** runs two native daemons (in-sim AX service + host-side HID daemon), shipped via the `argent-private` submodule. **Android TV** reuses the same tool surface, adb-backed (`input keyevent`, `uiautomator dump`, `input text`). ## Remote vocabulary parity `tv-remote` exposes the full 16-button vocabulary (`up`/`down`/`left`/`right`/`select`/`back`/`home`/`menu`/`playPause`/`rewind`/`fastForward`/`next`/`previous`/`volumeUp`/`volumeDown`/`mute`): | | Apple TV | Android TV | Vega | |---|---|---|---| | D-pad / select / back / menu / home / playPause | ✅ | ✅ | ✅ | | media-transport + volume/mute | ❌ rejected | ✅ | ✅ | Media-transport/volume keys **genuinely work on Android TV** (real keycodes — verified live: `volumeUp` moved `STREAM_MUSIC`, `mute` toggled per `dumpsys audio`). On the **Apple TV simulator** they are **rejected with a clear error**: on-device testing confirmed the tvOS sim's HID stack silently drops Consumer-Control events, so returning success would be a lie. ## Key design points - **tvOS UDIDs are UUID-shaped and indistinguishable from iOS by shape.** Handled with `runtimeKind` detection (tvOS runtime string for Apple TV; `pm list features` leanback/television for Android TV — *not* `ro.build.characteristics`, which lies on TV emulators). - Converted launch-app / restart-app / screenshot / screenshot-diff / run-sequence from **eager** to **lazy** service resolution, so a tvOS target never spins up (and hangs on) the iOS-only simulator-server / native-devtools blueprints. - Native injection selects the platform-matched (TVOSSIMULATOR) dylib slice; daemons recycle across sim reboots and app relaunches so injection and focus survive. - SKILL.md footprint minimized per review: the two TV skills were collapsed into a single lean **`argent-tv-interact`** (~40 lines). ## Verification - `npm run build`, ESLint, Prettier, and the full tool-server suite (**1565 tests**) all pass; CI green (incl. the Apple TV / Android TV / Vega e2e jobs). - Core flows verified live against real apps on both an Apple TV 4K simulator and a Google ATV emulator. ## Dependency / merge order Pins the `argent-private` submodule to the tip of its `feat/tv-support` branch (software-mansion/argent-private#20). **Merge that PR to `main` first, then bump the submodule pointer here to the merged SHA before merging this PR.** 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
23cd32073a |
fix(native-devtools): re-apply DYLD env after out-of-band sim reboot (#346)
## Summary After an iOS simulator loses its launchd `DYLD_INSERT_LIBRARIES` (any reboot — out-of-band *or* an argent `boot-device`), `native-devtools-status` keeps reporting `envSetup: true` / `nextLaunchWillBeInjected: true` while the env is actually **empty**. Freshly launched apps aren't injected and native feature tools (`native-describe-screen`, `native-find-views`, …) fail with `restart_required` — and `restart-app` couldn't break out of it. ## Root cause `envSetup` is a **write-once latch**. `ensureEnvReady()` short-circuits once it flips `true`, and nothing resets it, so the env is never re-applied after a reboot clears it. Two paths relied on that latched function and so never recovered: 1. `requiresAppRestart()` — written to recover from the reboot case, but called the latched `ensureEnvReady()`. 2. `boot-device` — primes the native-devtools service after (re)booting, but the simulator watcher usually initialized it earlier, so `resolveService` returns the cached/latched instance and the wiped env is never re-applied. A clean argent `boot-device` + `launch-app` therefore produced an uninjected app and a spurious `restart_required`. ## Fix - Split a latch-free `runEnsureEnv()` out of `ensureEnvReady()` and add `reverifyEnv()` — re-applies the env even when the latch says it's set (still honouring the give-up guard). `ensureEnv` is already idempotent, so it's a cheap no-op when correct. - `requiresAppRestart()` → uses `reverifyEnv()` (fixes the native feature tools' recovery; precheck routes through it). - `native-devtools-status` → calls `reverifyEnv()` when the app isn't connected, so its flags reflect reality and a later launch is injected. - `boot-device` → forces `reverifyEnv()` after the (re)boot, so a clean argent boot injects on first launch with no restart. The hot path (`ensureEnvReady`, app already connected) is unchanged — no added latency on normal calls; the extra simctl round-trips only fire when there's evidence the env is stale. ## Verified end-to-end on a real iOS 26.3 sim (patched tool-server) - **Clean argent boot → launch → just works:** prime service, `boot-device force`, `launch-app` → `connected:true`, `requiresRestart:false`, `native-describe-screen` returns the full tree. `DYLD_INSERT_LIBRARIES` is present immediately after `boot-device` returns. ✅ - **Out-of-band reboot → `native-devtools-status`:** status call self-heals the env; next launch is injected. ✅ - **Out-of-band reboot → launch → native tool → `restart-app`:** the previously dead-end recovery now works. ✅ - **Before the fix (same hardware):** status claimed `envSetup:true` with the env empty; launches uninjected; `restart-app` and `boot-device force` did not recover. ## Tests - New regression tests: `native-devtools-status` re-applies when disconnected / does not when connected; `boot-device` forces a re-apply after boot. - Full `packages/tool-server` suite green (1155), typecheck + prettier clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Radoniusz Cloudiusz <radoniusz@radoniusz.local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9b027096d7 |
feat: Electron support (#247)
## Summary Adds Electron (Chromium + CDP) as a third device platform alongside iOS and Android. Argent can boot an Electron app, list it next to simulators/emulators, drive it with the same tool surface where it makes sense — taps, swipes, keyboard, screenshots, describe, navigation — and debug it via the four ported `debugger-*` tools. Mirrors the sim-server architecture in TypeScript: a per-device server with HTTP API, MJPEG screencast, refcounted CDP screencast, FPS tracker, clipboard, and a WebSocket command bus. ## What works on Electron | group | tools | status | |---|---|---| | Lifecycle | `boot-device` (with `electronAppPath`), `launch-app`, `open-url` | ✅ works | | Vision | `screenshot`, `describe` (DOM walker w/ shadow-DOM + same-origin iframes) | ✅ works | | Input | `gesture-tap`, `gesture-swipe`, `keyboard` (text + named keys) | ✅ works | | Sequencing | `run-sequence` (per-step capability check) | ✅ works | | **JS debugger** | `debugger-connect`, `debugger-status`, `debugger-evaluate`, `debugger-log-registry` | ✅ **direct CDP** | | Discovery | `list-devices` (Electron entries shown alongside sims/emulators) | ✅ works | | Cleanup | `stop-simulator-server`, `stop-all-simulator-servers`, `stop-metro` | ✅ works | | Misc | `update-argent`, `dismiss-update`, `gather-workspace-data`, all `flow-*` recording tools | ✅ works | ### Debugger port — how it routes Dispatch lives in `tools/debugger/debugger-service-ref.ts`: an Electron device id (`electron-cdp-<port>`) routes to a new `ElectronJsRuntimeDebugger` blueprint that wraps the page CDP session already opened by `boot-device`. Everything else stays on the Metro-driven `JsRuntimeDebugger` blueprint. The new blueprint exposes the same `JsRuntimeDebuggerApi` shape so tool code is unchanged. `Runtime.consoleAPICalled` events feed into the existing `LogFileWriter` + cluster pipeline — `debugger-log-registry` returns the same shape on Electron as it does on Metro. ## What is cleanly rejected with `Tool 'X' is not supported on electron app` HTTP capability gate declines these up-front (HTTP 400): | group | tools | |---|---| | Mobile gestures | `gesture-pinch`, `gesture-rotate`, `gesture-custom` | | Mobile controls | `button`, `rotate` | | App lifecycle | `restart-app`, `reinstall-app` | | iOS-native | `paste`, all `native-*`, `native-profiler-*` | | RN-only debugger | `debugger-component-tree`, `debugger-reload-metro`, `debugger-inspect-element` | | RN-only network | `view-network-logs`, `view-network-request-details` | | RN-only profiler | all `react-profiler-*` (incl. `component-source`), all `profiler-*` query / combined tools | ## Verification history Three commits resolved findings from two parallel verification swarms: - `507b295` — swarm v1 fixes: early-disconnect race in new blueprint, NaN timestamp crash, disconnect listener leak, missing test coverage for 13 of 15 lockouts, missing disconnect→terminated propagation test, stale skill + tool descriptions. - `dd0f185` — pre-existing crash: `spawn()` in boot-electron lacked an `'error'` event handler; ENOENT escalated to `uncaughtException`. + 4 regression test cases. - `95161ab` — swarm v2 fixes: orphan promise rejection when pid-less spawn fires deferred `'error'`, misleading rationale comment, stale cross-skill references calling this "Metro debugging only". Branch was also cherry-picked onto v0.8.0 (`e1327b4`) to pick up #245. ## Follow-ups left for separate PRs - **CPU profiling on Electron** — Chromium's `Profiler.start`/`Profiler.stop` work fine; only the V8 sample format differs from Hermes'. A normalizer in the query layer would unlock `react-profiler-cpu-summary` + `profiler-cpu-query` for arbitrary Electron renderers (without the React-DevTools commit half). - **Source-mapped element inspection on Electron** — `debugger-inspect-element` could be rebuilt on top of `DOM.getNodeForLocation` + CSS source maps, but it'd be a from-scratch implementation rather than a port. ## New sim-server-equivalent HTTP surface Mounted at `/electron-server/:deviceId/*` — not advertised to MCP, consumed by preview UIs and integration tests: - `GET /viewport`, `GET /stream.mjpeg` (multipart-JPEG screencast) - `POST /api/screenshot`, `/api/clipboard/text`, `/api/fps`, `/api/navigate`, `/api/reload`, `/api/history/back`, `/api/history/forward` - `WS /ws` (input commands + event bus) Sharp is loaded as an optional dependency — screenshots still work without it (no rotate/resize, just raw PNG). ## Test plan - [x] 786 vitest cases pass; new coverage for the dispatch helper, blueprint factory edge cases (early disconnect, NaN timestamp, dispose symmetry), 15-tool table-driven lockout assertion, and 5 boot-electron spawn-error scenarios. - [x] Build + prettier clean. - [x] E2E swept the original 27 surfaces (11 MCP tools, 8 HTTP endpoints, MJPEG, WebSocket, 6 capability rejections, session cleanup). - [x] E2E swept the debugger surface against a live Electron app — connect / status / evaluate (`1+1`, JSON-stringified globals, `document.title`) / log-registry (clusters + log file). - [x] E2E swept the 17 lockouts — every one returns HTTP 400 with `Tool 'X' is not supported on electron app`. - [x] Two rounds of parallel verification swarm; every real finding addressed. - [ ] Reviewer drives an Electron app end-to-end via MCP and confirms describe / tap / swipe / keyboard / debugger-evaluate land correctly. --------- Co-authored-by: Filip131311 <contact@filipkaminski.com> |
||
|
|
80661fad8f |
feat: Linux support (#249)
This PR makes Linux work.
# AI Summary
<details>
Brings Argent up on Linux as a host: per-platform npm bundle, renderer
selection that actually works on hybrid-GPU / Wayland / containerized
Linux, hot-boot guarding that survives the SwiftShader composite gap, a
real Linux preflight, and a Wayland-headless E2E job in CI. macOS stays
on
the path it was on before (no `-gpu` flag was passed; AVD config
decided);
nothing about the iOS Simulator behavior changes.
The diff is broader than "ship the Linux artifact" — calling it out
below
because several pieces affect macOS too and reviewers should be primed
for
that rather than skim past expecting "just packaging."
### What's actually in here
**npm packaging (the original framing — still load-bearing)**
| File | Change |
|---|---|
| `packages/argent/scripts/argent-simulator-server.cjs` (new) | Node
dispatcher exposed as the `argent-simulator-server` bin entry. Joins
`process.platform` into the bin path and execs. Forwards
SIGTERM/SIGINT/SIGHUP parent→child so non-TTY supervisors can't orphan
the binary. |
| `packages/argent/scripts/bundle-tools.cjs` | Copies per-platform
binaries from `packages/native-devtools-ios/bin/<platform>/` into
`packages/argent/bin/<platform>/`. darwin required, linux best-effort. |
| `packages/argent/package.json` | `bin.argent-simulator-server` points
at the dispatcher. |
| `packages/native-devtools-ios/src/index.ts` | Resolver joins
`process.platform` into the simulator-server bin path. iOS-only dylibs
and `ax-service` throw `"requires a macOS host"` on Linux callers so
they get the root cause, not "file not found". |
| `scripts/download-simulator-server.sh` | Downloads
`simulator-server-argent-{macos,linux}` into platform-keyed subdirs.
**Purges each platform dir before its download attempt** so a failed
download can't leave stale binaries in place from a prior run. |
| `scripts/dev.cjs` | Local dev copies the current host's binary into
`bin/<process.platform>/` to match the resolver layout. |
| `.github/workflows/publish{,-next}.yml` | Replaces single `test -x
darwin/...` with a per-platform inventory loop. darwin always required;
Linux gated by `ARGENT_REQUIRE_LINUX_BINARY=1` (flip once radon#118
lands). |
**Linux runtime — emulator boot path**
| File | Change |
|---|---|
| `packages/tool-server/src/tools/devices/boot-device.ts` |
`selectGpuMode()`: Linux defaults to `-gpu swiftshader` (`auto` lands on
lavapipe with ~10× cold-boot slowdown; `-gpu host` produces silent
black-window failures on Optimus / NVIDIA+libglvnd / Wayland-on-hybrid /
containers). macOS keeps `auto` (ANGLE→Metal).
`ARGENT_EMULATOR_GPU_MODE` overrides, **validated against `emulator
-help-gpu`'s whitelist** so a typoed value fails fast instead of burning
the whole boot budget. `ARGENT_EMULATOR_NO_WINDOW=1` appends
`-no-window` for headless contexts (CI / containers / Wayland sessions
with no Qt wayland plugin). |
| `packages/tool-server/src/tools/devices/boot-device.ts` (cont.) |
**Cold-boot:** `awaitFirstRealFrame` polls SwiftShader's lockscreen
composite (PNG byte-count via `screencap -p`); blocks `booted:true`
until a real frame shows up. Without this, callers chaining `boot-device
→ screenshot` get an all-black PNG because `sys.boot_completed=1` fires
5–60 s before the first composite. |
| `packages/tool-server/src/tools/devices/boot-device.ts` (cont.) |
**Hot-boot:** `assertScreencapAlive` polls within an 8 s budget instead
of single-probing. The earlier single-probe caught the sticky-blank
state but also killed the emulator on every transient snapshot-restore
blank, forcing "every boot is cold" cycles. Sticky vs transient is
discriminated by whether the budget exhausts. |
| `packages/tool-server/src/utils/linux-preflight.ts` (new) |
`linuxBootDiagnostics()` checks `/dev/kvm` access. `diagnoseAvdSizing()`
warns when a target AVD's `hw.ramSize`/`vm.heapSize` is below the floor
an aggressive RN/Expo app needs (2G/228M avdmanager defaults wedge under
load). |
| `packages/tool-server/src/utils/android-binary.ts` |
`defaultAndroidRoots()` probes typical SDK install locations when
`ANDROID_HOME`/`ANDROID_SDK_ROOT` are unset — **cross-platform**,
benefits macOS launches from GUI-launched MCP hosts (Claude Code Desktop
etc.) where shell rc env doesn't propagate. |
**Cross-platform fixes that landed alongside**
| File | Change |
|---|---|
| `packages/argent-installer/src/utils.ts` | `getLatestVersion()` now
redirects `npm view`'s stderr to `ignore` so `EBADDEVENGINES` warnings
from a consumer project's `package.json` (e.g. Bluesky's pnpm pin) don't
leak to the user's terminal. **Cross-platform fix**, not Linux-specific.
|
**CI**
| File | Change |
|---|---|
| `.github/workflows/wayland-e2e.yml` (new) | Boots an AVD through the
tool-server under headless Weston; asserts `hw.gpu.mode = swiftshader`
and that screenshots + gesture-tap work without a display. Currently
runs on `pull_request` for the affected paths + `workflow_dispatch`.
Pinned to `radon-daily` for the simulator-server until radon#118 ships a
Linux asset to `radon-main`. |
### Tests added in this PR
- `packages/argent/test/dispatcher.test.ts` — e2e against tmpdir-staged
dispatcher + fake `<platform>/simulator-server`: missing-binary error
path, exit-code passthrough, argv passthrough, SIGTERM forwarding.
- `packages/tool-server/test/native-devtools-ios-resolver.test.ts` —
`requireDarwin` gates for dylibs and `ax-service` on Linux, per-platform
`simulator-server` path resolution, missing-binary error,
`ARGENT_NATIVE_DEVTOOLS_DIR` override.
- `packages/tool-server/test/linux-preflight.test.ts` — KVM diagnostic
shape + `diagnoseAvdSizing` (ram low, heap low, both, neither, missing
keys).
- `packages/tool-server/test/boot-device-hotboot.test.ts` — extended
with `it.each([linux→swiftshader, darwin→auto])` so a single CI runner
exercises both GPU branches (was runner-platform-dependent, regressions
could pass on both runners individually). New cases for the wedged-frame
fallback (under fake timers because the polling budget is short but
real-time enough to exceed vitest's 5 s default), the GPU-mode whitelist
rejection, and the cold-boot composite-wait timeout.
- `packages/tool-server/test/android-binary.test.ts` — extended for
`defaultAndroidRoots` probe ordering and HOME pinning.
### Verification
- `tsc --build`: clean apart from pre-existing
`@argent/native-devtools-android` import errors unrelated to this
branch.
- `vitest run` in `packages/tool-server` and `packages/argent`: 689 + 11
= 700 tests passing; the 4 pre-existing test-file load failures
(native-devtools-android missing) reproduce on `main` and are out of
scope.
- `npm pack -w @swmansion/argent`: produces `swmansion-argent-0.8.0.tgz`
with the per-platform layout.
- Wayland-e2e: triggered on this branch — link will be in a PR comment
once the run completes.
- Manually verified end-to-end on a Wayland-on-hybrid-GPU developer
laptop: AVD boots through the tool-server with `-gpu swiftshader`,
screencap returns real frames, gesture tools work.
### Dependencies / what still gates this
- **radon#118** must land before the Linux artifact ships through the
default channel. The download script tolerates the asset being absent
today (best-effort), so this PR is technically safe to merge before —
Linux installs just won't get a working `simulator-server` until the
upstream artifact is published. Once it lands:
- Drop the `radon-daily` arg in `.github/workflows/wayland-e2e.yml`.
- Flip `ARGENT_REQUIRE_LINUX_BINARY` to `1` in both publish workflows.
- **wayland-e2e is not a required status check** today (no branch
protection on `main` requires it). Worth turning on before merging
anything else that touches the Linux boot path.
### Related
- radon#118 — produces the `simulator-server-argent-linux` artifact this
PR consumes.
- argent-private#11 — cross-builds the iOS Simulator dylibs on Linux.
Strictly orthogonal: those dylibs are macOS-runtime-only regardless of
where they're built, and this PR keeps them under `dylibs/` with a clean
error path on Linux callers.
</details>
Fixes #246
|
||
|
|
800b781faf |
feat: make ax-service a socket client, add optional TCP connection to native tools (#270)
- flips the server-client relationship between `tool-server` and `ax-service` launched on iOS simulators: now, `ax-service` connects to a listener in `tool-server` - adds an option to connect to `ax-service` / native devtools dylib via TCP instead of UNIX sockets Corresponds to [argent-private PR #12](https://github.com/software-mansion/argent-private/pull/12) |
||
|
|
d091421e3a |
fix(describe): unlock iOS 26.5 SpringBoard-hosted dialogs via IgnoreAXServerEntitlements (#244)
## Summary
iOS 26.5 tightened cross-process AX access: SpringBoard's AX server
rejects MIG queries from non-UIApplication clients with kAXError
\`-25215\`. Our \`ax-service\` daemon is a \`simctl spawn\`-launched CLI
binary, so it fails the new check, and \`describe\` returns an empty
ROOT for any SpringBoard-hosted dialog — most visibly the TCC permission
prompts (\`Allow X to use your location?\`, camera, contacts, photos,
microphone, notifications). On iOS 26.4 the same daemon code works fine.
Apple ships a debug switch in \`AccessibilityUtilities\` for exactly
this case: the \`com.apple.Accessibility/IgnoreAXServerEntitlements\`
preference. When set, the server skips the entitlement check, and our
existing \`-[AXElement explorerElements]\` walk returns the dialog tree
as expected.
This PR wires the pref next to the existing \`AutomationEnabled\` write
in \`ensureAutomationEnabled\`, plus a one-time SpringBoard kickstart so
SB re-reads its AX-server config. The pref persists on the sim's disk,
so subsequent ax-service spawns detect it's already set and skip the
kickstart — avoiding the disruptive app-icon reflow on every argent run.
## Before / after on iOS 26.5
**Before** (raw socket dump for the Maps location prompt):
\`\`\`
{ \"alertVisible\": true, \"elements\": [], \"systemElements\": [], ...
}
\`\`\`
→ describe returns \`source: ax-service ROOT AXGroup\` with no children.
**After**:
\`\`\`
Source: ax-service
ROOT AXGroup (0.000, 0.000, 1.000, 1.000)
AXStaticText \"Allow 'Maps' to use your location?\"
AXStaticText \"Your location is used to show your position on the map,
...\"
AXButton \"Allow Once\"
AXButton \"Allow While Using App\"
AXButton \"Don't Allow\"
...
\`\`\`
Matches iOS 26.4 behaviour. Same daemon binary, no entitlement claims,
no resigning.
## What I changed
Single file: \`packages/tool-server/src/blueprints/ax-service.ts\` —
added \~50 lines to \`ensureAutomationEnabled\` that:
1. Read the current value of \`IgnoreAXServerEntitlements\` from
\`com.apple.Accessibility\`.
2. If it isn't \`1\` already: \`defaults write\` it true, then
\`launchctl kickstart -k system/com.apple.SpringBoard\` so SB re-reads.
3. \`kickstart\` emits a warning about user/foreground service
identifier on iOS 26 — tolerated with \`.catch(() => undefined)\`; the
restart still happens.
## Cost characterisation
| Scenario | Cost |
|---|---|
| Fresh sim never touched by argent | One SpringBoard restart (~3-5s,
apps reflow, status bar blinks) |
| Sim with the pref already on disk (post-first-run, post-reboot, etc.)
| Zero — just one \`defaults read\` |
| Sim shut down when argent first targets it | Pref is written, SB picks
it up on next boot — no restart needed |
| iOS < 26.5 sim | Pref-write is harmless;
\`_shouldValidateEntitlements\` doesn't read it on older versions |
The kickstart fires at most once per sim, ever. The pref is per-sim in
CoreSimulator data; \`simctl erase\` wipes it (next run sets it again).
## Background — why this is the right layer
Briefly investigated:
- **Daemon code-path tweaks** (enumerating
\`currentApplicationsIgnoringSiri\`, fresh
\`_AXUIElementCreateAppElementWithPid\`, etc.) — all hit the same
server-side gate and return \`-25215\`. Cf. closed
software-mansion/argent-private#10 and software-mansion/argent#243.
- **Sign daemon with private AX entitlements** — sim AMFI rejects
(\`Security policy issue\` 163) for anything stricter than what we
already have.
- **Re-host daemon as UIApplication** — would pass the new check but is
a substantial refactor (Info.plist, scene config, background-task
management for the socket server, install/launch instead of spawn). Not
worth it when a one-line debug-pref unlocks the same outcome.
The IgnoreAXServerEntitlements path is the only one that's small,
contained, and reversible.
## Test plan
- [x] \`vitest run\` in tool-server passes (175/175)
- [x] \`tsc --noEmit\` clean on tool-server
- [x] Manual verify on iPhone Air iOS 26.5: Maps location prompt now
returns 5 AX elements from \`describe\` with \`source: ax-service\`
- [x] Manual verify on iPhone 17 Pro iOS 26.4: still works (pref-write
is a no-op for the gating logic on that version)
- [ ] CI / unit coverage for the new conditional kickstart path
## Files
- \`packages/tool-server/src/blueprints/ax-service.ts\` — extended
\`ensureAutomationEnabled\`
|
||
|
|
2180dd8e6a |
ci: typecheck test files in CI (#202)
We've had lots of type errors in our tests, scripts and other non-core
code despite all CI being green. This PR fixes that.
This PR also removes some redundant features like `@ts-check` inline
directives which have been replaced by proper ts config files.
<details>
## AI Summary
Audit of every CI workflow against every package's `package.json` and
`tsconfig` surfaced several gaps where CI did partial coverage. This PR
closes them and fixes every pre-existing type error the new gates
surfaced.
## Gaps closed
| # | Gap | Fix |
|---|---|---|
| 1 | `tsc --build` only covers `src/**`; test files (`test/**`,
`tests/**`) never typecheck. Vitest transforms tests with esbuild —
strips types instead of checking them. | New `tsconfig.test.json` per
test-bearing package (`composite: false`, `noEmit`, `rootDir: "."`,
includes `src/**` + `test/**` (or `tests/**`) + `vitest.config.ts`). New
`typecheck:tests` npm script per package. New CI step `npm run
typecheck:tests --workspaces --if-present`. |
| 2 | Build/publish scripts (`scripts/*.{cjs,mjs}`,
`packages/argent/scripts/*.cjs`, `packages/skills/scripts/install.js`) —
JS, never typechecked. `sync-readme.cjs` runs in `prepack`;
`postinstall.cjs` runs on every install; `bundle-tools.cjs` is
build-critical. A typo only surfaces at runtime. | Root
`tsconfig.scripts.json` with `allowJs` + `checkJs` + `noImplicitReturns`
covers all script paths. Root `typecheck:scripts` npm script. New CI
step. Per-file `// @ts-check` comments removed — tsconfig drives
inclusion. |
| 3 | `argent-cli` had no `typecheck:tests` script. When tests get added
there they'd silently bypass the gate. | Added `tsconfig.test.json` +
`typecheck:tests` script. `--if-present` would have skipped it anyway,
so this is future-proofing. |
| 4 | Local `prettier --check .` walked into the `argent-private`
submodule and reported violations there. CI didn't see this because
`actions/checkout@v4` doesn't init submodules — so local `prettier
--check` did not match CI behavior. | New `.prettierignore` excludes the
submodule plus `dist/`, `node_modules/`, lock files, `tsbuildinfo`.
No-op in CI; aligns local with remote. |
## Test type errors fixed
These were already present. The `npm test` passed because esbuild
stripped the offending types:
- **argent-installer**: widen `scope` literal type for
dead-code-elimination guard; non-null `addAllowlist!`/`removeAllowlist!`
(optional methods on adapter).
- **argent-mcp**: add `.js` extensions for NodeNext module resolution.
- **registry**: broaden `StaticBlueprintResult` blueprint api from `{
id; deps? }` to `Record<string, unknown>` so existing tests can pass
arbitrary api shapes.
- **tool-server**:
- non-null `zodSchema!` on tool defs that always carry one
(`launchAppTool`, `restartAppTool`, etc.)
- cast sentinel `"ignored"` strings to `DeviceInfo` in factory-rejection
tests
- match the 4-arg `dispatchByPlatform<IosServices, AndroidServices,
Params, Result>` signature (tests still passed the old 3-arg shape)
- `assertFlowRunResult` type guard for the `FlowRunResult |
FlowPrerequisiteNotice` union before `.steps` access
- swap broken `typeof import("supertest").default` for static `import
supertest` (supertest is `export = supertest`)
- fill in the 4 fields missing from a `NativeProfilerSessionApi` mock
(`xctraceProcess`, `recordingTimedOut`, `recordingExitedUnexpectedly`,
`lastExitInfo`)
- cast a vitest `Mock` to a plain function at one direct call site where
the `Procedure | Constructable` union loses callability
- fix `update-checker.test.ts` reading `../../package.json` (off-by-one
— went past tool-server; was relying on a Vite resolver quirk)
- replace one cross-package `../../../registry/src/index` import with
`@argent/registry` so registry's private `services` field isn't compared
between src and dist declarations
All fixes are type-level. Full `npm test --workspaces --if-present`
still reports **991 passed across 87 test files**.
</details>
|
||
|
|
4255b0bd66 |
fix: stop simctl spawn loop on iOS init failure (#233)
Fixes #210 — `argent mcp` pinning CPU to 100% and spawning hundreds of `simctl` processes that survive `kill -9`. ## The problems 1. **No retry cap.** The simulator watcher polls every 10s; on any failure it forgets the UDID and re-classifies it as "new" on the next tick. Each retry runs 4–5 `xcrun simctl spawn` calls in series. A persistently failing simulator produces an unbounded loop — hundreds of `entered ERROR state` log lines and a steady process pile-up. 2. **No timeouts on `simctl spawn`.** A degraded `CoreSimulatorService` makes simctl block forever. The factory `await`s the hung call, never rejects, never retries, but the stuck child process stays alive. ## How this fixes it - **Bounded retries with terminal "given up" state.** Each `NativeDevtools` service instance now tracks `{attempts, lastError, givenUp}`. After 3 consecutive init failures the service stops trying and reports `init_failed` to any caller. The watcher keeps polling but skips UDIDs in the given-up state. - **10s timeout on every `simctl spawn` call** in `ensureEnv`, `ensureAccessibilityEnabled`, and `ensureAutomationEnabled`. Healthy CSS responds in ~300ms; 10s is well above any plausible legit latency and well below "hung forever." A hung simctl now rejects cleanly, increments the attempt counter, and feeds the bounded retry instead of accumulating children. - **Tools surface `init_failed` as a typed result** instead of throwing. `launch-app`, `boot-device`, `native-devtools-status`, and the rest of the native tools now return a structured error with the failure reason and a hint to restart the simulator, instead of throwing an opaque exception. - **Factory no longer throws on first env-init failure.** Previously a single failed `simctl` call during factory creation leaked the socket server. The factory now tolerates init failure; the watcher's bounded retry handles the recovery. Verified on live simulator and against test simulating `simctl` timeouts. Happy path works as before, `restart-needed` is still signalled and the new failure path works as well. |
||
|
|
837d088744 |
test: cover #148 (android) (#186)
This PR adds tests for #148. ## Summary Moves the Android emulator branch's test coverage out of #148 so that PR can be reviewed without the test churn. This branch adds the test files back on top of the (now test-stripped) base branch. Tests inseparable from source changes (`describe-tool`, `workspace-reader`, `workspace-reader-integration`) and the `boot-simulator.test.ts` deletion stayed on #148 - base PR's CI passes on its own. ## Test plan - [x] `npx tsc --build` — clean - [x] `npx prettier --check .` — clean - [x] `npm test --workspaces --if-present` — 585/585 tool-server tests pass; all other workspaces green --------- Co-authored-by: Paweł Fornagiel <pawel.fornagiel2@gmail.com> |