mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
analyze/pr241-projectpath
321 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
18d30586dc |
test(installer): stop hasArgentEntry test deleting real global configs
The hasArgentEntry per-adapter test resolved configPath via projectPath() ?? globalPath(), then rmSync'd it. For global-only adapters (Windsurf, Hermes) projectPath() is null, so this block — which never pinned homedir() — deleted the developer's real ~/.codeium/windsurf/mcp_config.json or ~/.hermes/config.yaml. Pin homedirOverride to tmpDir for the block (matching findConfiguredAdapterScopes) so globalPath() stays inside the sandbox. |
||
|
|
c5e4e471ca |
fix(update): keep adapter detection best-effort on malformed configs
findConfiguredAdapterScopes probed every adapter's hasArgentEntry with no error handling. Hermes' hasArgentEntry parses YAML via readYaml, which throws on malformed input, so a single broken ~/.hermes/config.yaml aborted the entire `argent update` flow instead of being skipped best-effort like on main. Wrap each probe so a throwing/unparseable config is treated as "not configured" and skipped. |
||
|
|
f1210e5548 |
Merge branch 'main' into fix/issue-195-update-respects-init-choices
# Conflicts: # packages/argent-installer/src/update.ts # packages/argent-installer/test/mcp-configs.test.ts |
||
|
|
c5c3f7af11 |
feat: feature flags (#260)
## Summary
- New top-level CLI commands `argent enable <flag>` / `argent disable
<flag>` / `argent flags`.
- Storage is a small JSON file at `~/.argent/flags.json` by default;
`--scope project` writes to `<project-root>/.argent/flags.json` instead.
- `enable` writes `true`. `disable` removes the entry at the chosen
scope (and deletes the file + empty `.argent/` when it becomes the last
flag).
- `isFlagEnabled` walks project → global, so a project entry shadows the
same key in the global file. To opt a single project out of a
globally-enabled flag, hand-edit `<project>/.argent/flags.json` to
`{"flags":{"name":false}}` — there is no CLI for an explicit override.
- The dispatcher routes the new commands to `@argent/cli` exports;
bundling needs no config changes (only `node:fs|path|os`).
## Behaviour
- `argent enable foo` → writes `{ "flags": { "foo": true } }` to global
file.
- `argent disable foo` → removes `foo` from the global file (clean round
trip with `enable`).
- `argent enable foo --scope project` → writes to
`<project-root>/.argent/flags.json`.
- `argent flags` → lists effective state with the winning scope;
`--json` for scripting.
- Project root is the nearest ancestor with `.argent`, `.git`, or
`package.json` — falls back to cwd when none found.
- POSIX `--` separator: `argent enable -- weird-name` is supported.
- Help (`--help` / `-h`), invalid `--scope`, invalid flag names, missing
arg, unknown flags all produce a clear stderr error and `exit 2`.
- Flag-name validation: `/^[a-zA-Z][a-zA-Z0-9._-]*$/`.
- Writes are atomic (tmp file + rename) so concurrent invocations cannot
tear the JSON.
## Reader API
`isFlagEnabled(name)` is exported from `@argent/cli` for future
consumers; it walks project → global and returns `false` when unset.
## Files
- `packages/argent-cli/src/flags.ts` (new) — storage + CLI handlers
- `packages/argent-cli/src/index.ts` — re-exports `enable` / `disable` /
`flags` / `isFlagEnabled` / `FlagScope`
- `packages/argent-cli/test/flags.test.ts` (new) — 41 tests
- `packages/argent-cli/vitest.config.ts` (new)
- `packages/argent-cli/package.json` — description bump
- `packages/argent/src/cli.ts` — dispatcher routes + help text
- `README.md`, `CONTRIBUTING.md` — command tables
## Test plan
- [x] `npm test --workspaces --if-present` → 1085 tests pass (41 in
`@argent/cli`).
- [x] `npm run typecheck:tests --workspaces --if-present` → clean.
- [x] `npm run build` (root `tsc --build`) → clean.
- [x] `npm run build -w @swmansion/argent` (dispatcher + esbuild
bundles) → clean.
- [x] `npx prettier --check .` → clean.
- [x] End-to-end against the built dispatcher in a tmpdir:
- `argent enable my-feature-flag` (global) writes to
`~/.argent/flags.json`.
- `argent enable proj-only --scope project` writes to
`<proj>/.argent/flags.json`.
- `argent disable my-feature-flag` removes the entry; round trip leaves
a clean tree (file + empty `.argent/` removed when no other state lives
there).
- `argent disable foo` preserves sibling flags in the same file.
- `argent flags` lists each flag with the winning scope.
- `argent enable --` errors cleanly; `argent enable -- name-after`
accepts the literal.
- `argent enable foo --scope project` walks up to `.git` /
`package.json` / `.argent`; stray editor configs (`.claude`, `.cursor`,
…) do NOT count as project roots.
- Error paths (`--scope bogus`, no name, `1bad-name`) exit `2` with a
clear message.
|
||
|
|
dee49473c8 |
refactor(tool-server): remove ARGENT_DISABLE_UPDATE_NOTIFICATIONS opt-out (#282)
## What Removes the `ARGENT_DISABLE_UPDATE_NOTIFICATIONS` environment variable and everything tied to it: the `DISABLE_ENV` constant, the exported `areUpdateNotificationsDisabled()` predicate, the early-return gate in `startUpdateChecker()`, the reminder-note bullet that advertised it, and the corresponding test. ## Why The env var made `startUpdateChecker()` a no-op, which left `getUpdateState()` stuck at its defaults (`updateAvailable: false`, `installableVersion: null`). Because the `update-argent` tool reads that shared state, **silencing the periodic reminder also blinded the explicit, user-consented update path** — `update-argent` would report *"Argent is already up to date"* even when a newer version existed on npm. A user who'd quietened the nags but then explicitly asked the agent to update was wrongly told there was nothing to do (only the CLI `npx @swmansion/argent update` still worked). Postponing/suppressing reminders is already handled by the time-based `dismiss-update` tool, whose suppression persists in `~/.argent/update-suppression.json` and does **not** disable the underlying check — so the update state stays accurate. That makes the env var both redundant and a footgun, so it's removed rather than worked around. ## Behavior after this change - The 24h update checker always runs; `getUpdateState()` reflects real registry state. - Reminders are still gated by `dismiss-update` (and the 30-minute auto-suppress after each surfaced note). - `update-argent` now sees accurate state regardless of reminder suppression. ## Tests - Removed the now-obsolete "does not check or notify when notifications are disabled" test. - `vitest run` for `update-checker`, `http-update-note`, and `update-argent-tool` passes (42 tests); `tsc --noEmit` clean for `tool-server`. |
||
|
|
d670751db3 |
chore: bump all package versions to 0.9.0 (#281)
## Summary - Bump all 11 workspace packages from `0.8.1` → `0.9.0` (`package.json` + `package-lock.json`) - Includes `@argent/update-core`, which was added since the last bump (#267) - `@argent/ui` left at `0.0.1` (independent versioning) ## Notes - Ran `npm install` to update `package-lock.json` — resolved cleanly - Ran `npm run format` (prettier) — no files needed reformatting - No `lint` script / eslint config exists in this repo, so there was nothing to lint ## Test plan - [ ] Verify `npm install` resolves cleanly - [ ] Verify `argent --version` reports `0.9.0` |
||
|
|
cb8db6d4ac |
feat(boot-device): pass canonical launch-hardening flags (#269)
## Summary argent's boot-device currently spawns `emulator -avd <name> -gpu auto` plus the snapshot flags — nothing else. Five flags every MCP-driven boot benefits from were missing; add them as `LAUNCH_HARDENING_ARGS` and spread into the snapshot probe, hot boot, and cold boot. ### Performance (3 flags) - `-noaudio` — guest skips pulseaudio init; saves one thread and ~50 MB RSS. - `-no-boot-anim` — skips the Pixel boot animation, a major CPU spike on software-rendered GPU modes for ~30 s. - `-netfast` — disables qemu network shaping; pure overhead for MCP use. Measured on a 4-core i5-6300U with a 4096 MB / 228 MB-heap AVD, warm-cache cold boot, no other workload, `-gpu swiftshader`: | | Boot time | Δ | |---|---|---| | Baseline (no extras) | 66 s | — | | + 3 perf flags | 49 s | −26% | | + all 5 (incl. dialog suppression) | 47 s | −29% | qemu RSS at +20 s post-boot drops ~190 MB (5%) with the perf flags. `android-emulator-runner` (the canonical CI launcher) defaults to the same three. ### Dialog suppression (2 flags) - `-crash-report-mode never` — keeps emulator crashes from popping a Qt consent dialog that blocks the next boot until a human dismisses it. Crash dumps still land in `/tmp/android-unknown/emu-crash-*.db`; only the modal popup is suppressed. - `-no-metrics` — same for the metrics-collection consent dialog. Unrelated to any argent profiler tool: those run guest-side via Perfetto/simpleperf or via Metro CDP, none of which depend on the emulator's host-side Google telemetry. ### Snapshot parity `-noaudio` and `-netfast` change qemu device topology. The snapshot's saved state has to match the runtime config the next boot uses or the snapshot validator rejects perfectly loadable snapshots — the same class of bug as the prior `-gpu auto` parity work that introduced `RENDERER_ARGS`. Plumb the hardening args into the same three call sites (probe `extraArgs`, hot spawn, cold spawn) so the probe agrees with both boot paths. ## Test plan - [x] Unit tests: extended `boot-device-hotboot.test.ts` with two new cases asserting the flags reach the hot-boot and cold-boot spawn argv; tightened the "same renderer args" parity test to also require `-noaudio`/`-netfast` on the probe. **712 tests pass.** - [x] Live-emulator verification on Linux: actual emulator boot with all 5 flags completes in 47 s, no flag-rejection warnings in the emulator log. - [ ] Confirm no regression on a macOS test host (please run locally before merging — I can only verify Linux here). - [ ] Confirm CI green. Pre-existing test failure in `android-binary.test.ts` on `origin/main` reproduces without this change — host-side `$PATH` leakage in the test setup, not in scope for this PR. |
||
|
|
ee97c443b7 |
feat(tool-server): make the update reminder configurable and release-age aware (#276)
## Why The tools-server shows an update reminder (a `note` appended to every tool response) once a newer `@swmansion/argent` lands on npm. Two pain points: 1. **No way to turn it off.** Suppression is in-memory only (`dismiss-update`, 30-min auto), so it comes back on every server restart. 2. **It nags about versions you can't install yet.** Registries with a **minimum-release-age** (package-cooldown) security policy refuse to install a version until it has been public for N days. Until then the reminder repeats every day for the whole cooldown window with nothing the user can do about it — exactly the "reminded for 5+ days" situation. ## What's in this PR **1. A config option to disable the reminder** - New env var **`ARGENT_DISABLE_UPDATE_NOTIFICATIONS=1`** (`"1"` or `"true"`). - When set, `startUpdateChecker()` is a no-op: **no registry request** is made and **no note** is ever attached. - The opt-out is also surfaced inside the reminder text itself, so the agent can tell the user how to silence it permanently. **2. Minimum-release-age awareness** (answers "detect the policy and check the update is actually installable first") - The checker now fetches the **npm packument** (`registry.npmjs.org/@swmansion/argent`) instead of `/latest`, because only the packument carries the `time` map of version → publish timestamp. - It detects the machine's policy and only reminds once the latest version has **aged past** it. Notification is gated on a new **`updateInstallable`** flag (newer **and** old enough) rather than `updateAvailable`. - `updateAvailable` keeps its pure version-comparison meaning; with no policy, `updateInstallable === updateAvailable` (no behavior change for users without a policy). ### Is detecting the user's min-age policy possible? Yes — partially. Each package manager stores it differently, and the tools-server has no reliable signal for *which* PM installed argent, so we probe every PM present and take the **most restrictive** (largest) value. Erring toward over-waiting is intentional: a slightly late reminder beats nagging about a forbidden version. | PM | Config key | Unit | Detect via | Min PM version | |----|-----------|------|-----------|----------------| | npm | `min-release-age` | days | `npm config get min-release-age` | 11.10.0 | | pnpm | `minimumReleaseAge` | minutes | `pnpm config get minimumReleaseAge` | 10.16 | | yarn (berry) | `npmMinimalAgeGate` | minutes | `yarn config get npmMinimalAgeGate` | 4.10 | | bun | `minimumReleaseAge` (bunfig.toml `[install]`) | seconds | no `config get` — not auto-probed | 1.3 | | yarn classic | — (unsupported) | — | — | — | Because bun has no `config get` for this and auto-detection is best-effort, there's also an explicit override: - **`ARGENT_MIN_RELEASE_AGE_DAYS`** — positive number of days; wins outright and skips probing (useful for bun users, locked-down CI, or pinning the value). When a policy holds an update back, a single stderr line explains the silence; no reminder reaches the agent. ## Conservative edge handling - Network/registry failure → state unchanged (never crashes the server). - Policy in effect but publish time missing/unparseable → treated as **not** installable (stay silent rather than nag). - PM probe errors / PM absent → treated as no policy (0). - Probes shell out via PATH (`exec`, 3s timeout, constant commands only — no caller input) so Windows `.cmd`/`.ps1` shims resolve. ## Tests - `test/min-release-age.test.ts` (new): unit-level parsing, override precedence, unit normalization, most-restrictive-wins, PM-absent. - `test/update-checker.test.ts`: rewritten for the packument shape; new cases for held-by-policy, aged-past-policy, unknown publish time, and the disable flag (probe mocked so tests never spawn a real PM). - `test/http-update-note.test.ts`: gate now keys off `updateInstallable`; added a "available but held by policy → no note" case. - Full tool-server suite green (**732 tests**), `tsc --noEmit` and `typecheck:tests` clean. ## Follow-up (not in this PR) The env-var reference lives in the `argent-private` submodule (`docs/environment-variables.md`), which is a separate repo — needs a companion PR. Suggested rows for the tools-server table: ``` | `ARGENT_DISABLE_UPDATE_NOTIFICATIONS` | `"1"`/`"true"` or unset | disabled | When truthy, disables the update reminder entirely (no registry check, no note). | | `ARGENT_MIN_RELEASE_AGE_DAYS` | number | _(auto-detect)_ | Minimum age (days) a newer version must reach before it is advertised. Overrides auto-detection of the npm/pnpm/yarn minimum-release-age policy. | ``` ## Open questions for review - **Most-restrictive-across-PMs** vs. detecting a single PM — is "max wins" the behavior we want, or should it follow `npm_config_user_agent`? - Should an update held by policy stay fully silent (current), or emit a softened "available, installable in ~N days" note? --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
f34ce22b9a |
fix(tool-server): persist update-note suppression across restarts (#273)
## Summary `dismiss-update` (and the auto-suppression after delivering an update note) only stored `suppressUntil` in a module-scoped variable, so the dismissal evaporated the moment the tool-server process exited — which the launcher does after 30 min of idleness, on machine reboot, and on `npx @swmansion/argent update`. Users asking to silence the note for 24 h would see it reappear well within the window, contradicting the tool's own description. This PR persists `suppressUntil` to `~/.argent/update-suppression.json` (alongside the existing `tool-server.json` / `tool-server.log`) and reads it back on module init, so the dismissal survives restarts. - `suppressUpdateNote` now writes through to disk and throws on write failure (matches `dismiss-update`'s documented "Fails if … the suppression state cannot be persisted"). - The HTTP auto-suppress call (after delivering a note alongside an unrelated tool result) is wrapped in try/catch — a disk-write failure there must not 500 the user's tool call. Worst case: the note repeats next request. - Missing/malformed/wrong-typed file all degrade to "no suppression" — never crashes the server. ## Test plan - [x] `npx vitest run test/update-checker.test.ts` — 19/19 pass (10 original + 9 new persistence tests). - [x] `test/http-update-note.test.ts` + `test/http-tools-meta.test.ts` — 14/14 pass (mock update-checker; persistence is transparent). - [x] Full `@argent/tool-server` suite — 726/727 pass. The single failure (`test/android-binary.test.ts:73`, `/usr/bin/adb` resolution) is pre-existing on `main` and unrelated. - [x] `npm run typecheck:tests` clean, `npm run build` clean, `prettier --check` clean. - [x] Restart simulated via `vi.resetModules()` + re-import → suppression loaded back from disk. |
||
|
|
de0dbbda66 |
feat(tools): implement screenshot-diff tool (#229)
## Summary Adds a new `screenshot-diff` tool for comparing mobile screenshots during UI QA and visual regression checks. The tool can compare two saved PNGs, or compare one saved PNG against one live full-resolution device capture, then returns a compact text summary and generated diff artifacts. ## Context Mobile UI validation often needs more than a single screenshot: small layout, color, typography, copy, clipping, and regression changes are easy to miss manually. This feature gives agents a deterministic before/after comparison path while keeping `screenshot-diff` as supporting evidence. ## What Changed - Registered a new `screenshot-diff` tool in the tool server. - Supports saved PNG comparisons and one-sided live capture via `captureBaseline` or `captureCurrent`. - Adds PNG diffing with fixed internal defaults, changed-region detection, status-bar-band ignoring, full-size diff output, and downscaled context diff output. - Adds optional OCR/text analysis for moved, appeared, disappeared, changed, and restyled text. - Updates MCP content rendering to attach the downscaled context diff image plus the text summary. 1. Capture a stable full-resolution baseline. 2. Perform an interaction or apply a UI change. 3. Compare the current screen against the baseline. 4. Review a summarized diff plus generated artifacts. |
||
|
|
05fb58552d |
chore: bump all package versions to 0.8.1 (#267)
## Summary - Bump all 10 workspace packages to version `0.8.1` (`package.json` + `package-lock.json`) - Includes `native-devtools-android` (`0.7.1` → `0.8.1`), all others from `0.8.0` → `0.8.1` - `ui` package left at `0.0.1` (independent versioning) ## Test plan - [ ] Verify `npm install` resolves cleanly - [ ] Verify `argent --version` reports `0.8.1` |
||
|
|
c6f4995cfe | chore: bump argent-private submodule to latest main | ||
|
|
90c6081204 |
fix: prevent returning empty screenshots (#261)
## What broke `toMcpContent` base64-encoded whatever bytes the simulator-server URL fetch returned and labelled them `image/png` with no checks. A 404 on the `/media/...` endpoint (file missing) returns an empty body — that became `data: ""` shipped as a PNG, which the model API rejects with "Image could not be processed". Claude Code then loops on the error and burns the session limit (cf. anthropics/claude-code#60334). Reproduces directly: ``` $ curl -sI http://127.0.0.1:<port>/media/missing.png HTTP/1.1 404 Not Found content-length: 0 ``` ## What changed One helper added: `fetchPngBytes(url)` returns the bytes only if: - `fetch` resolves (catches ECONNREFUSED / DNS / abort) - `res.ok` is true - the buffer starts with the 8-byte PNG signature If any check fails, `toMcpContent` emits a single text placeholder instead of an image block. Diff: **+103 / −27** across `content.ts` + tests. ## E2E verification Tested against the real MCP stdio JSON-RPC interface (not just unit tests): **Happy path** — real Android emulator, real tool-server: ``` [1] initialize: argent v0.5.3 [3a] screenshot returned 2 block(s) [0] image: mime=image/png dataLen=66968 validPngSig=true [1] text: Saved: /var/folders/.../media/430131000-...png HAPPY PATH overall: PASS ``` **Failure path** — mock tool-server returning a URL pointing at a closed port: ``` mock on :56529 returned 1 block(s) [0] text: (Screenshot unavailable: no valid PNG at http://127.0.0.1:1/stale.png. Take a new screenshot.) hasImage=false, hasPlaceholder=true FAILURE PATH: PASS ``` ## Test plan - [x] `npx vitest run` — 1048/1048 pass (5 new regression tests in `content.test.ts` cover 404, non-PNG body, fetch-throw, and the no-url fallback) - [x] `npm run build` — tsc clean - [x] `npx prettier --check` — clean - [x] Bundled MCP server rebuilt and driven via real JSON-RPC stdio against (a) a real Android screenshot and (b) a mock tool-server reproducing the bug --------- Co-authored-by: Filip131311 <contact@filipkaminski.com> |
||
|
|
324d8c6049 |
fix(android): stop forcing cold boot on every emulator launch (#251)
## Summary `hasDefaultBootSnapshot` enforced a 60s mtime-skew guard between `snapshot.pb` and `ram.bin`. In practice the emulator updates `snapshot.pb` metadata on every load (load count, last-loaded timestamp) even with `-no-snapshot-save`, so the two mtimes drift apart by hours or days after a few hot-boot sessions and the guard rejects every valid snapshot — forcing a cold boot every time. The bug hits macOS too; Linux just makes it painful because cold boot is slow there. Confirmed against my own `Pixel_6_API_34`, whose `snapshot.pb` is 4 days newer than `ram.bin` while the snapshot itself is fully usable. ## What changed - **Drop the mtime-skew guard.** `-check-snapshot-loadable` + the hot-boot spawn's `-force-snapshot-load` already make truncated / corrupt `ram.bin` fail loudly via `attemptBoot`'s `earlyExitError` race. The on-disk pre-check only needs to confirm both files exist and `ram.bin` is non-empty. - **Resolve the `.avd` folder via the AVD's `.ini` `path=` line**, matching what the emulator binary itself does (`external/qemu/android/android-emu/android/avd/util.cpp`). Snap-installed Studio on Linux and users who relocate AVDs to faster disks both put `.avd` outside the convention root, where the old `<root>/<name>.avd` assumption silently failed. - **Honor `$ANDROID_USER_HOME`, `$XDG_CONFIG_HOME`, `$ANDROID_SDK_HOME`** in priority order alongside `$ANDROID_AVD_HOME` and `$HOME/.android/avd`, mirroring the emulator binary's own lookup precedence. - **Reject whitespace-only and relative `path=` values.** The regex's non-greedy `(.+?)` still captures a single space for `path= `, which would have leaked past `if (!avdPath)` as truthy; a relative `path=` would have resolved against `process.cwd()` and silently mis-located snapshots. ## Test plan - [x] 18 new unit tests in `avd-snapshot.test.ts` covering: priority order across all five env-var candidates, relocated AVDs, drifted mtimes (4-day skew explicitly regression-tested), zero-length / missing / directory-shaped files, whitespace-only paths, relative paths, bad-then-good fallthrough. - [x] Full `npm run test -w @argent/tool-server` — 703 passing. - [x] `npm run typecheck:tests -w @argent/tool-server` — clean. - [x] `npm run build` from workspace root — clean. - [x] `npx prettier --check` on changed files — clean. - [x] End-to-end repro against a real local AVD: drifted `Pixel_3a_API_34`'s `snapshot.pb` mtime to 5 days newer than `ram.bin` (the exact pathology in the wild), drove `boot-device` through this branch's built code, observed `qemu-system-aarch64 ... -force-snapshot-load -no-snapshot-save` in the spawned process — i.e. the hot-boot path was taken with a snapshot the old check would have rejected. ## Risk Pre-check is now more lenient on disk shape. If `ram.bin` is non-empty but truncated/corrupt, the hot-boot spawn still catches it via `-force-snapshot-load` (loud early child-exit) and `attemptBoot` falls back to cold boot. So the worst case has not regressed; it just surfaces at spawn-time instead of pre-spawn. |
||
|
|
11df817616 |
test: drop real-time timeouts from the slowest unit tests (#236)
The slowest unit tests were waiting on real wall-clock timers, not doing
work. This makes them deterministic and fast with **no production
behavior change and no production test seams**.
## Speedup
**Suite-level**
| Scope | Before | After | Faster |
|---|---|---|---|
| tool-server — wall clock | 2.72s | ~1.3s | ~2× |
| tool-server — aggregate test time | 9.70s | 4.03s | 2.4× |
| argent-mcp — aggregate test time | 0.90s | 0.28s | 3.2× |
**Per file**
| File | Before | After | Faster |
|---|---|---|---|
| simulator-server-blueprint.test.ts | 2.55s | 0.05s | ~50× |
| boot-device-hardening.test.ts | 2.17s | 0.18s | 12× |
| boot-device-spawn-error.test.ts | 1.11s | 0.005s | ~200× |
| boot-device-hotboot.test.ts | 1.01s | 0.06s | 16× |
| fetch-timeout.test.ts | 0.81s | 0.18s | 4.5× |
| flow-tools.test.ts (delayMs test) | 0.31s | 0.04s | ~7× |
**Worst individual tests, before → after**
| Test | Before | After |
|---|---|---|
| rejects the boot promise…when spawn emits error | 1.06s | ~1ms |
| reports the emulator crash error… | 1.00s | ~2ms |
| falls back to cold boot when hot-boot child exits early | 1.00s | ~1ms
|
| reports a signal-terminated emulator (SIGSEGV)… | 1.00s | ~1ms |
| 5× simulator-server-blueprint tests | ~0.50s ea | ~0.001–0.02s ea |
| throws after retries when POST never responds | 0.60s | 0.14s |
| sleeps the step's delayMs | 0.31s | 0.04s |
No test exceeds ~0.7s anymore (the slowest,
`workspace-reader-integration`, is genuine subprocess I/O — `node
--version` etc. — left as-is on purpose; it is not a timeout).
## What changed
- **`boot-device.ts` (only production change):** the three hardcoded
poll cadences are extracted into a single immutable
`BOOT_POLL_INTERVALS_MS` constant (values unchanged: 1500/1000/500ms)
and referenced at the four `setTimeout` sites. Pure readability
extraction — **no mutable state, no exported test hooks, no runtime
behavior change.**
- **Boot-device tests:** the timing-sensitive tests now use vitest fake
timers (`vi.useFakeTimers()` + `vi.advanceTimersByTimeAsync`).
`boot-device-hardening` / `-spawn-error` add the same partial
`vi.mock("../src/utils/adb")` already used by
`boot-device-hotboot.test.ts`, so pre-flight does no real filesystem I/O
and the timers drive deterministically.
- **`simulator-server-blueprint.test.ts`:** `signalReady` now emits
`stream_ready` then `api_ready` (matching the real streaming server's
ordering) so the blueprint resolves immediately instead of waiting out
the 500ms `STREAM_GRACE_MS` fallback. Adds a dedicated fake-timer test
that explicitly covers the non-streaming grace path (coverage gained,
not lost).
- **`fetch-timeout.test.ts` / `flow-tools.test.ts`:** smaller timing
knobs; same behaviors asserted.
## No regressions
- 1011/1011 tests pass (tool-server 656, argent-mcp 53, argent 6,
registry 64, tools-client 1, installer 231); `tsc --build` and Prettier
clean. The converted boot-device, simulator-server-blueprint, and
flow-tools tests are deterministic under fake timers (verified across
stress runs).
- No production behavior change; `simulator-server.ts` is byte-identical
to `main`; net +1 test.
- Heads-up: `argent-mcp/test/fetch-timeout.test.ts` has a
**pre-existing** macOS flake (~10–20% rate; `connect EADDRNOTAVAIL
127.0.0.1:<port> - Local (0.0.0.0:0)` — kernel source-port exhaustion
under rapid-fire loopback fetches). Stress-testing showed it on `main`'s
pristine file too (2/10 failures vs 1/10 on this branch), so this PR
does not introduce or worsen it. Fixing it (e.g., EADDRNOTAVAIL retry,
or moving the test off real loopback fetch) is worth a follow-up.
|
||
|
|
18cf0f12cd | chore(deps): bump zod to v4 (#265) | ||
|
|
8d17dc9b4b |
fix(init): stop cramming homedir into mcp config (#257)
`argent init` was writing `/Users/<username>/.argent/mcp-calls.log` into
every generated MCP config (Cursor, Claude Code, VS Code, Windsurf, Zed,
Gemini, Codex, Hermes, opencode). Those files get committed and shared,
so collaborators ended up with the original author's username pinned
into their environment.
Drop the env var from `buildMcpEntry` and have each adapter omit the
`env`/`environment` field when the entry carries no env vars. The MCP
server already falls back to `${homedir()}/.argent/mcp-calls.log` at
runtime, so logging keeps working for every user on their own machine.
Fixes #238
|
||
|
|
a96f1f1578 |
feat: refactor android describe to not rely on uiautomator (#237)
## Summary - `formatDescribeTree` was branching on `source === "uiautomator"` to pick nested rendering; the newer `android-devtools` source fell through to flat mode and lost every node below depth 1. Extends the predicate to cover both Android sources so the helper-service path renders with the same hierarchy as the legacy `uiautomator dump` path. - Bumps `packages/argent-private` to pick up the persistent Android describe service that emits `source: "android-devtools"`, plus its build-and-sign CI workflow. - Wires the Android helper APK + `manifest.json` into the published `@swmansion/argent` package via `bundle-tools.cjs`, and adds `pack:mcp:local` / `build:android-binaries` so a local tarball can be produced without going through `download:native-binaries`. - Minor: tightens a couple of comments in `android-helper-install.ts` and `format-tree.ts`. This is connected to argent-private PR: https://github.com/software-mansion/argent-private/pull/8 |
||
|
|
5b147e9c25 |
fix: profiler attribution on older React DevTools backends (#262)
## Summary - Resolve profiler display-name lookup across both `getDisplayNameForElementID` and legacy `getDisplayNameForFiberID` backends. - Add a fallback diagnostic (`displayNameApiAvailable` / `unattributed_reason`) so fully-unattributed captures are reported as an API-surface mismatch instead of a transient lifecycle race. - Extend multi-renderer script coverage for modern, legacy, mixed, and missing display-name accessor scenarios. |
||
|
|
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\`
|
||
|
|
69538e74a3 |
fix(ax-service): remove unnecessary AutomationEnabled pref write (#256)
## Summary - Removes the `ensureAutomationEnabled` function and all its call sites - The ax-service binary uses `AXRuntime`/`AXSpringBoardServer` private frameworks directly and does not need the `AutomationEnabled` preference on iOS 26.x simulators - Writing it pre-boot into the Accessibility plist actively breaks the AX subsystem by interfering with `cfprefsd` domain initialization, causing `describe` queries to hang ## Investigation Tested on erased sims across five device types (iPhone 17e, iPhone 17, iPhone Air, iPad mini, iPad Pro 11-inch) on iOS 26.5 (runtime `23F77`): 1. **No plist at all** → `describe` returns full hierarchy including SpringBoard-hosted TCC dialogs ✅ 2. **Empty plist** → works ✅ 3. **Plist with `AutomationEnabled=true`** → `describe` hangs indefinitely ❌ The pref was originally added in #98 (April 2026) likely cargo-culted from older iOS automation patterns. On iOS 26.x the `AXSpringBoardServer` MIG interface accepts connections from the ax-service binary without it. ## Test plan - [x] TypeScript clean - [x] All affected tests pass (simulator-server-blueprint, describe-missing-xcrun-424) - [x] E2E: erased sim → boot → launch Maps → `describe` returns TCC dialog with all buttons 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
09a18a65ef | bump to v0.8.0 (#245) | ||
|
|
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>
|
||
|
|
98ba15a737 |
fix(update): only refresh editors that actually have argent configured
`argent update` was calling detectAdapters() (which probes for editor dirs on disk) and writing MCP entries, allowlists, rules, and agents into every detected editor's config — including ones the user explicitly unchecked during `init`. The presence of `~/.gemini` (or any other editor dir) is not consent. Switch update to walk each adapter and probe the actual config file for an argent MCP entry. Adapters without one are skipped end-to-end: no MCP write, no allowlist, no rules/agents copy. Closes #195 |
||
|
|
7f1c78104b |
fix(describe): surface dialogs hosted by remote-view services (#240)
## Summary Bumps the \`argent-private\` submodule to pull in the ax-service daemon fix in [software-mansion/argent-private#9](https://github.com/software-mansion/argent-private/pull/9). The radon-lite side of this PR is just the submodule pointer — the actual source change is one if-statement in \`ax_service.m\`. \`describe\` returns nothing when a \`_UIRemoteViewController\`-hosted overlay is on top of the foreground app — most visibly, the \`StoreKitUISceneService\` Apple ID auth sheet from the reporter's case. Root cause: \`+[AXElement primaryApp]\` returns nil while the remote-view service is frontmost, so the daemon never reaches \`collectExplorerElements\`. The submodule PR adds a single fallback: when \`primaryApp\` is nil, look up the AXElement via \`+[AXElement applicationAtCoordinate:<screen-centre>]\` instead, which returns the *hosting* app's AXElement regardless of which process owns the frontmost view. AX bridges the XPC boundary, so the hosting app's \`explorerElements\` walk surfaces the remote-view's content in the same flat-leaf shape the daemon already emits. ## Why this PR vs. #234 I closed [#234](https://github.com/software-mansion/argent/pull/234) after empirical reproduction showed the TS-side \`systemElements\` fallback there had no effect on the reporter's case — the daemon's \`isSystemAppShowingAnAlert\` gate doesn't fire for XPC-hosted sheets either, so \`systemElements\` was empty too. The reachability gap was at the daemon layer (\`primaryApp\` selector returning nil), not the TS adapter. This PR addresses that root cause. ## Empirical verification Built the patched daemon + ran against the reporter's exact trigger on an erased iOS 26.5 sim (News → "Get News+" → "Get Started" → "Apple Account Sign in Requested" sheet): - **Old daemon** (md5 \`d5240e8…\`, currently shipped): raw describe socket returned \`{ "alertVisible": false, "screenFrame": {…} }\` — no \`elements\` field, agent has no labels. - **Patched daemon** (md5 \`75fdffcc…\`, dev build): 42 elements, all dialog content labelled: heading "Apple Account Sign in Requested", email field, "Forgot password?", **"Sign In" at normalized (0.500, 0.414)**, **"Cancel" at (0.500, 0.482)**, plus 37 keyboard keys. The agent can now tap by label. ## Risk - No wire-format change, no TS/adapter change. \`@argent/tool-server\` and the describe handler consume the same shape they did before. - Fallback gates on \`primaryApp == nil\`, so every working case stays on its existing path. - If \`applicationAtCoordinate:\` also returns nil at the centre, the daemon emits no \`elements\` — same as current behaviour. The existing native-devtools fallback in \`describeIos\` still kicks in afterwards. ## Open items before un-drafting - [ ] Merge https://github.com/software-mansion/argent-private/pull/9 (the source change lives there) - [ ] Publish a new \`argent-main\` build to \`software-mansion-labs/argent-private-releases\` so \`npm run download:native-binaries\` picks up the patched \`bin/ax-service\` - [ ] Re-verify \`argent run describe\` end-to-end against the same dialog with the released binary - [ ] One regression-style smoke test: an in-process dialog (e.g. Wallet location prompt) to confirm the existing \`primaryApp\` path still fires for the unchanged cases ## Notes - Daemon binary lives in \`packages/native-devtools-ios/bin/ax-service\` but is \`.gitignore\`d; the release pipeline supplies it. This PR cannot ship the new binary in-repo for the same reason. - I empirically introspected the AXRuntime surface inside the simulator before committing to this approach — \`+systemWideElement\` and \`+systemEventDispatchElement\` segfault from a non-UI process, so coordinate lookup is the practical option that doesn't require a daemon-process redesign. Fixes #207 |
||
|
|
2977d2ca77 |
fix(describe): bump argent-private with WKWebView describe fix (#157)
The native ax-service now recurses into `primaryApp.children` beyond the flat `explorerElements` pass, so WKWebView web content (labels from the WebContent process) is surfaced alongside the host app's VoiceOver targets. Details: https://github.com/software-mansion/argent-private/pull/7 |
||
|
|
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. |
||
|
|
fbe7b087e7 |
chore(deps): batch-apply Dependabot updates (#214–#227) (#228)
This PR consolidates every open Dependabot PR that requires **no source-code changes** into a single branch with one regenerated, coherent lockfile. | PR(s) | Update | Status | |---|---|---| | #214 | GitHub Actions group — checkout@v6, setup-node@v6, setup-python@v6, github-script@v9, upload-artifact@v7 | ✅ Applied | | #215 | prettier 3.8.3, @clack/prompts 1.4.0, semver 7.8.0, yaml 2.9.0, @modelcontextprotocol/sdk 1.29.0, esbuild 0.28.0 | ✅ Applied | | #216 / #221 / #224 / #227 | typescript 6.0.3 | ✅ Applied | | #217 / #222 / #223 | @types/node 25.9.0 | ✅ Applied | | #220 / #226 | vitest 4.1.6 | ✅ Applied | | #218 / #225 | zod 4 | ⏸️ Excluded — requires code migration | | #219 | express 5 | ⏸️ Excluded — requires code migration | | #215 (partial) | tree-sitter 0.25.0 | ⏸️ Excluded — see below | **tree-sitter 0.25.0** ships no prebuilt binaries and its `binding.gyp` hard-codes `-std=c++17`, which fails to compile against Node 20+/24 C++20 `cppgc` headers — a hard `npm install` blocker (would also break CI). Kept at `^0.21.1`; all other #215 updates applied. zod 4 and express 5 are excluded here because they require real source changes; tracked separately. Verified: `tsc --build` clean, esbuild bundles, **1010 unit tests pass**, **zero source files modified**. |
||
|
|
896e775e01 |
Fetch argent skills from GitHub for portable lockfiles (#230)
### Description Install and sync argent skills from a GitHub-pinned source (`software-mansion/argent/packages/skills/skills#v<version>`) instead of the local bundle when possible, so `skills-lock.json` stays portable across machines. Falls back to the bundled skills directory when offline or the version is unknown. ### Fixes #208 |
||
|
|
95d2ffa0e4 |
fix(skills): remove iOS bias from skill/tool docs (#212)
We've had multiple instances of iOS-biased references in our skills. This PR makes these references platform-agnostic. <details> ## Summary After Android emulator support landed (#148), several skill docs and tool descriptions still treated iOS as the only/default platform. A QA reported that asking the agent to "test … using argent" makes it spin up an iOS simulator even when an Android emulator is already running. These are **minimal de-biasing edits** — mostly one-word `simulator` → `device` swaps. Android is **not** promoted above iOS; the incidental iOS bias is just removed where it was unnecessary. Genuinely iOS-only tools (`paste`, `native-*`, `native-profiler-*`) are left untouched since the capability gate already rejects them on Android. ## Changes (14 files, +43/-29) - **`argent.md` rule**: one-line `PLATFORM DETECTION` note (call `list-devices` first, don't default to iOS); `simulator actions` → `device actions`; mention `adb` next to `xcrun` in the escape-hatch bullet. Routing order unchanged (iOS still listed first). - **Skill docs**: `native iOS app navigation` → `app navigation`; `simulator` → `device` where the action is platform-neutral; added the missing `argent-android-emulator-setup` row to the RN-workflow Related Skills table; `argent-ios-simulator-setup` description scoped to iOS. - **`flow-run` / `flow-start-recording`**: `simulator interactions` → `device interactions`. - **`network-logs` / `network-request`**: `device_id` description `iOS Simulator UDID` → `Device UDID`. - **`list-devices`**: merged result now sorts by readiness so a booted Android emulator is not buried beneath idle iOS simulators (the only logic change). ## Test plan - [x] `npx tsc --build` clean - [x] `@argent/tool-server` 478 tests pass; `@argent/mcp` 51; `@argent/installer` 231; `@argent/registry` 57 - [x] `prettier --check` clean - [ ] Sanity-check that an agent with only an Android emulator booted no longer reaches for an iOS simulator </details> |
||
|
|
94fa867818 |
feat(describe): render tree as text to cut token cost (#197)
## Summary
- Move the JSON tree out of the describe tool's response and add an
in-between layer (`format-tree.ts`) that converts it to a sectioned text
rendering. On a typical iOS screen the new `description` field is ~6×
cheaper on the wire than the previous nested tree once braces, keys, and
indentation are counted.
- Public response shape becomes `{ description, source, should_restart?
}`. All existing pruning (Android v2 trimmer in `uiautomator-parser.ts`,
iOS frame clamp in `ios-ax-adapter.ts`) is untouched — the new layer
only re-presents what the platform adapters already produced.
- Header now spells out the coordinate space and the tap-point formula
so an agent can act on a frame without further docs.
## How it renders
Two modes picked automatically from the tree shape:
- **flat** (iOS ax-service): bucket children by `frame.y` into Top bar /
Body / Composer / Bottom; split the bottom zone into keyboard rows by
rounded y and classify each row (letters / shift / return /
globe-dictate).
- **nested** (Android uiautomator, iOS native-devtools fallback):
iterative DFS with depth indentation, preserving `clickable` /
`scrollable` / `scrollHidden` / `disabled` / `password` flags inline.
Example (Messages with keyboard up):
```
Source: ax-service
Mode: flat
Coordinates are normalized [0,1] fractions of the screen (x, y, width, height), not pixels — pass them straight to gesture-tap / gesture-swipe / gesture-pinch, which expect this same space. To tap an element, use its centre: tap_x = frame.x + frame.width / 2, tap_y = frame.y + frame.height / 2.
ROOT AXGroup (0.000, 0.000, 1.000, 1.000)
— Top bar —
AXButton "Messages" (0.040, 0.071, 0.109, 0.050)
AXButton "+1 (888) 555-1212" (0.267, 0.134, 0.466, 0.037)
— Body / content —
AXStaticText "iMessage  Encrypted" (0.040, 0.156, 0.920, 0.030)
...
— Bottom row 1: letters —
AXImage "Q" (0.012, 0.675, 0.098, 0.062)
...
```
## Files
- `packages/tool-server/src/tools/describe/contract.ts` — split into
`DescribeTreeData` (internal) and `DescribeResult` (public)
- `packages/tool-server/src/tools/describe/index.ts` —
`withDescription(data)` wrapper applied to both platform handlers
- `packages/tool-server/src/tools/describe/format-tree.ts` — the
renderer (new)
-
`packages/tool-server/src/tools/describe/platforms/{ios,android}/index.ts`
— return `DescribeTreeData`
- `packages/tool-server/test/describe-tool.test.ts` — assertions
migrated to `result.description` / `result.source` /
`result.should_restart`
- `packages/tool-server/test/describe-format-tree.test.ts` — renderer
unit tests (new): flat zoning, nested indentation + flags,
embedded-newline escaping, empty tree
## Test plan
- [x] `npm test` in `packages/tool-server` — 49 files / 482 tests
passing locally
- [x] `tsc --build` at workspace root — clean
- [x] Live MCP sanity check: `argent run describe --udid <booted>
--json` returns the new shape; tested against Messages (full keyboard
surface), Safari (chrome-only, expected), and a minimal in-process
WKWebView host. Existing pruning behaviour unchanged in every case.
|
||
|
|
a0a43522c8 |
docs(skills): device selection guidance for list-devices and RN runs (#203)
## Summary This change improves how Argent skills tell agents to pick and use devices before booting, running Metro, or driving UI. It reduces wrong-platform assumptions and encourages explicit targets derived from `list-devices`. ## Fixes #196 ## What changed - **`packages/skills/rules/argent.md`**: Added a `<device_selection_rule>` block that instructs agents to call `list-devices` first, then follow a clear decision order: explicit user intent → prefer already-running devices → ask when both iOS and Android are ready with no intent → boot or ask for cross-platform vs single-platform projects using environment inspector signals. - **`argent-react-native-app-workflow/SKILL.md`**: RN run commands now require an explicit simulator name or Android `deviceId` from `list-devices`; minor checklist wording tweak for booting devices. - **`argent-test-ui-flow/SKILL.md`**: Clarifies resolving the test device via `list-devices` and `<device_selection_rule>` before per-platform setup. ## Motivation Agents sometimes assumed a platform or picked an arbitrary device. Aligning skills with `list-devices`-first behavior makes device choice observable and consistent with multi-platform workflows. |
||
|
|
12f6ff1115 | chore: bump to 0.7.1 | ||
|
|
8a3b5a1ff7 |
fix(react-profiler): self-bootstrap DevTools backend when no client is attached (#205)
## Summary `react-profiler-start` failed in any RN bridgeless dev build with no external React DevTools client (Fusebox React tab / `npx react-devtools`) connected — returning the misleading error *"No React renderer interface attached yet. Wait for the app to render its first commit and retry."* even when the app had rendered. This PR makes the tool self-bootstrap the DevTools backend so profiling works out of the box, and replaces the one-size-fits-all error with mode-specific actionable messages. Companion to #199 (schema fix). With both landed, `react-profiler-start` is end-to-end usable from MCP clients against a vanilla RN 0.76 dev app with zero extra setup. > [!IMPORTANT] > The bootstrap path mutates global runtime state (Bridge + Agent + hook subscriptions via `connectWithCustomMessagingProtocol`). Unit tests cover every reason branch, but the live behavior — especially coexistence with a later-attached `npx react-devtools` client and the absence of subscription leaks across repeated start/stop cycles — must be **smoke-tested against a working RN application** before this merges. ## Root cause argent's profiler delegates React commit capture to the in-app DevTools backend via `rendererInterface.startProfiling(...)`. The two maps on `__REACT_DEVTOOLS_GLOBAL_HOOK__` look similar but are populated by different actors: | Map | Populated by | When | |---|---|---| | `hook.renderers` | React itself via `hook.inject(renderer)` | First render | | `hook.rendererInterfaces` | DevTools backend's `attach(hook, id, renderer, global)` (in `react-devtools-shared/src/backend/renderer.js`) | When `initBackend(hook, agent, global)` runs | In an RN 0.76 bridgeless dev build, `setUpReactDevTools.js` runs at bundle load and calls `react-devtools-core.connectToDevTools({ host: 'localhost', port: 8097, websocket })`. That opens a WebSocket; `initBackend` only runs on `ws.onopen`. With no DevTools client listening on 8097, the connection never opens, `initBackend` never runs, `rendererInterfaces` stays empty — but `hook.renderers` is populated because React doesn't care about the backend. So `rendererInterfaces.size === 0` while `renderers.size === 2` is the steady-state of a dev build with no DevTools client. argent's pre-flight check saw the empty map and threw with a misleading message ("wait for first commit") that conflated three distinct failure modes: 1. App hasn't rendered yet (legitimate wait). 2. App rendered but no DevTools backend attached (this PR — the common case). 3. Production build / no `react-devtools-core` in bundle (different recovery). Direct CDP verification of the hook from the bug state: ```js { rIds: [1, 2], riIds: [], reactDevtoolsAgent: undefined } ``` ## Fix When the pre-flight detects `rendererInterfaceFound: false` with the hook present, `react-profiler-start` now runs a self-bootstrap script (`BOOTSTRAP_DEVTOOLS_BACKEND_SCRIPT`) that: 1. **Looks up `react-devtools-core` in the Metro module registry** by scanning `__r.getModules()` entries for a `verboseName` matching `react-devtools-core/dist/backend`. Handles both `Map<id, meta>` (modern Metro) and array-of-meta (older Metro) shapes — mirrors the pattern in `debugger-component-tree`. 2. **Calls `connectWithCustomMessagingProtocol`** (rdt-core ≥5.1) with no-op `onSubscribe` / `onUnsubscribe` / `onMessage` handlers. The function internally constructs a `Bridge` + `Agent` and runs `initBackend(hook, agent, window)`, which iterates `hook.renderers` and calls `attach()` on each — exactly the call path that populates `rendererInterfaces`. The fake wall discards every outbound message, which is fine because argent calls into the rendererInterface directly and never reads from the bridge. 3. **Returns one of seven distinct reasons**, each mapping (via `bootstrapFailureMessage` in `utils/react-profiler/devtools-bootstrap.ts`) to its own actionable error message: - `no-hook` → "production build" guidance - `no-renderers` → "wait for first commit" - `no-metro-modules` → bundle isn't Metro-style - `no-rdt-module` → react-devtools-core stripped from bundle (production) - `unsupported-rdt-version` → rdt-core <5.1 (RN <0.74) → fall back to `npx react-devtools` - `metro-scan-error` / `bootstrap-threw` / `bootstrap-no-effect` → unexpected, with verbatim error text After a successful bootstrap, `REACT_NATIVE_PROFILER_SETUP_SCRIPT` is **re-run** to install argent's `__argent_startWrapped__` wrappers on the freshly-attached interfaces. Without this re-run, `buildStartScript`'s post-start `__argent_isProfiling__` check would falsely report `startProfiling-threw` (the wrapper sets the flag; an unwrapped interface leaves it false even on a successful start). ### Idempotency / leak protection `initBackend` adds subscriptions to `hook.sub('renderer', ...)` on every call, so naive re-invocation would leak listeners. The script short-circuits with `reason: 'already-attached'` when `rendererInterfaces.size > 0`, so repeated `react-profiler-start` calls within a session never re-bootstrap. Verified empirically: listener counts stay flat across multiple start/stop cycles. ### Coexistence with external DevTools If `npx react-devtools` is later started while argent has already bootstrapped, RN's bundled reconnect path calls `connectToDevTools` again, which goes through `attachRenderer` → sees `rendererInterface != null` (argent's already there) → re-emits `renderer-attached` without re-attaching. Both backends coexist; argent talks to the renderer interface directly, so the active "agent" is irrelevant to profiling. ## Files changed - `packages/tool-server/src/utils/react-profiler/scripts.ts` — adds `BOOTSTRAP_DEVTOOLS_BACKEND_SCRIPT` (the injected IIFE that does the Metro scan + connect call). - `packages/tool-server/src/utils/react-profiler/devtools-bootstrap.ts` *(new)* — `BootstrapResult` type, `BootstrapReason` union, and the `bootstrapFailureMessage()` helper that maps each reason to its agent-facing error. Mirrors the `session-ownership.ts` convention (types + pure helpers as a sibling of `scripts.ts`, unit-testable without CDP / vitest mocking). - `packages/tool-server/src/tools/profiler/react/react-profiler-start.ts` — calls bootstrap on empty `rendererInterfaces`, re-runs setup, throws via the imported `bootstrapFailureMessage`. Also replaces two pre-existing error strings ("hook not present", `how_to_reclaim`) with the same agent-friendly phrasing used elsewhere in this PR. - `packages/tool-server/test/react-profiler/bootstrap-devtools-backend.test.ts` *(new)* — 12 unit tests covering every reason branch (Map + array Metro registries, require() throws, getModules throws, connect throws, connect is a no-op, idempotent already-attached path). ## Manual verification (iPhone Air, RN 0.76.7 bridgeless, no external DevTools) 1. Reload bundle → confirm `rIds: [1,2], riIds: []` (bug state). 2. Pre-fix: `react-profiler-start` returns *"No React renderer interface attached yet…"* 3. Post-fix: `react-profiler-start` returns `{ started_at, hermes_version: "for RN 0.76.7", detected_architecture: "bridgeless" }`. Hook now shows `riIds: [1,2]`, `__argent_startWrapped__: true` on each, `reactDevtoolsAgent` set, `__argent_isProfiling__: true`. 4. Forced a React state-hook dispatch during the session and stopped: `total_react_commits: 2`, `unattributed_ms: 8.41` — real commit data captured through the bootstrapped backend. 5. Second `react-profiler-start` in same session returns success via `already-attached` early-exit (verified directly against the script). Hook listener counts (`renderer`, `renderer-attached`, `react-devtools`) stable across multiple cycles — no subscription leak. 6. Simulated `no-renderers` by clearing `h.renderers` → got *"React has not rendered yet, so there is nothing to profile. Ask the user to wait until the app shows React content…"* |
||
|
|
74481155af |
fix: remove hardcoded model: haiku from argent-environment-inspector skill (#206)
Removes the `model: haiku` frontmatter field from `packages/skills/agents/argent-environment-inspector.md`. The hardcoded model caused non-Anthropic coding harnesses (e.g. Gemini) to fail when loading the skill, since `haiku` is an Anthropic-specific model identifier that those runtimes don't recognize. Dropping the field lets each harness use its own default model. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kacperkapusciak <39658211+kacperkapusciak@users.noreply.github.com> |
||
|
|
bea43f555a |
feat: per-step delayMs on flow steps (#198)
- Tool steps in a flow YAML may carry an optional `delayMs` that sleeps that long *before* the step runs; `flow-add-step` exposes the same field. - New `longRunning?: boolean` field on `ToolDefinition`; when set, the MCP fetch wrapper bypasses its 30s per-request timeout (`fetchTimeoutMs: null`) so long invocations aren't aborted. `run-sequence` and `flow-execute` opt in. |
||
|
|
8ce8f8e573 |
fix: 100% CPU usage on broken stdio (#191)
One of our users reported occasional 100% CPU usage stale MCP server process. I wasn't able to reproduce this bug directly in Codex. We only have non-codex repros that are able to consistantly reproduce a bug that *looks* like the one reported to us. The bug in question is: Our error handler causes errors which throws it into an infinite loop. <img width="713" height="251" alt="image" src="https://github.com/user-attachments/assets/4b9fad41-55d0-49ab-b19c-25b7f5c12d12" /> ### More details: When `argent mcp` runs as an orphaned process whose stderr pipe is broken (e.g. parent died), every `stderr.write` from the uncaughtException handler emits an async 'error' event. Without an 'error' listener that becomes another uncaughtException, runs the handler again, and so on forever. Move the handlers into `fatal-handlers.ts` and add three guards: - 'error' listeners on stdout/stderr exit cleanly when stdio is broken, before the failure becomes another uncaughtException - try/catch around `stderr.write` so a sync write failure exits instead of escaping - try/catch around the formatter so a throwing `.stack` getter or `toString` (the production trace pointed at defaultPrepareStackTrace) can't take down the handler Idempotent: a second `installFatalHandlers` call is a no-op. |
||
|
|
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> |
||
|
|
85e7aa9612 |
fix(registry): emit JSON Schema type for ZodDefault fields (#199)
## Summary
`zodTypeToJsonSchema` in `packages/registry/src/zod-to-json-schema.ts`
did not handle `z.ZodDefault`. Any tool input field declared as
`z.X().default(...)` fell through to `{}`, dropping the type.
Downstream, the MCP harness sent values for those fields as strings, and
the tool-server's strict Zod validators rejected them with `Expected
number, received string` / `Expected boolean, received string`.
Also fixes a related issue in the same file: `isOptional` returned
`false` for `ZodDefault`, so defaulted fields were pushed into
`required` even though the caller doesn't have to provide them.
Tools that misbehave in practice today because of this and start working
after this fix:
- `stop-metro` — `port`
- `react-profiler-start` — `port`, `force`, `sample_interval_us`
- `debugger-component-tree` — `onScreenOnly`, `includeSkipped`
(Reproduced against the published `argent 0.6.1` MCP server while
profiling an Expo app — the `argent run <tool> --args '<json>'` CLI path
is unaffected because it doesn't go through the published JSON Schema.)
## Changes
- `packages/registry/src/zod-to-json-schema.ts`: handle `ZodDefault`
(recurse into `_def.innerType`, attach `default: defaultValue()`); treat
`ZodDefault` as optional.
- `packages/registry/tests/zod-to-json-schema.test.ts`: new file. Covers
`z.number()`, `z.boolean()`, `z.string().optional()`,
`z.number().default(...)`, `z.boolean().default(...)`,
`z.coerce.number().int().positive().default(...)`, and a mixed-object
happy path.
## Test plan
- [x] `npx vitest run` in `packages/registry` — 64 passed, 0 failed (4
files; the new test file contributes 7 cases).
- [ ] Reviewer sanity check: load the rebuilt MCP server and confirm a
tool with a `.default(...)` numeric/boolean field now publishes the
correct \`type\` in its input schema.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
60ae7cb767 | chore: bump versions to 0.7.0 | ||
|
|
ad502a46ed | chore: bump argent-private submodule | ||
|
|
d017ba340c |
feat: Android emulator support (#148)
### For reviewers - what to focus on: The diff is ~6k LoC; this breakdown should help triage where to spend attention: | Bucket | Lines | Effort | | --- | --- | --- | | **Net-new Android logic**: `boot-device.ts` (660) + `adb.ts` (399) + `uiautomator-parser.ts` (292) | 1351 | **Read carefully** | | Blueprint `DeviceInfo` refactor: `simulator-server`, `ax-service`, `native-devtools` | 365 | Read | | Tool `index.ts` / `types.ts` sprawl across ~20 tools (capability gates, `services()`, descriptions) | ~965 | Skim — watch for cross-file consistency | | `platforms/android.ts` impls (stub → real implementation) | ~426 | Skim | | Skill/docs prose, profiler rename ripple | ~1000 | Skim | | **`platforms/ios.ts` deletions** — 12 files, 0 additions; logic moved into unified handlers | 684 | **Skip — mechanical** | ## What works, what doesn't: ### Just works thanks to either sim server or metro: - `gesture-*` - `button`, `keyboard` - `screenshot` - `run-sequence`, `flow-*` - `debugger-*` - `react-profiler-*` ### Adjusted to work on android too: | tool | android | ios | | --- | --- | --- | | `describe` | `uiautomator dump` → `DescribeNode` | `AXServiceApi` | | `launch-app` | `cmd package resolve-activity` + `am start -W` | `simctl launch` | | `restart-app` | `cmd package resolve-activity` + `am force-stop` + `am start -W` | `simctl terminate` + `simctl launch` | | `reinstall-app` | `adb install -r` (+`-g`/`-d`) | `simctl uninstall` + `simctl install` | | `open-url` | `am start -a VIEW -d <url>` | `simctl openurl` | | `list-devices` | `adb devices` + `emulator -list-avds` | `xcrun simctl list --json` | | `boot-device` | snapshot probe → `-force-snapshot-load` hot boot with screencap-alive guard, falls back to cold boot on any failure | `simctl boot` + `bootstatus -b` | ### Will not work on android for now: - `native-devtools-*` (7 total) - `native-profiler-*` (3 total — was `ios-profiler-*`) - `profiler-stack-query`, `profiler-combined-report` (depend on `native-profiler-*`) - `paste` — unregistered on both platforms for now. --------- Co-authored-by: Paweł Fornagiel <pawel.fornagiel2@gmail.com> |
||
|
|
626c3274e2 |
chore: bump package versions to 0.6.1 (#193)
## Summary - Bumps all 9 published packages from `0.6.0` → `0.6.1`. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. - `packages/ui` (`0.0.1`) is on a separate version track and left as-is. - Lockfile updated via `npm install`; `npm run format` was a no-op. ## Test plan - [ ] CI green - [ ] Run the stable publish workflow against this commit and confirm publish succeeds |
||
|
|
9cd453b857 |
fix(skills): prefix bare skill references with 'argent-' (#183)
All skills are currently exposed as `argent-***`. In the markdown though, we refer to them as just `***`. This may confuse the agent when it tries reading `skills/***` instead of `skills/argent-***`. This PR changes all skill references in docs to the correct `argent-***` form. ## Why? There's no downside to this fix. Keeping it as-is potentially introduces some confusion especially if when the user is running a weaker model or if we encounter namespace conflicts. |
||
|
|
529fffb322 |
fix installer update version detection under npx (#188)
## Summary - fix `argent update` version detection when invoked via `npx @swmansion/argent`, so it compares against the actually installed global binary instead of the transient npx-cached package - add `getGloballyInstalledVersion()` that resolves the real global binary path (`which -a` / `where`), follows symlinks, and reads the owning `package.json` - add regression tests covering staged global-install layouts and npx-style transient paths to prevent false "already on latest" results |
||
|
|
55d1567d48 |
feat: improve iOS profiler startup, shutdown, and exit recovery (#181)
## Summary Refactor of the iOS profiler (`xctrace`-driven) session to make start, stop, and unexpected-exit handling proper, without the iOS hanging infinitely. ### Startup (`ios-profiler-start`) - **Locale ready signal**: subscribe-before-spawn to a Darwin notification (`com.argent.ios-profiler.started.<pid>.<ts>`) via `notifyutil`, passed through `xctrace --notify-tracing-started`. Falls back to the existing stdout substring match (`Starting recording` / `Ctrl-C to stop`) if `notifyutil` fails to spawn or register within 2 s. - **Startup timeout**: new 10 s cap on reaching the ready state — on timeout, `SIGKILL` the child so it cannot leak. - **Bounded `simctl` calls**: `detectRunningApp` now applies a 10 s timeout to both `xcrun simctl spawn ... launchctl list` and `xcrun simctl listapps`, with explicit error messages telling the user to verify the simulator is booted. - **Cold-start retry**: if xctrace's process resolver fails with `"Cannot find process matching name:"` (launchd has registered the bundle but xctrace's resolver hasn't caught up), the tool retries once after a 1.2 s backoff — up to 2 attempts total. On exhausted retries throws a classified error naming the cold-launch race and pointing at `app_process` as the override. Non-cold-start failures propagate immediately without retry. - **Exit listener installed**: every `xctrace record` child gets a long-lived `'exit'` handler (`handleXctraceExit`) that marks the session as `recordingExitedUnexpectedly` and stores `lastExitInfo`. ### Shutdown (`ios-profiler-stop`) - **SIGINT → SIGTERM → SIGKILL ladder** via `utils/ios-profiler/lifecycle.ts::shutdownChild` (30 s grace, 5 s term, 5 s kill). - **Returns a `warning`** when the ladder had to escalate past SIGINT, so callers know the trace bundle may be incomplete. ### Exit recovery - **New `recordingTimedOut` / `recordingExitedUnexpectedly` flags** on the session API. If `ios-profiler-stop` is called after either fired, the tool now exports whatever `.trace` bundle is on disk and returns it with a descriptive `warning` instead of throwing "no active session". - Covers the in-process 10-min recording cap, attached-app crashes, and simulator daemon hiccups. ### Dispose semantics - Registry teardown (process shutdown) skips SIGINT and goes straight to `SIGKILL` with a 1 s reap window. The explicit `ios-profiler-stop` contract is the only path that takes the slow finalise grace. ### Other - `utils/ios-profiler/run-with-timeout.ts`: `execSyncWithTimeout` wrapper with a 60 s default — guards every shelled-out `xctrace export` / `xctrace version`. - New reference doc `utils/ios-profiler/IOS_PROFILER_REFERENCE.md` covering the native foundations, capture flow, and pipeline. ## Tests New unit tests under `packages/tool-server/test/ios-instruments/` --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
dd702fc720 |
fix: detect missing global install in argent update (#180)
## Summary `npx @swmansion/argent update` falsely reported *"Already on the latest version"* after the user had removed the package via `npx @swmansion/argent uninstall`. `getInstalledVersion()` reads `package.json` from `PACKAGE_ROOT`, which under `npx` is the npx-cached copy of argent — always the registry's latest. The version compare then matches and short-circuits, hiding the fact that there is no global install at all. - Share `isGloballyInstalled()` between `init` and `update` via `utils.ts`; `update.ts` now routes through a fresh-install prompt (`Install @swmansion/argent@<latest> globally?`) when no permanent argent binary is on PATH. - Fix a Windows-only bug in the temp-runner check while the helper was being moved: `where argent` returns multiple lines, and the previous logic flagged the entire output as transient if *any* line contained an npx marker. Now each line from `where` / `which -a` is inspected independently, so a real global install is not masked by a concurrent npx invocation. - Add `isTempRunnerPath()` plus 5 unit tests covering npx, pnpm dlx, bunx, Windows dlx, and real global paths. ## Test plan - [x] `npm run build` (argent-installer) — passes - [x] `npm test` (argent-installer) — 231 passing (5 new) - [x] `npx prettier --write` on touched files — no diffs - [ ] Manual: `npm install -g @swmansion/argent` → `npx @swmansion/argent uninstall` → `npx @swmansion/argent update` should now offer to install the latest version instead of reporting "already on the latest" - [ ] Manual: `argent update` while globally installed and on latest still reports "already on the latest version" - [ ] Manual: `argent update` while globally installed and on an older version still prompts to update |
||
|
|
bb3f484d04 |
chore: bump package versions to 0.6.0 (#178)
## Summary - Bumps all 9 published packages from `0.6.0-next.2` → `0.6.0` (stable) in preparation for the `0.6.0` release. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. `packages/ui` (`0.0.1`) is on a separate version track and left as-is. - Lockfile updated via `npm install`; `npm run format` was a no-op. ## Test plan - [ ] CI green - [ ] Run the stable publish workflow against this commit and confirm publish succeeds |
||
|
|
f004c08a19 |
chore: bump package versions to 0.6.0-next.2 (#177)
## Summary - Bumps all 9 published packages from `0.6.0-next.1` → `0.6.0-next.2` in preparation for the next prerelease publish. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. `packages/ui` (`0.0.1`) is on a separate version track and left as-is. - Lockfile updated via `npm install`; `npm run format` was a no-op. ## Test plan - [ ] CI green - [ ] Run `Publish prerelease to npm (next)` workflow against this commit and confirm publish succeeds |
||
|
|
127a32a101 |
fix: installer throwing error (#175)
This PR fixes regression introduced in #162: ``` ◇ Add Argent tools to editor auto-approve lists? - recommended │ Yes node:internal/modules/cjs/loader:1459 throw err; ^ Error: Cannot find module '/Users/ignacylatka/.npm/_npx/9bf42f550c32750c/node_modules/@swmansion/argent/tool-server.cjs' at Module._resolveFilename (node:internal/modules/cjs/loader:1456:15) at defaultResolveImpl (node:internal/modules/cjs/loader:1066:19) at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1071:22) at Module._load (node:internal/modules/cjs/loader:1242:25) at wrapModuleLoad (node:internal/modules/cjs/loader:255:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 { code: 'MODULE_NOT_FOUND', requireStack: [] } Node.js v24.14.1 ``` ### Root cause `packages/argent-installer/src/mcp-configs.ts:22` resolves the bundled tool-server with: ```ts path.join(import.meta.dirname, "..", "tool-server.cjs") ``` That was correct in the pre-#162 layout where the file lived at `packages/mcp/dist/cli/mcp-configs.js` and `..` stepped from `dist/cli/` to `dist/`. After the refactor the file is inlined into `packages/argent/dist/installer.mjs` — a direct **sibling** of `dist/tool-server.cjs` — so `..` now climbs one level too far and resolves to `<pkg>/tool-server.cjs`. The codex adapter's `addAllowlist`/`removeAllowlist` calls `getAvailableToolIds()` → `execFileSync("node", [TOOL_SERVER_BUNDLE, "-t"])`, which is where the broken path is dereferenced. ### Fix Drop the `..`. Existing `packages/argent/src/bundled-paths.ts:7` already uses the sibling form, so this aligns the two. |