mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
fix/describe-system-elements-fallback
366 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
13cbc7e4c8 | chore: apply prettier formatting | ||
|
|
20fb8ba0ee |
fix(describe): fall back to systemElements when primaryApp walk is empty
The ax-service daemon already collects two AX trees: `elements` (foreground app via `[AXElement primaryApp]`) and `systemElements` (Springboard via `[AXElement systemApplication]`, gated on `isSystemAppShowingAnAlert`). The TS blueprint silently dropped the second one, so any dialog presented outside the foreground app's process tree was invisible to `describe`. Wire `systemElements` through the typed response and let the iOS adapter use it as a fallback when the `primaryApp` walk produced zero usable children. In-process dialogs (UIAlertController, Settings sheets) already land in `elements`, so the fallback never fires for them and we avoid duplicating the same buttons. Status-bar leakage from the fallback path is accepted as cheap and rare. |
||
|
|
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. |
||
|
|
b75cb3bb42 |
chore: add Dependabot config (npm workspaces + GitHub Actions) (#213)
## Summary - Adds `.github/dependabot.yml` to enable weekly version + security updates. - Covers the **npm workspaces monorepo** (`/` + `/packages/*`) and **GitHub Actions** used in `.github/workflows`. - Groups updates to keep PR noise low: - `npm-minor-patch` — one PR/week bundling all minor/patch bumps across the workspace. - `npm-major` — separate grouped PR for majors (typically needs human review). - `npm-security` — grouped security updates across all directories. - `github-actions` — one PR/week for all Actions bumps. - Uses `directories:` (plural) with the `/packages/*` glob so every workspace package is monitored; majors stay separate from minor/patch so reviewers can triage breaking changes without blocking routine bumps. - `gitsubmodule` ecosystem was intentionally **not** enabled — `packages/argent-private` is an internal submodule maintained by the same org, so SHA-bump PRs would be noise rather than signal. ## Notes - Schedule: weekly, Monday 06:00 UTC. - Commit prefixes follow the existing `chore(...)` convention used in this repo (`chore(deps)`, `chore(deps-dev)`, `chore(ci)`). - `open-pull-requests-limit` set to 10 (npm) / 5 (actions) to bound the queue. ## Test plan - [ ] Merge to `main`; confirm Dependabot picks up the file (visible under repo *Insights → Dependency graph → Dependabot*). - [ ] First scheduled run on the following Monday produces grouped PRs (or trigger immediately via *Check for updates* in the Dependabot tab). - [ ] Verify a sample PR's diff touches the expected workspace `package.json` + root `package-lock.json`. |
||
|
|
cc9d5d035b |
Add Dependabot configuration file
Configure Dependabot for version updates with weekly schedule. |
||
|
|
1bfe516fbd |
fix(deps): resolve dependabot alerts (#211)
Manual dependabot fixes. ## Details <details> **Lockfile-only.** `package.json` is unchanged; all bumps are within parents' already-declared semver ranges (API-compatible by semver contract). Refreshing the stale lock to the in-range patched versions: - `fast-uri` 3.1.0 → **3.1.2** — *shipped* - `path-to-regexp` 0.1.12 → **0.1.13** — *shipped* (upstream fix = two `backtrack = ''` resets) - `hono` 4.12.3 → 4.12.19, `@hono/node-server` 1.19.9 → 1.19.14, `express-rate-limit` 8.2.1 → 8.5.2, `router`'s `path-to-regexp` 8.3.0 → 8.4.2 — *not shipped* (SDK HTTP/SSE transport; argent uses stdio only → tree-shaken out of bundles). Same low-risk stale-lock class, bumped in the same pass. - `ip-address` 10.0.1 → 10.2.0 — benign in-range cascade from `express-rate-limit@8.5.2`'s own manifest. `npm audit` HIGH for shipped/runtime deps: **7 → 0**. ## Scope In scope (the QA finding): the two shipped HIGH advisories — fixed and proven present in freshly-built bundles. Out of scope (deliberately): the remaining ~7 `npm audit` entries are **exclusively the dev-only vitest toolchain** (`vite`, `vite-node`, `esbuild`, `postcss`, `picomatch`, `@vitest/mocker`, `vitest`). Not shipped, not runtime. Their fix requires a `vitest` **semver-major** — a separate, breaking change. ## Verification - `tsc --build` clean; **998 tests pass** across all 7 workspaces; `prettier --check .` clean. - Bundles rebuilt from the patched tree; MCP stdio + tool-server E2E confirmed working. - 4 independent reviews (correctness / scope / edge-cases / ripple): all PASS. Edge-case review ran an **offline differential** of every tool-server express route under 0.1.12 vs 0.1.13 (byte-identical `RegExp` source + keys) and a **live E2E** on the running patched tool-server — route matching is unchanged. `fast-uri` 3.1.2 only rejects more malformed URIs; `ajv` uses it solely for schema `$id`/`$ref`, never runtime data. - `npm ci` is byte-stable against this lockfile (does not regenerate it); `npm pack --dry-run -w @swmansion/argent` passes. ## Residual `npm audit` 7 vulns remain (2 high, 5 moderate) — all dev-only vitest toolchain, tracked separately (needs `vitest` major). </details> |
||
|
|
12f6ff1115 | chore: bump to 0.7.1 v0.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 v0.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 succeedsv0.6.1 |
||
|
|
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 |
||
|
|
2bdc8ca323 |
chore: bump package-lock.json (#187)
This PR fixes stale `package-lock.json` on main. It's been adding noise to my other PRs so i'm fixing it in this one. ### Why? #158 forgot to regenerate `package-lock.json` |
||
|
|
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 succeedsv0.6.0 |
||
|
|
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 succeedsv0.6.0-next.2 |
||
|
|
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. |
||
|
|
4d826ca01c |
chore: update header image in README.md (#176)
Due to popular vote here's an Argent branded banner in the readme <img width="993" height="597" alt="image" src="https://github.com/user-attachments/assets/30b62430-3888-4e50-a3c7-eb84fef6cad8" /> |
||
|
|
1f1f55f72e |
chore: bump package versions to 0.6.0-next.1 (#174)
## Summary - Bumps all 9 published packages from `0.6.0-next.0` → `0.6.0-next.1` in preparation for the next prerelease publish (the `0.6.0-next.0` train was blocked by the build-config and update-checker fixes in #172 and #173). - 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, matching #171. - Lockfile updated via `npm install` — only the 9 workspace `version` entries changed. ## Test plan - [ ] CI green - [ ] Run `Publish prerelease to npm (next)` workflow against this commit (after #172 and #173 land) and confirm publish succeedsv0.6.0-next.1 |
||
|
|
22a4811cb1 |
fix(tool-server): use semver for update-checker comparison (#173)
## Summary - The homegrown `parseSemver` in `packages/tool-server/src/utils/update-checker.ts` only matched plain `MAJOR.MINOR.PATCH` and returned `null` for prerelease strings. After [#171](https://github.com/software-mansion/argent/pull/171) bumped the local version to `0.6.0-next.0`, `isNewerVersion` started returning `false` for **any** value of `latest`, because the *current* side failed to parse. Two consequences: - Users on the `next` dist-tag are never notified of new releases (silent feature degradation, not a crash). - The `detects an available update on startup` unit test broke because its asserted state now reads `{ updateAvailable: false, latestVersion: "99.0.0" }` instead of `{ true, "99.0.0" }` — see [run 25161184594](https://github.com/software-mansion/argent/actions/runs/25161184594/job/73755770427). - Replaces the regex parser with the `semver` package (already pinned at `^7.7.4` and present in the lockfile via `@argent/installer`). Adds it as a dep of `@argent/tool-server` along with `@types/semver`. - Keeps the existing UX rule: don't notify when upstream `latest` is itself a prerelease — npm's `latest` dist-tag should be stable, and we shouldn't push betas to stable users. `semver.gt` correctly treats a stable version as greater than its own prereleases, so a user on `0.6.0-next.0` will be notified when `0.6.0` ships. ## Behavior matrix | current | npm `latest` | before (broken) | after | |---|---|---|---| | `0.6.0-next.0` | `99.0.0` | not flagged | **flagged** | | `0.6.0-next.0` | `0.6.0` | not flagged | **flagged** | | `0.6.0` | `99.0.0` | flagged | flagged | | `0.6.0` | `1.0.0-beta.1` | not flagged | not flagged | | anything | invalid string | not flagged | not flagged | ## Test plan - [x] `npm test -w @argent/tool-server -- test/update-checker.test.ts` — all 10 tests pass (was 9/10). - [x] `npm test -w @argent/tool-server` — full suite 444/444 passes. - [x] `npx tsc --build` clean from the repo root. - [ ] CI green on this branch. |
||
|
|
0dca6bbfef |
fix: build workspace deps before packing/publishing argent (#172)
## Summary - The `publish` and `publish-next` workflows ran `npm run build -w @swmansion/argent` directly, but after the recent CLI/MCP/main split, `@swmansion/argent` imports types from `@argent/cli`, `@argent/mcp`, `@argent/installer`, `@argent/tools-client`. Those packages expose types from their own `dist/index.d.ts`, which doesn't exist until they're built — so argent's `tsc` failed with `TS2307: Cannot find module '@argent/...'` (e.g. [run 25161244218](https://github.com/software-mansion/argent/actions/runs/25161244218)). - Added `npx tsc --build` before the per-workspace argent build in both publish workflows so the sibling packages produce their `dist/` first. This mirrors what `unit-tests.yml` already does, which is why the regression didn't surface on PRs. - Applied the same fix to the root `pack` script (`npm run pack`) since it had the same latent bug for local packing. ## Test plan - [x] Reproduced the failure locally by wiping every `dist/` and `tsconfig.tsbuildinfo`, then running `npm run build -w @swmansion/argent` — same four `TS2307` errors as CI. - [x] Verified `npx tsc --build && npm run build -w @swmansion/argent` succeeds from a clean state. - [ ] Re-run the `Publish prerelease to npm (next)` workflow against this branch (or after merge) to confirm green. |
||
|
|
be31093f6c |
chore: bump package versions to 0.6.0-next.0 (#171)
## Summary - Bump all published packages to `0.6.0-next.0` in preparation for the next prerelease train. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. `@argent/native-devtools-ios` was at `0.5.1`; aligned with the rest. `packages/ui` (`0.0.1`) is on a separate version track and left as-is. ## Test plan - [ ] CI green - [ ] `npm next` prerelease publish workflow picks up the new versionv0.6.0-next.0 |
||
|
|
289fda7ea5 |
fix: opencode detect alternative filenames (#170)
OpenCode supports: - project-scope: `opencode.jsonc` and `opencode.json`; - global-scope: `config.json` (legacy/migrated), `opencode.json`, and `opencode.jsonc` This PR adds support for these alternative filenames. This PR also adds `jsonc` support. <img width="502" height="377" alt="image" src="https://github.com/user-attachments/assets/a0f1d437-93ce-4677-bc6f-b8d0c8427b57" /> |
||
|
|
aac6151cc9 |
fix: preserve old Zed config (#168)
Closes #165. `argent init` was wiping users' `~/.config/zed/settings.json` - Zed's settings format is JSONC, so `readJson` used `JSON.parse` and silently fell back to `{}` on any parse failure, after which the Zed adapter would overwrite the file with an argent-only stub. Co-authored-by: Filip131311 <contact@filipkaminski.com> |
||
|
|
72e0d4d6fd |
chore: update banner image in README (#169)
Updated the README banner to be Software Mansion branded <img width="955" height="594" alt="image" src="https://github.com/user-attachments/assets/eb57abd2-2870-483a-9719-447ebbdea30b" /> |
||
|
|
62d0fe0c73 |
refactor: overhaul React profiler - DevTools backend, session ownership, richer data (#156)
## What this PR does
This refactor:
- replaces the DFS fiber-walking hook inside the app's JS runtime with
the **React DevTools backend** as the source of profiling data
- adds a session-ownership system so two tool-server instances can't
silently clobber each other
- gives agent a new status tool for recovery after interruptions
- improves what the profiling analysis report shows the agent
I realise this a big pull request, but changes began to affect each
other when it comes to registered scripts on the react-native side.
This, all-in-all, should however cover our needs when it comes to the
react profiler and if it proves sufficient - we will only have to
iterate on small fixes afterwards.
If I were you, I would skip over the implementation of scripts on the
native-side and focus on what the tool-server does when it comes to
profiling right now.
---
## Profiling workflow
The overall flow has not changed from the agent's perspective - agent
calls `react-profiler-start`, do something in the app, call
`react-profiler-stop`.
What changed is how the profiling data is collected.
**Before:** `react-profiler-start` injected a custom `onCommitFiberRoot`
hook that walked the live fiber tree at every React commit in DFS-like
way and appended records to a global `__ARGENT_DEVTOOLS_COMMITS__` array
in the app's JS heap. On stop, the array was fetched in chunks over CDP.
**Now:** `react-profiler-start` calls `ri.startProfiling(true)` on the
React DevTools backend's `rendererInterface`. React's own profiler
accumulates commit data internally. On stop, a single
`STOP_AND_READ_SCRIPT` call stops the backend and reads
`ri.getProfilingData()` in one round-trip. The DevTools backend data is
richer, more accurate, and doesn't require maintaining a bespoke
fiber-walking implementation.
---
## Session ownership
The profiler runs inside the app's JS heap. There's only one "slot": it
used to be that when two tool-server instances (or the same instance
after a process restart) both try to start profiling, the second call
silently overwrites the first and all data from the first session is
gone.
Right now, there is an approach which blocks silent overtake of the
profiling session.
When `react-profiler-start` runs, it:
1. Reads the current state from the runtime using `READ_STATE_SCRIPT` (a
pure, side-effect-free read).
2. If a session is already running, it reads the
`__ARGENT_PROFILER_OWNER__` object written into `globalThis`, which
contains `sessionId`, `startedAtEpochMs`, and `lastHeartbeatEpochMs`.
3. Classifies the existing session as **fresh** or **stale** using
`classifyStaleness()` (stale = no heartbeat for > 5 minutes).
If the session is fresh and you don't pass `{ force: true }`, start
refuses and returns an `already_running` payload instead of overwriting.
4. If the session is stale or `force: true`, it calls
`STOP_FOR_TAKEOVER_SCRIPT` to cleanly stop the prior session, then
starts a new one.
### Heartbeats
Each `sessionId` UUID is written to `__ARGENT_PROFILER_OWNER__` in the
runtime at start. While profiling, any side query tool
(`react-profiler-renders`, `react-profiler-fiber-tree`) that this same
tool-server process calls will bump `lastHeartbeatEpochMs` via
`HEARTBEAT_SCRIPT`. This keeps the session from being classified as
stale as long as the owning process is still making calls.
### What the agent sees when something goes wrong
Previously, if you called `react-profiler-start` into an already-running
session you'd get a silent clobber or a confusing Hermes error. Now:
- **Running session detected, still fresh:** `{ already_running: true,
owner: { sessionId, startedAtEpochMs, lastHeartbeatEpochMs },
age_seconds, how_to_reclaim }` — the agent can read the owner timestamp
and decide whether to tell the user to wait or force-reclaim.
- **Running session detected, stale:** automatic takeover with a log
note.
- **No owner record (e.g. another DevTools client started the
session):** also automatic takeover.
### New `react-profiler-status` tool
A read-only tool that reports the current state without starting or
stopping anything. Returns `session_status` as one of `"active"` /
`"taken_over"` / `"stopped"` / `"no_react_runtime"`, plus the full owner
record. Use this after a debugger disconnect, an unexpected error, or
when the agent restarts mid-session to decide whether to continue,
reclaim, or give up.
The agent has instructions to use the tool whenever it is interrupted
during the profiling by the user or otherwise. Will have to look into
how much this is actually used.
---
## What changed in the data we collect
The old hook read fiber fields directly (`fiber.type.displayName`,
`fiber.memoizedProps`, `fiber.selfBaseDuration`, etc.) at commit time.
This meant:
- Names for **transient components** (modals, tooltips, navigation
screens) that unmounted before stop were already lost - the fiber was
gone.
- Change-detection logic (which props changed, which hooks fired) was
reimplemented manually against the fiber internals.
The new approach reads `ri.getProfilingData()` which gives us the
DevTools backend's own commit log. Each commit entry has
`fiberActualDurations`, `fiberSelfDurations`, `changeDescriptions`,
`timestamp`, and `duration` - the same data React DevTools Profiler
shows in its browser panel.
### Fiber name cache for transient components
To address the lost-name problem, `FIBER_ROOT_TRACKER_SCRIPT` now hooks
`onCommitFiberRoot` and, immediately after the DevTools backend finishes
processing each commit (synchronously, while the fiber is still in
memory), populates `globalThis.__argent_fiberNames__` with `fiberID →
displayName` entries. Because fiber IDs are monotonically increasing and
never reused, this cache never goes stale. `STOP_AND_READ_SCRIPT` uses
this cache as a fallback for any fiber whose name
`getDisplayNameForElementID` can no longer resolve.
### Unattributed fiber tracking
When a transient fiber renders during the session but still can't be
named at stop time (not in the name cache, not in the DevTools instance
map), its work is now **surfaced rather than silently dropped**. The
stop tool computes `unattributedByCommit`: a per-commit tuple of
`[commitIndex, droppedFiberCount, droppedSelfMs]`. This flows through
to:
- The session metadata file on disk (`unattributedByCommit` array).
- The pipeline (`buildHotCommitSummaries` receives and indexes it).
- The `HotCommitSummary` type gains `unattributedMs` and
`unattributedFiberCount`.
- The generated report shows a warning line per commit with unattributed
work, e.g. `⚠️ 12ms unattributed — 3 fibers unmounted before stop`.
- `react-profiler-stop` returns top-level `unattributed_ms`,
`unattributed_fiber_count`, `unattributed_commit_count` in its response
so the agent immediately knows if there's a gap.
### Actual vs. self duration now both surfaced
The old report showed only `selfDuration` (exclusive render time). Both
are now captured in `HotCommitComponentEntry` and rendered in the report
as:
```
- `ComponentName` — 4.2ms self, 18.7ms w/children
```
A legend at the top of the report explains when to sum self and when not
to sum inclusive time. The summary table also shows both columns.
### Component name unwrapping
Display names from the DevTools backend can be wrapped:
`Forget(Memo(MyButton))`, `ForwardRef(Icon)`, etc. The pipeline's render
stage now strips these wrappers using `annotateComponentName()` and
appends a human-readable tag, so the report shows `\`MyButton\`
[React.memo + React Compiler]` instead of the raw internal string. The
raw DevTools name is still used in all tool-call suggestions (since
every pipeline stage keys on the original string).
---
## What scripts do what
All injected JS scripts now live in a single file —
`packages/tool-server/src/utils/react-profiler/scripts.ts` — grouped by
responsibility:
| Script | When injected | What it does |
|---|---|---|
| `REACT_NATIVE_PROFILER_SETUP_SCRIPT` | Every `react-profiler-start`
(idempotent) | Wraps `ri.startProfiling` / `ri.stopProfiling` to track
`isProfiling` state and `startedAtEpochMs` without clock skew; installs
the `__argent_profilerHeartbeat` helper |
| `FIBER_ROOT_TRACKER_SCRIPT` | On CDP connect (idempotent) | Intercepts
`onCommitFiberRoot` to populate the `__argent_fiberNames__` commit-time
name cache |
| `READ_STATE_SCRIPT` | Every `react-profiler-start`,
`react-profiler-status` | Pure read: returns `{ hookExists,
rendererInterfaceFound, isRunning, owner, nowEpochMs }` |
| `buildStartScript(ownerJson)` | `react-profiler-start` on the happy
path | Calls `ri.startProfiling`, writes owner record, returns
post-start verification flags |
| `STOP_FOR_TAKEOVER_SCRIPT` | `react-profiler-start` on the takeover
path | Stops the prior session cleanly before starting a new one |
| `HEARTBEAT_SCRIPT` | `react-profiler-renders`,
`react-profiler-fiber-tree` | Bumps `lastHeartbeatEpochMs` while the
owning process is still active |
| `STOP_AND_READ_SCRIPT` | `react-profiler-stop` | Stops the backend
profiler and reads `getProfilingData()` + name cache in one round-trip |
| `RESOLVE_FIBER_META_SCRIPT` | `react-profiler-stop` (if commits > 0) |
Walks the live fiber tree to collect `hookTypes`, `parentName`, and
`isCompilerOptimized` metadata per component |
|
||
|
|
c1fda3834b | fix: remove private vscode config from the code base (#167) | ||
|
|
dbf4de1ab0 |
refactor(tool-server): per-tool dirs + cross-platform dispatch (iOS only, Android-ready) (#164)
## Summary
Sets up the foundation for adding Android support without changing any
user-visible iOS behavior. iOS works exactly as before; Android handlers
are stubs that throw a clear \"not yet implemented\" message until
they're filled in.
### New primitives
- `Platform`, `DeviceKind`, `DeviceInfo`, `ToolCapability`,
`ToolDependency` (in `@argent/registry`)
- `classifyDevice(udid)` + `resolveDevice(udid)` (UUID shape → iOS, else
Android)
- `assertSupported(toolId, capability, device)` — throws
`UnsupportedOperationError`
- `ensureDep(s)` — host-binary preflight with install hints
- `dispatchByPlatform({ toolId, capability, ios, android })` — one-line
cross-platform `execute()`
### HTTP gating
`/tools/:name` now runs:
- Host-binary preflight (`requires`) → 424 with install hint
- Capability gate (`capability` + `udid`) → 400 with reason
### Per-tool directory layout
Every cross-platform tool now lives in its own directory:
\`\`\`
tools/gesture-tap/
index.ts ← ToolDefinition, capability, dispatchByPlatform
platforms/
ios.ts ← iOS impl (existing logic)
android.ts ← stub: throws \"not yet implemented\"
\`\`\`
Refactored 17 tools this way: gesture-{tap,swipe,custom,pinch,rotate},
screenshot, button, paste, keyboard, launch-app, restart-app,
reinstall-app, open-url, rotate, run-sequence, describe (with shared
contract + iOS adapters under platforms/).
### Tool call flow (iOS, today)
1. Agent calls \`gesture-tap { udid: \"<uuid>\", x, y }\`
2. HTTP zod-parses, then host-binary preflight, then capability gate
(passes — capability has \`apple\`)
3. Tool's \`execute()\` (built by \`dispatchByPlatform\`):
- \`resolveDevice(udid)\` → \`{ platform: \"ios\", kind: \"simulator\",
id }\`
- \`assertSupported\` → passes
- branches to \`tapIos(services, params, device)\`
4. iOS impl uses \`SimulatorServerApi\` exactly as before.
### Tool call flow (Android, future)
Same flow; the Android branch in \`dispatchByPlatform\` runs. Filling in
Android = three steps:
1. Implement the function in \`tools/<tool>/platforms/android.ts\`
2. Add \`android: { emulator: true, device: true, unknown: true }\` to
the tool's capability
3. Make \`services()\` platform-aware where the iOS service URN doesn't
apply (e.g. \`launch-app\` declares \`nativeDevtools\` for iOS only)
## Test plan
- [x] All 395 existing tests pass; 9 new tests for `device-info` +
`capability`
- [x] TypeScript clean across `@argent/registry` and
`@argent/tool-server`
- [ ] Manual smoke-test on a real iOS simulator (gesture-tap,
screenshot, describe, launch-app)
## Deferred to follow-ups
- **Per-tool capability declarations on iOS-only tools**
(`native-devtools/*`, `profiler/ios-profiler/*`, `network/*`,
`simulator/*`). They still work; the HTTP capability gate is a no-op
when no declaration exists. Adding them is mechanical and doesn't affect
iOS behavior.
- **`list-simulators` / `boot-simulator` rename to `list-devices` /
`boot-device`** — that's a behavior change, paired with the Android
impls.
- **Platform-aware `services()`** for tools whose iOS service has no
Android equivalent (e.g. `nativeDevtools` in `launch-app` /
`restart-app`). Needed when filling in Android impls.
|
||
|
|
e226fe5404 |
test: speed up long unit tests (#153)
## Summary
These tests were taking up too long.
| File | Before | After |
|------|-------:|------:|
| `fetch-timeout.test.ts` hanging-POST test | 6281ms | ~600ms |
| `workspace-reader.test.ts` (full file) | ~4500ms | ~140ms |
| `gather-workspace-data.test.ts > valid snapshot` | ~590ms | ~3ms |
**Package totals:** `packages/mcp` 6.44s → ~830ms,
`packages/tool-server` 5.29s → ~790ms.
### What was slow and why
- `fetchWithReconnect` is now exported from `mcp-server.ts` so the test
uses the real function. The config accepts an `expBackoffBase` and a
`fetchTimeoutMs` so the hanging-POST test shrinks the backoff base to
`5ms` and the per-attempt timeout to `100ms` (~575ms total)
- every `readWorkspaceSnapshot` call spawned 8 real subprocesses
(`node`, `npm`, `yarn`, `pnpm`, `bun`, `pod`, `eas`, `expo`) via
`runVersionCommand`. Added `vi.mock("node:child_process", ...)` that
stubs `execFile`.
|
||
|
|
de2d000300 |
feat(tool-server): prefix stdout/stderr lines with ISO timestamp (#155)
Adds timestamp to every `tool-server.log` line. `mcp-calls.log` already has timestamps, so no changes. The tz is UTC+0 because that's what we already use for the `mcp-calls.log` Works: <img width="1074" height="599" alt="image" src="https://github.com/user-attachments/assets/db7094f3-8957-4f37-8643-9b2e7c449a1e" /> --------- Co-authored-by: pFornagiel <pawel.fornagiel2@gmail.com> |
||
|
|
26da07d371 |
feat: add MCP config adapter for opencode (#152)
This PR adds MCP config adapter for OpenCode At one point OpenCode worked presumably because it read `.claude` or some other config (i've had like 13 of them on my machine). That doesn't seem to be the case anymore. before|after ---|--- <img width="508" alt="image" src="https://github.com/user-attachments/assets/615d07f6-aa92-49a4-b159-92d6b292d2c7" />|<img width="510" alt="image" src="https://github.com/user-attachments/assets/4dabcd1a-d920-4979-9e9e-31e45b12d1c2" /> <img width="400" alt="image" src="https://github.com/user-attachments/assets/8853ddf4-0001-4874-81b7-d2a88d3b6956" />|<img width="400" alt="image" src="https://github.com/user-attachments/assets/048928bf-7c59-4ac0-8483-04cc3b3edcda" /> --------- Co-authored-by: Filip131311 <contact@filipkaminski.com> |
||
|
|
e42308106c |
ci: add workflow to publish prereleases under npm next tag (#163)
## Summary - Adds `.github/workflows/publish-next.yml`, a manual `workflow_dispatch` workflow that publishes `@swmansion/argent` to npm under the `next` dist-tag so users can opt into prereleases via `npm install @swmansion/argent@next` without affecting `latest`. - Mirrors the existing `publish.yml` (same checkout / native binaries download / build / smoke checks); the only functional differences are `npm publish --tag next` and a pre-publish guard that fails fast unless `packages/argent/package.json` has a SemVer prerelease version (contains `-`, e.g. `0.6.0-next.0`). ## Why `npm publish` defaults to the `latest` dist-tag even for prerelease SemVer versions. Without an explicit `--tag next`, a published `0.6.0-next.0` would be served to anyone running `npm install @swmansion/argent`. This workflow makes prerelease publishing safe and explicit. ## Usage 1. On a release branch, bump `packages/argent/package.json` to e.g. `0.6.0-next.0` and tag `v0.6.0-next.0` (the existing `release.yml` will create a GitHub Release). 2. Run **Actions → Publish prerelease to npm (next) → Run workflow** with `ref: v0.6.0-next.0`. 3. Verify with `npm dist-tag ls @swmansion/argent` — `latest` is unchanged, `next` points at the new prerelease. ## Test plan - [ ] Dry-run the workflow against a prerelease tag and confirm `npm pack --dry-run` succeeds and the publish step runs `--tag next`. - [ ] After a real prerelease publish, confirm `npm view @swmansion/argent dist-tags` shows `next: <version>` and `latest` is untouched. - [ ] Sanity check the guard: trigger against a non-prerelease ref (e.g. `v0.5.3`) and confirm the workflow fails at the "Verify package version is a prerelease" step before running `npm publish`. |
||
|
|
6355620c4d |
refactor(mcp): split mcp package into argent, argent-cli, argent-installer, argent-mcp, argent-tools-client (#162)
## Summary - Splits the monolithic `packages/mcp` package into five focused packages: - `argent` — the user-facing CLI entry (`argent` binary, init/uninstall/update flows, bundled-paths, scripts). - `argent-cli` — CLI runner internals (flag-parser, run, server, tools). - `argent-installer` — `init`/`update`/`uninstall` flows, MCP config adapters, skills installer. - `argent-mcp` — MCP server itself (mcp-server, tool-mapping, auto-screenshot, content adapters). - `argent-tools-client` — HTTP client + launcher for the tool-server. - Adds Gemini agent definitions, VSCode MCP config, and per-package `tsconfig.json` / `vitest.config.ts`. - Updates root `tsconfig.json`, `scripts/dev.cjs`, CI workflows, and `package.json` to wire the new packages together. ## Test plan - [ ] `npm install` resolves the new workspaces cleanly - [ ] `npm run build` succeeds across all packages - [ ] `npm test` passes (renamed test files keep their assertions) - [ ] `npx argent init` still produces the expected MCP config in a sample project - [ ] `npx argent update` / `argent uninstall` exercise the installer package - [ ] CI workflows (`publish.yml`, `unit-tests.yml`) run on this branch |
||
|
|
99cd3870ba |
feat: Add CLI capability to argent (#161)
This PR adds a thin cli layer to argent by introducing 3 new commands: 1) `server` allows for checking the tools-server status 2) `tools` allows for tools discovery 3) `run` allows for running tools straight from the shell This is a baseline functionality needed for testing hypothesis that CLI with skills is more efficient then mcp and allow should allow easier integration with potential cloud infrastructure |
||
|
|
037c474956 |
Feat: Baseline web UI for the simulator (#158)
This is a very base line UI: - integrates into the existing simulator server on tools server - does not expose any endpoints via mcp |
||
|
|
acc5798185 |
feat(mcp): support progressive tool loading via _meta hints (#120)
* feat(mcp): support progressive tool loading via _meta hints Add optional alwaysLoad and searchHint fields to ToolDefinition, forward them through GET /tools, and emit them as _meta["anthropic/alwaysLoad"] / _meta["anthropic/searchHint"] in the MCP ListTools response. Claude Code's ToolSearch defers MCP tools by default; tools marked alwaysLoad stay in context on every turn, and searchHint feeds the BM25 ranker so deferred tools surface for relevant queries. Mark the core discovery + interaction tools as alwaysLoad so Claude never has to round-trip through ToolSearch to tap the screen: describe, debugger-component-tree, screenshot, gesture-tap, gesture-swipe, launch-app, run-sequence. Everything else defers. * feat(mcp): mark restart-app as alwaysLoad Tools that depend on controlling app startup (relaunch for clean state, refreshing native-devtools injection) need restart-app to be immediately callable rather than routed through a search round-trip. |
||
|
|
6c97269306 |
feat: add Get Started tip to end of argent init flow (#154)
* feat: add Get Started tip to end of argent init flow New users finished init and asked "what now?" — surface the "ask your assistant What can Argent do?" tip from the README as a final note before the outro so the next step is obvious. * style: make Get Started tip more prominent in init flow Bold colored title, bold cyan call-to-action indented on its own line, and a dimmed tagline below — so the "what now?" answer visually pops instead of reading as another summary line. * style: fold outro into Get Started note and make it more prominent * style: render Get Started as outro so the end of init flow is clear * style: restore boxed banner around Get Started tip, keep outro terminator * human fixv0.5.3 |
||
|
|
927620cb82 |
fix: actually update skills on argent update and use semver-aware comparison (#151)
* fix: update skills during argent update and use semver-aware comparison
- Replace `installed !== latest` with `isNewerVersion` so a locally
installed prerelease (e.g. 0.5.4-beta on top of published 0.5.3) no
longer triggers a downgrade prompt during `argent update`.
- Replace the "Check if skills have updates?" prompt (which only ran
`npx skills check`) with an actual update that runs `npx skills update
-y`. Under --yes/-y the step runs unattended so CI and scripted
upgrades actually pick up new skill versions.
* Update skills unconditionally, scope to argent-owned skills, cover both scopes
- Drop the skills prompt entirely — the rest of the update flow already
refreshes MCP configs, allowlists, rules, and agents without asking, so
the skills step is now consistent. Stale skills silently break argent
behavior after a version bump, so the safe default is to always resync.
- Scope the update to argent-owned skills by reading bundled skill names
from SKILLS_DIR at runtime (new `listBundledSkills` helper). Third-party
skills the user installed separately are left untouched.
- Run against both -p and -g scopes instead of relying on `-y`'s
auto-detect, which only covers one scope and would miss whichever the
user didn't pick during init.
* Use `skills add` for argent skill resync, only in scopes that track them
The skills CLI's `update` command silently skips every entry with
sourceType="local" (which is how argent ships its bundled skills), so
`npx skills update` never actually re-synced them — the previous commit
was effectively a no-op. Switch to `npx skills add <SKILLS_DIR> --skill
'*' -y`, which hashes the source on disk and reinstalls whatever
changed after `npm i -g @swmansion/argent@new`.
To avoid creating a `skills-lock.json` in an unrelated working
directory, only resync a scope that already tracks at least one
argent-owned skill. Scope detection reads the skills CLI's lock files
directly: `./skills-lock.json` for project scope and
`$XDG_STATE_HOME/skills/.skill-lock.json` or `~/.agents/.skill-lock.json`
for global scope.
* Prune argent skills that are no longer shipped
`skills add` only scans the current SKILLS_DIR, so a skill that argent
ships in v0.5.3 but removes in v0.5.4 stayed in every lock file and in
every agent's skills/ directory — the agent would keep loading it.
After resync, diff the scope's lock against the bundled set and run
`npx skills remove` for each argent-prefixed entry that's no longer
bundled. Ownership detection is prefix-based (every skill argent has
ever shipped starts with `argent-`), which also lets us find orphans
that listBundledSkills() no longer knows about.
Replaces the previous hasSkillsInLock helper with
listArgentSkillsInLock, which returns the actual names so they can be
used for both scope detection and orphan computation.
* Extract argent-skills refresh into a shared module
Pulls the two-scope resync+prune block out of update.ts into
packages/mcp/src/cli/skills.ts, exposed as `refreshArgentSkills` (pure
data) and `formatSkillRefreshSummary` (display). update.ts shrinks
from ~80 lines of inline logic to a two-line call.
Also wires the helper into init.ts's upgrade prompt: when the user
accepts an in-init version bump, we now prune argent skills that the
new version dropped — previously orphans leaked forever because
init.ts never ran a remove, only a single-scope `skills add` in
Step 2.
The install action is deliberately not extracted here — update's and
init's install paths differ on stdio mode, failure handling,
killToolServer prerequisite, and the ARGENT_SKIP_POSTINSTALL env, and
unifying them would force behavior alignment that doesn't obviously
belong together.
* format
* Clean project lock after skills remove
The skills CLI's `skills remove` deletes the canonical skill directory
and per-agent symlinks for both scopes, but only removes the entry
from the GLOBAL lock (`~/.agents/.skill-lock.json`) — the project
`skills-lock.json` is left with a stale entry pointing at a skill
whose files no longer exist. The end-to-end test in
`scripts/test-skills-prune.sh` caught this: canonical dir and symlink
gone, lock still contained the orphan.
Without cleanup, every subsequent `argent update` would keep seeing
the orphan in the lock, try to prune it again, and report "pruned 1"
for something that's already been pruned. Fix it by stripping the
pruned names from the lock file after a successful `skills remove`.
Also commits `scripts/test-skills-prune.sh`, the e2e script that
uncovered this.
* Revert project-lock cleanup workaround
The previous commit worked around a bug in the vercel-labs/skills CLI
where project-scope `skills remove` cleans the canonical skill
directory and per-agent symlinks but leaves the `skills-lock.json`
entry behind. In practice this only matters cosmetically — the agent
never loads the orphan because its files are gone — so maintaining a
workaround in our code base for a bug that belongs upstream is more
churn than it's worth.
Reverts the `removeSkillsFromLockFile` helper and its call site in
`refreshArgentSkills`, plus the two tests that asserted on it.
Updates the e2e script to expect the stale lock entry for project
scope and only fail on missing canonical dir / symlink cleanup.
Global scope still gets the strict check — the skills CLI does clean
the global lock correctly there.
Issue to be filed upstream so they can fix it in the right place.
* Drop scripts/test-skills-prune.sh
The e2e script served its purpose uncovering the skills-CLI
project-lock quirk (now filed upstream as vercel-labs/skills#977) and
isn't needed as a committed artifact.
* Add --from tarball flag to argent update
Mirrors the `--from <path>` flag that already exists in `argent
init`: installs from a local tarball or filesystem path instead of
the registry, then runs the same refresh+prune as a normal update.
Intended for dry-running a locally-packed argent end-to-end before
publishing to npm.
Undocumented by design — it's a dev tool, not a user-facing feature.
Bypasses the registry probe entirely so testing doesn't depend on
what npm currently has as latest.
Extracts `extractFlag` from init.ts into utils.ts so both commands
share a single implementation.
* Revert "Add --from tarball flag to argent update"
This reverts commit
|
||
|
|
9fee899e5f | chore: bump package versions to 0.5.3 for mcp, registry, skills, and tool-server (#149) | ||
|
|
aaa1a21c7c |
fix: only prompt for update when remote version is strictly newer (#150)
Previously the CLI checked `latest !== version`, which triggered an update prompt (and recommended a downgrade) whenever a locally installed prerelease was newer than the published release. Switch to semver-aware comparison so update prompts only appear for genuine upgrades. |