mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
feat/android-atomic-clear
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b1cadd7cd6 |
fix(native-devtools): terminal state for non-injectable Apple system apps (#459)
Fixes #453 ## The bug For an app that can never be injected (an Apple built-in app such as `com.apple.Preferences`), `native-devtools-status` kept reporting `requiresRestart: true` / `nextLaunchWillBeInjected: true`, and the native-* feature tools (native-describe-screen, native-find-views, etc.) kept returning "restart-app then retry". Restarting never changes the state, so an autonomous agent loops `restart-app → retry` forever. There was no terminal "this app cannot be injected" signal. ## Why these apps can't inject Apple system/built-in apps (bundle ids under `com.apple.`) are platform binaries with library validation enabled; the simulator ignores `DYLD_INSERT_LIBRARIES` for them, so the Argent native devtools dylib can never load. This is a reliable, well-understood signal on the simulator. Third-party apps the user installs carry no such restriction and inject fine. So the fix treats bundle ids starting with `com.apple.` as non-injectable, via a general predicate rather than hardcoding the single bundle id from the issue. ## The fix - New pure predicate `isInjectableBundleId(bundleId)` in `blueprints/native-devtools.ts` returning `false` for `com.apple.*` bundle ids (case-sensitive prefix, matching how bundle ids are compared), with a doc comment explaining the library-validation rationale. - New failure code `NATIVE_DEVTOOLS_NOT_INJECTABLE` in `@argent/registry`. - `precheckNativeDevtools(api, udid, bundleId)` (the 3-arg overload) now throws a terminal `FailureError` with that code, *before* the `requiresAppRestart → restart_required` branch, when the bundle id is non-injectable. Because every native-* feature tool awaits this precheck and propagates the throw, all 7 tools now surface a hard, terminal error instead of an unbounded restart→retry instruction — no change needed in the individual tool files. The 2-arg overload (no bundleId), used by `native-devtools-status`, is unchanged and never throws (guarded by `bundleId !== undefined`). - `native-devtools-status` now *reports* the state instead of throwing: it adds an `injectable: boolean` field. For a non-injectable app it returns `injectable: false, requiresRestart: false, nextLaunchWillBeInjected: false` (with `envSetup`/`appRunning`/`connected` still measured), giving an agent that checks status first a terminal signal. The tool description documents the new field and that `injectable: false` is terminal (do not restart/retry; use the standard describe / screenshot / view-at-point tools). ## Verification - `npm run build` (root tsc): clean. - `vitest run` on `native-devtools-status.test.ts`: 13/13 passing. Added coverage for the predicate, the status terminal state for a `com.apple.*` app, and that the 3-arg precheck throws `NATIVE_DEVTOOLS_NOT_INJECTABLE` while the 2-arg overload does not throw for the same api. - Telemetry `sanitize.test.ts` (iterates every registered failure code): 49/49 passing — the new code is accepted. - Prettier + eslint (`--max-warnings 0`) clean on all touched files. Device E2E on an iOS simulator with `com.apple.Preferences` (confirming `native-devtools-status` returns `injectable: false` and the native-* tools return the terminal error rather than looping restart-app) is pending and will be run separately. --- ### Device verification (iOS 18.5 simulator, com.apple.Preferences) Reproduced before/after on a real simulator with a dev tool-server: **Before (origin/main):** `native-devtools-status` for `com.apple.Preferences` → `requiresRestart:true, nextLaunchWillBeInjected:true`; `native-describe-screen` → `restart_required`; after `restart-app` the status is identical (the infinite loop). **After (this branch):** - `native-devtools-status` → `{envSetup:true, appRunning:true, connected:false, requiresRestart:false, nextLaunchWillBeInjected:false, injectable:false}` — terminal. - `native-describe-screen` / `native-find-views` → terminal error: "com.apple.Preferences is an Apple system app ... can never be injected ... use the standard describe / screenshot / view-at-point tools instead." (no more restart→retry loop). **Regression (injectable third-party app `xyz.blueskyweb.app`):** `native-devtools-status` → `injectable:true` with the normal `requiresRestart:true`; `native-describe-screen` → the normal `restart_required` (not the terminal error). The predicate does not false-positive on normal apps. --- ### Review follow-ups (@hubgan on-device review) - **Recovery guidance no longer recommends `view-at-point`.** Both view-at-point tools run this same 3-arg precheck and re-throw `NATIVE_DEVTOOLS_NOT_INJECTABLE` for a `com.apple.*` bundle, so recommending them dead-ended the agent. All three surfaces (the precheck throw, the `describe` iOS fallback hint, and the `native-devtools-status` description) now share one verbatim "do not fall back to the native-* tools" warning (`NON_INJECTABLE_NATIVE_WARNING`) and recommend only `describe` and `screenshot`. ( |
||
|
|
20f5a1282c |
feat: initial sim-remote support (#291)
## Summary
Adds **initial support for remote iOS simulators** ("sim-remote") —
simulators that run in the cloud (radon-cloud orchestrator) and are
driven from the dev's machine over a QUIC/MoQ tunnel, alongside the
existing local iOS simulators. A remote sim is exposed as a new device
platform, `ios-remote`, and the existing
interaction/inspection/profiling tools work against it through the same
tool surface.
To get there without scattering `isRemote` branches across every tool,
the iOS code paths are refactored behind a small set of
host/transport/backend abstractions so each tool stays
transport-agnostic.
## How it works
- **Discovery** — `list-devices` enumerates remote sims via the
`sim-remote` CLI in addition to local `simctl`. They classify as
`platform: "ios-remote"`. Enumeration is best-effort: if `sim-remote`
isn't installed or the user isn't logged in, it silently returns none.
- **Transport** — remote interaction goes over a MoQ relay
(WebTransport/QUIC). `moq-client.ts` subscribes to the server-published
`simulator` broadcast (catalog/video/screenshot) and publishes a
`control` track carrying protobuf-encoded input commands (touch / key /
button / rotate / wheel / screenshot). WebTransport is provided in Node
by `@fails-components/webtransport`, polyfilled lazily on first use.
- **Native devtools over the tunnel** — unix sockets don't bridge the
QUIC tunnel, so on remote sims `ax-service` and `native-devtools` are
forced onto TCP with OS-assigned ephemeral ports, and a reverse proxy is
wired before the in-sim daemon dials back.
## Key components
New (`packages/tool-server/src/`):
- `utils/sim-remote.ts` — thin wrapper around the `sim-remote` CLI
(simctl verbs, `moq-info`, proxy), propagating CLI stderr verbatim.
- `utils/moq-client.ts` — pure-JS MoQ client for the remote
simulator-server relay.
- `utils/datachannel-proto.ts` + `proto/datachannel.proto` — vendored
protobuf schema + encoders for the control channel.
- `utils/ios-host.ts` — `IosHost` abstraction (`localIosHost` /
`remoteIosHost`, picked by `pickIosHost(device)`) capturing the
local-vs-remote differences (transport, env setup, proxying,
`requiresTcp`).
- `utils/simctl-backend.ts` — strategy for simctl verbs so one iOS
handler serves both `xcrun simctl` (local) and `sim-remote simctl`
(remote).
- `utils/ax-prefs.ts` — AX-pref helpers extracted from `ax-service`.
-
`tools/{launch-app,open-url,reinstall-app,restart-app}/platforms/ios-remote.ts`
+ shared handlers.
Refactors:
- **Strategy pattern** to limit `isRemote`/platform branching inside
tool and blueprint implementations.
- `simulator-client.ts` gains a transport abstraction
(`SimulatorServerTransport`) so call sites stay transport-agnostic;
local sims back it with WebSocket+HTTP, remote sims with the MoQ client.
- `ax-service` / `native-devtools` blueprints updated for ephemeral TCP
ports and the `ios-remote` platform.
- Dependency checks: tools that pick a platform path internally now gate
on the right CLI (`sim-remote` vs `xcrun`).
## New dependencies
`@moq/lite`, `@moq/net`, `@fails-components/webtransport`,
`@fails-components/webtransport-transport-http3-quiche`, `protobufjs`.
## Testing
- Existing unit suite passes (CI green); the native-devtools
status/describe tests now stub the platform dep-check so they exercise
their logic, not the host toolchain.
- E2E device-smoke jobs (local iOS sim, Android, Chromium, remote VVD)
pass.
---
<sub>Branch rebased onto latest `main` (linear history). `sim-remote` is
opt-in — only required when driving a remote iOS simulator.</sub>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
d9dc17d0d5 |
fix: move describe to native devtools (#83)
* fix: move describe to native devtools Replace the AX describe path with an app-scoped native hierarchy adapter so coordinate discovery stays on the supported injection flow. Also add safer targeting and restart guidance to reduce ambiguous Home and cold-start recovery paths. Made-with: Cursor * init * update sim server * Delete NATIVE_DEVTOOLS_DESCRIBE_MIGRATION_NOTE.md |