mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
dependabot/github_actions/github-actions-073acec55a
742 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ec2521d850 |
chore(ci)(deps): bump the github-actions group across 1 directory with 5 updates
Bumps the github-actions group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `4` | `7` | | [actions/setup-node](https://github.com/actions/setup-node) | `4` | `7` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4` | `7` | | [actions/setup-python](https://github.com/actions/setup-python) | `6` | `7` | | [actions/setup-java](https://github.com/actions/setup-java) | `5` | `6` | Updates `actions/checkout` from 4 to 7 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v7) Updates `actions/setup-node` from 4 to 7 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v7) Updates `actions/upload-artifact` from 4 to 7 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7) Updates `actions/setup-python` from 6 to 7 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v7) Updates `actions/setup-java` from 5 to 6 - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-java dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
e1d7654af5 |
docs: add a Limits section to the physical iOS devices page (#1050)
## Summary - Adds a `## Limits` section to `packages/docs/docs/features/physical-ios-devices.mdx`, matching the shape of the other feature pages. - Moves the unsupported-capability bullets out of "What works" into the new section. - Adds limits that were undocumented: iPhone only, USB cable only, home/volume/action buttons only, Enter and Backspace only. ## Docs This PR is a docs-only change. `npx docusaurus build` and `npm run format` pass. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified limitations when using physical iOS devices. * Documented supported hardware: iPhones are available, while iPads are not currently shown. * Added details about USB-cable-only control, supported home, volume, and action buttons, and available text, Enter, and Backspace keys. * Moved simulator-specific limitations into a dedicated “Limits” section, including unavailable rotation, shake, permission changes, debugging, profiling, inspection, recording, and paste capabilities. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b27f222386 |
fix(flows): refuse to record a step whose native-devtools precheck blocked (#1116)
Fixes #935. **Cause:** a blocked native-devtools precheck is a *resolved* result, so `flow-add-step` read "it returned" as "it ran" and wrote the step - a `restart-app` that terminated and launched nothing became a passing `launch:`, while the runner scores that same result an error at replay. **Fix:** the recorder reads the runner's own `isNativeDevtoolsBlockResult` right after the sub-invoke and fails the call, recording nothing. Keyed on `params.command`, so it covers all nine precheck tools, not just the `restart-app` in the report. **Left alone:** `isDebuggerNotConnectedResult` is the same shape on the recorder path and is filed separately as #964; `nestedOrchestratorOutcome` is #719's scope. **Docs:** none needed - no tool, CLI, config key or flow directive changed, and `flow-add-step`'s description already states that an error means nothing was recorded. <details> <summary>Verification</summary> New `packages/tool-server/test/flows/flow-record-native-devtools-gate.test.ts` - all four block statuses on `restart-app`, the raw `tool:` spelling on `native-full-hierarchy`, plus two negative controls (a green `restart-app` still records `launch:`; a `gesture-tap` answering `{status: "connect_pending"}` still records). Discriminates - with the source hunk stashed: ``` Tests 5 failed | 2 passed (7) AssertionError: promise resolved "{ ...(5) }" instead of rejecting + "message": "Step added to \"nd-rec\" flow", + "recorded": "1. tool: native-full-hierarchy {\"bundleId\":\"com.example.app\"}", ``` Restored: ``` Test Files 1 passed (1) Tests 7 passed (7) ``` - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - 369 files, 4839 passed, 1 skipped - `npx prettier --check` on both files - unchanged - eslint on the two files could not run locally (`packages/docs/tsconfig.json` needs its own `npm ci`); CI covers it. </details> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3dbf7bd3cb |
fix(screenshot-diff): classify an undecodable PNG and name the failing side (#1147)
Fixes #818 **Cause** - `decodePngFile` let the pngjs/fs error through as-is, so an undecodable input answered with a bare library string, no failure code (bucketed `ARGENT_UNCLASSIFIED_FAILURE`), and no hint which of the two sides was bad - both decode in one `Promise.all`. **Fix** - wrap the decode in a `FailureError` carrying `SCREENSHOT_DIFF_INPUT_INVALID` / `screenshot_diff_decode_failed` and the offending path, matching `validateInputSources` in the same tool. `flow`'s `snapshot` goes through the same `diffPngFiles`, so it gets it too. **Class** - the other `PNG.sync.read` sites decode bytes the tool itself just captured, not a caller path: `vega-screen.ts` (emulator's own output), `flow-visual.ts` `cropPngFile` (the capture it just wrote), `flow-pixels.ts` (deliberately soft - absence of visual evidence). Left as-is. **Docs** - none needed: no tool schema, CLI, config key or flow directive changed. <details> <summary>Verification</summary> Three-case table test (`test/screenshot-diff.test.ts`), each failing on the un-fixed code with exactly the strings the issue reports: ``` non-PNG bytes expected Error: Invalid file signature to be an instance of FailureError truncated PNG expected Error: There are some read requests waitn… to be an instance of FailureError a directory expected Error: EISDIR: illegal operation on a dir… to be an instance of FailureError ``` Proven by stashing the source fix, running the file (3 failed | 21 passed), restoring it (24 passed). - `npx tsc --build` clean - `npm run typecheck:tests -w @argent/tool-server` clean - `npm test -w @argent/tool-server` -> 368 files, 4835 passed | 1 skipped - `npx prettier --write` on both changed files - unchanged </details> |
||
|
|
a3312cab97 |
fix(tool-server): admit a digit-leading bundleId in the four app-lifecycle schemas (#1101)
Fixes #1024 **Cause.** `launch-app`, `restart-app`, `reinstall-app`, `settings-permissions` and `open-url` each carried their own copy of `/^[A-Za-z_][A-Za-z0-9._-]*$/`, which rejects an iOS bundle id such as `9gag.app` - with a message that says digits are allowed, so the agent reading it has nothing to act on. **Fix.** One shared `BUNDLE_ID_PATTERN` / `BUNDLE_ID_MESSAGE` in `src/utils/bundle-id.ts`. The head class becomes `[A-Za-z0-9_]` (the same class `native-devtools`' handshake validator already uses); `-` and `.` stay out, which is the whole of the flag-injection guard the old comments cited. The message names the head rule it enforces. **Class check.** No copy of the pattern remains (`grep BUNDLE_ID_PATTERN`). `open-url` was a fifth copy that landed on main while this branch was open - the catalogue-wide test caught it. `ACTIVITY_PATTERN` in `launch-app` / `restart-app` keeps its letter-only head deliberately: an activity is a Java class name and cannot start with a digit. **Docs.** No docs change needed - `reference/tools.mdx` lists these tools by one-line summary and documents no parameter constraints. <details> <summary>Verification</summary> New `test/bundle-id-pattern.test.ts` walks the whole catalogue rather than one tool, so a sixth `bundleId` constraint has to satisfy the same rules. Discrimination proved by restoring the old pattern and message in the shared module and re-running: ``` × derives a sane MCP JSON schema with bundleId required for every action × launch-app: admits a digit-leading bundle id and still refuses a flag × launch-app: names the head rule when it rejects one × restart-app / reinstall-app / settings-permissions / open-url: both, each Tests 11 failed | 61 passed (72) ``` With the fix in place, from the worktree root: ``` npx tsc --build OK npm run typecheck:tests -w @argent/tool-server OK npm test -w @argent/tool-server 434 files, 6174 passed | 1 skipped npx prettier --write <changed files> unchanged npx knip bundle-id.ts absent from the report ``` `npx eslint` could not run in this checkout (`packages/docs/tsconfig.json` cannot resolve `@docusaurus/tsconfig` without a separate `npm ci` in that workspace, a pre-existing local-environment issue); the ESLint CI job passes. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9e196a97be |
fix(installer): quote the project root in the manual-install advice (#1099)
Fixes #1053 **Cause.** `installLocally`'s failure advice built `cd ${projectRoot} && ${cmdStr}` with the root interpolated raw. A project path with a space is cut in two when the user pastes it; one holding `$`, a backtick or `\` still runs, aimed elsewhere. **Fix.** `shellQuotePath` in `package-manager.ts` single-quotes the path (escaping embedded `'`), and the advice uses it. **Class.** This is the only discovered path interpolated into printed shell advice - the two other "Install manually with" lines print `cmdStr` alone. `cmdStr`'s one variable argument is `--from <tarball>`, a path the reader typed themselves and gets back in the form they gave; left as is. **Docs.** No tool, CLI flag, config key or flow file changes - nothing in `packages/docs/` needs an update. **Verified.** New test in `install-runner.test.ts` drives a failing local install with the root `.../My 'Project' $x`, extracts the printed `cd`, and runs it under `/bin/sh` - `pwd` must come back as the project root. It reproduces the issue's error on the unfixed line. <details> <summary>Test discriminates - fix reverted</summary> ``` FAIL test/install-runner.test.ts > installLocally failure handling > quotes the project root in the manual-install advice /bin/sh: line 0: cd: /var/folders/.../argent-install-advice-UW3dHe/My: No such file or directory Test Files 1 failed (1) Tests 1 failed | 6 passed (7) ``` with the fix in place: ``` Test Files 1 passed (1) Tests 7 passed (7) ``` </details> <details> <summary>Checks</summary> - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/installer` - clean - `npm test -w @argent/installer` - 17 files, 556 tests passed - `npx knip` - 30 lines, identical to the same run on the unchanged tree - `npx eslint` on the three changed `src/` files - clean; `npx prettier --write` applied </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b14d1fb478 |
fix(settings-permissions): 400, not 500, for a permission the platform cannot map (#1100)
Fixes #1032 **Cause** - both `settings-permissions` platform arms rejected an unmappable permission with a bare `FailureError`. The HTTP dispatcher keys the status on the error *class*, never on `error_kind`, so it fell through to **500** and the agent read caller input as the retryable class. **Fix** - throw `InvalidToolInputError` instead (same message, same `SETTINGS_PERMISSION_UNSUPPORTED` signal), which the dispatcher maps to **400** while keeping the granular telemetry bucket. Same shape as the keyboard backends' "no keycode for this character" arm. Both sites of this class inside `settings-permissions` are fixed (iOS + Android; there is no third platform arm). The two sites the issue also names in `devices/boot-device.ts` are left alone: `BOOT_IOS_UNSUPPORTED_HOST` (iOS asked for on a non-macOS host) and `VEGA_ALREADY_RUNNING` (a device-state conflict a `force:true` retry does resolve) each need a different class - a design call in its own right, not this fix. No docs change: `packages/docs` documents what the tool does, not per-tool HTTP status codes. <details> <summary>Verification</summary> Discriminating test - reverted just the two source files to `main` and re-ran the suite: ``` FAIL test/settings-permissions.test.ts > settings-permissions iOS branch > notifications is rejected as unsupported without calling simctl expected FailureError: Permission 'notifications' … to be an instance of InvalidToolInputError FAIL test/settings-permissions.test.ts > settings-permissions Android branch > reminders is rejected as unsupported (no Android equivalent) expected FailureError: Permission 'reminders' has … to be an instance of InvalidToolInputError Tests 2 failed | 59 passed (61) ``` With the fix restored: `61 passed (61)`. Also run from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - full package suite green - `npx prettier --check` on the three changed files - clean </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
15e369eff5 |
fix(tool-server): stop asking about --remote-debugging-port on a port that answered (#1138)
Fixes #880 **Cause.** `discoverPrimaryPage`'s no-page-target branch is only reached after `GET /json/list` succeeded, so the app provably has `--remote-debugging-port` (a missing flag fails earlier, as `CHROMIUM_CDP_UNREACHABLE`) - yet the message asked about the flag and withheld the window hint its `devtools://` sibling gives. **Fix.** One message: the endpoint answered but exposes no page target, i.e. the app is running with no window. Same shape as the sibling branch two lines up. | Before | After | | --- | --- | | `Chromium CDP on port N reported no page targets. Is the app started with --remote-debugging-port=N?` | `Chromium CDP on port N answered but exposes no page target (the app is running with no window). Open an app window and retry.` | Why it matters: `CHROMIUM_CDP_NO_PAGE_TARGET` maps to `cdp_unreachable`, whose Chromium guidance says "see detail" - this string *is* the detail, and its relaunch implication leaves a second copy contending for the single-instance lock while the windowless one still holds the port. **Class.** The other `--remote-debugging-port` question in the file (`fetchJson`'s connect catch) is correct - it fires only when the port did not answer - and is left. No docs/skills change: no prose surface quotes this string, and no tool, CLI flag, config key or flow behaviour changed. <details> <summary>Verification</summary> New test `packages/tool-server/test/chromium-no-page-target-message.test.ts` drives `discoverPrimaryPage` against a local HTTP server serving a `/json/list` with no `page` entry (and, for the sibling, only a `devtools://` page). Discriminates - source fix stashed: ``` FAIL test/chromium-no-page-target-message.test.ts > reports a windowless app when the list holds no page target at all AssertionError: expected 'Chromium CDP on port 55564 reported n…' not to match /--remote-debugging-port/ + Received: "Chromium CDP on port 55564 reported no page targets. Is the app started with --remote-debugging-port=55564?" ``` Fix restored: 2 passed. Ran from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w packages/tool-server` - clean - `npm test -w packages/tool-server` - 4833 passed, 1 skipped, 1 failed: `lens-tools-platform-gate.test.ts` timing out at 5000 ms, the known full-suite/cold-cache flake. Reproduced on a clean tree and passes on 3/3 warm reruns with the fix applied; it imports nothing this change touches. - `npx prettier --write` on both changed files - unchanged </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e09e200eb5 |
feat(flows): let a flow run select a booted remote iOS simulator (#1060)
## What
A booted remote iOS simulator is now a device a flow run can be pointed
at - both by auto-detection and by `--platform ios-remote`.
## Why
It was invisible to flow device resolution. With a remote simulator
booted alongside local ones:
```
$ argent flow run checkout
7 booted devices matched - pass --device or --platform to disambiguate.
Available devices: 79EE0744 (ios, Booted), A1E0DF35 (ios, Booted), emulator-5554 (android, device), ...
```
Three local iPhones and four Android emulators, and the remote simulator
absent entirely. Narrowing to it did not work either - `--platform
ios-remote` was rejected by the parameter's enum. The only way onto a
remote simulator was typing the full `remote:<uuid>` id.
The cause is one type. `RawDevice.platform` was typed from the tuple of
platforms an author can *write* in a flow file, which has no
`ios-remote`, so nothing forced the case to be handled and two functions
quietly fell through:
- `isBooted` hit its `default: return false`, so a remote row never
counted as booted.
- `deviceEntryId` fell through to `serial`, a field a remote row does
not carry (it uses `udid`, prefixed `remote:`). Even once counted, it
had no id - and a resolution error printed it as `? (ios-remote,
Booted)`, hiding the very id the caller needed.
## How
The tuple served two different jobs at once - what an author may write,
and what a run may be pointed at - so it is now two:
- `LAUNCH_PLATFORMS` - launch-map keys and `when:` guards. Unchanged.
- `SELECTABLE_PLATFORMS` - device selection. Adds `ios-remote`.
`ios-remote` is selectable but deliberately not writable. A flow says
what it drives, not which machine hosts the simulator, so `when: {
platform: ios-remote }` stays a parse error.
For the same reason `ios` and `ios-remote` do not overlap: `ios` selects
local simulators only. The flow file is identical either way, so which
host runs it is an explicit choice rather than something that changes
with whatever happens to be booted.
## Scope
Selection only. A remote flow run is still coordinate-only - selector
steps, `await: { idle: true }` and `snapshot: { cropOn }` need a UI tree
source that `ios-remote` does not have yet, and `launch:` maps still
have no `ios-remote` key. Those are separate changes.
## Verification
End-to-end against a booted cloud simulator (`remote:73A22194-…`, iPhone
17 Pro / iOS 26.5) driven through the branch tool-server, with three
local simulators and four Android emulators also up:
| Run | Result |
| --- | --- |
| `platform: "ios-remote"` | resolves `remote:73A22194-…`, flow passes |
| no platform | 8 booted devices listed, including `remote:73A22194-…
(ios-remote, Booted)` - named by its id, not `?` |
| `platform: "ios"` | 3 local simulators only, the remote one absent |
`npm run build` and the tool-server suite pass.
## Docs
`flow-yaml.mdx` and the `argent flow run --help` text both list
`ios-remote` and note that `ios` never picks a remote simulator.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added support for selecting remote iOS simulators with `--platform
ios-remote`.
* Remote simulators are automatically detected when booted and
identified by their device ID.
* Local `ios` selection continues to target only local simulators.
* **Documentation**
* Updated command-line help and flow documentation to describe remote
iOS simulator support and platform selection behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Hubert Gancarczyk <claude-hubert.gancarczyk@swmansion.com>
|
||
|
|
4c71bc911d |
fix(flow): raise the flow tree's depth cap and say why targeting failed (#721)
Rebased onto `main` (was stacked on #720), with a single hunk dropped — see **Restack notes** at the bottom. Two commits: `a71c0f0` is exactly this PR's own delta from the stacked version, `54fe761` is a review pass over it on the post-rebase base. Squash on merge. ## Depth cap: 40 → 100 The flow selector tree capped raw `UIView` nesting at **40**. That does not clear the invisible wrapper layers React Native stacks on every screen — nested navigators (`RNSScreenStackView` → `RNSScreenView`, often two deep) plus a drawer/root wrapper routinely bury on-screen content **40–60 levels down** before the first tappable view. In a deeply nested production app, plainly visible interactive elements sat at depths **41–62**. So `id:` and `text:` selectors silently failed to resolve, and only coordinate taps worked — which is the origin of coordinate-heavy flows on those apps. The overflow is **silent**: nothing reports "truncated", selectors just stop matching. So the new cap carries generous headroom rather than hugging the deepest observed measurement. **What it costs.** The tree itself is internal to selector resolution — consumed by `selectorToFrame`/`evaluateCondition`, never returned — so no tool result carries it. Text derived from it does reach the agent, though: `assertReason`'s `text` arm quotes the matched node's hoisted `subtreeText` verbatim into a failing `assert`/`await` reason, and nothing truncates it downstream. Descendants a depth-40 read dropped now hoist, so that quoted string grows with the cap. That is the trade — under the old cap those selectors did not resolve at all. Otherwise the cap only grows the `getFullHierarchy` payload over the native-devtools socket, which is already field-limited: ~11KB at depth 40, ~15KB at 48, and nothing at all past the tree's real depth. ## Targeting errors that could not be acted on **`Provide bundleId explicitly`** — `resolveNativeTargetApp`'s own errors close with that advice. A flow selector step cannot follow it: the call hardcodes auto-targeting. Each failure now carries the remedy that actually exists: - **ambiguous connected set** — foreground the intended app with `launch-app` (which does not terminate, so the instrumentation it already has survives), and clear the others with `xcrun simctl terminate` (argent exposes no terminate tool, and `restart-app` would just bring that app back to the front). The command is resolved through `simctlTargetForUdid`, so it carries the device's own CoreSimulator `--set` — a simulator from a configured `ios.additionalDeviceSets` resolves — and passes the same entitlement gate an actual spawn does. An external provider (#735) that grants `native-devtools` but withholds `simctl` gets the clause dropped whole, rather than a command argent itself would refuse and its device-set path quoted back. - **a lone connected app that is not foreground** — same foreground remedy; it is already instrumented, so a relaunch would only discard state. - **the state probe failed while connections are live** — which app is missing decides the remedy. While the app the flow launched is still in the live connections map it is merely suspended: foreground it, do not relaunch, since a relaunch discards the state the flow built up and cannot help when another app is the stale one. When it is *absent* from that map it is the one thing a relaunch fixes, so the reason names it and sends it to `restart-app` — `launch-app` cannot instrument a process that is already gone. - **no connected app** — this is the one state a relaunch fixes, through `restart-app` or a flow `launch` step. **`launch-app, or a flow launch step`** — this was wrong in a way that wastes a cycle. `launch-app` does **not** terminate first, so against an app already running from Metro/Expo, Xcode, or its home-screen icon it only foregrounds that same uninstrumented process, and the next read fails identically. Only `restart-app` (terminate + relaunch) guarantees an instrumented launch whatever was already running. These reasons are repeated per step — the recorder embeds one in the warning for every captured tap, and a failing `await:` repeats it per poll — so the two that enumerate connected apps are capped at two entries plus a count of what was left out, and the whole set is held under a documented ceiling by a test that iterates every branch. That ceiling is 800 characters. It is set by the `unregistered` reason, which measures 738 plus the bundle id, so it holds for the ids seen in practice and not for every one: a 63-character id clears it. Every figure quoted at `MAX_LISTED_APPS` and `MAX_TARGETING_REASON_CHARS` was re-measured against the current code. `FailureError` data is preserved when these messages wrap an underlying error, so the failure code still reaches callers that switch on it. ## Recorder: role-only selectors A tapped node with no identifier and no visible text replays on role alone, which holds only while that element keeps winning `selectorToFrame`'s ranking. The raised depth cap makes this more common — an unlabeled icon the old cap truncated away, which left `nodeAtPoint` to pick its `testID` container, is now present and is the smaller frame under the tap. The recorder now warns instead of recording that silently. ## Launch gate: why the wait is per bundle `treeSourceGate` already waits for every bundle, `com.apple.*` included, and withholds only the *verdict* for a non-injectable one. This PR documents why that wait is load-bearing rather than a readiness formality: it is what ties the launched bundle to the app a later selector step auto-targets, since `resolveNativeTargetApp(api, undefined)` never compares the two. Skipping it per bundle could hand the next step a different app's hierarchy and still report the run green. The matching fallback advice lands in the no-connected-app reason: for a `com.apple.*` bundle that never connects, drive the app with raw point taps and `tool: await-ui-element` steps, which read the AX tree instead of selectors. ## Restack notes - `a71c0f0` keeps the stacked version's per-file insert/delete counts unchanged. `54fe761` is the review pass on top: the entitlement gate above, the launched-app-gone remedy, four comments this branch left stale or overstated, and the re-measured figures. - No docs update needed — nothing in `packages/docs` documents the depth cap, the targeting reasons or the recorder warning. - Conflict resolved in `flow-ios-tree.ts`: #944 (physical iOS support) landed new device-tree functions in the same region. Both blocks kept — the targeting helpers sit next to `queryFullHierarchyTree`, which they serve; the device block follows. - **One hunk dropped:** a 35-line test in `flow-hidden-blank-reads.test.ts` asserting that an `exists` miss does not quote hoisted `subtreeText`. It is written against `compatibilityMissNote` / "typographic variant", which #720 introduces and `main` does not have. Worth re-adding to that PR, or here once #720 lands. - Verified on this base: `tsc --build` and `typecheck:tests` clean; 254 tests across the 4 touched test files pass; full tool-server suite 433 files / 6190 passed / 1 skipped (one run showed a single unrelated load flake, green on a clean re-run); eslint, prettier and both knip passes clean. All 8 CI checks green on `54fe761`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014dA4nMpWrzECNhV6if4YXZ <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - iOS hierarchy inspection now supports substantially deeper views, improving selector and condition handling for deeply nested content. - iOS targeting failures provide clearer, actionable recovery guidance, including relevant app and device-state details. - System-app flows can continue successfully when apps do not establish a connection, including coordinate-based actions. - **Bug Fixes** - Captured tap steps now warn when selectors rely only on an element’s role. - Related selector warnings are combined into a single concise warning. - iOS failure messages no longer include misleading recovery advice. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Hubert Gancarczyk <claude-hubert.gancarczyk@swmansion.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
42317b32ed |
test(tool-server): flood with multi-byte output so the UTF-8 cap is pinned (#1150)
Fixes #849 **Cause:** both `runVega` overflow fakes flood with `"x".repeat(4096)` - pure ASCII, so `Buffer.byteLength === length` and the `maxOutputBytes: 100` cap trips under either reading. Replacing both `Buffer.byteLength(chunk, "utf-8")` in `vega-cli.ts` with `chunk.length` leaves `vega-cli-timeout.test.ts` green 12/12. **Fix:** flood with 60 `U+2713` instead - 60 characters, 180 UTF-8 bytes. Over the cap on bytes, under it on length, so only a byte-counted cap still rejects. Same class: `ndjson-socket`'s dropped-frame `bytes` had the same ASCII fixture, and asserted it with `Buffer.byteLength` itself, so a `raw.length` regression passed. Its frame is multi-byte with a literal expected count. Left alone: `LogFileWriter.fileSizeBytes` also sums `Buffer.byteLength`, but its only assertions are `toBeGreaterThan(0)` / `toBe(0)` - the value is not pinned at all, a wider test gap than this issue. Tests only - no production code, no tool/CLI/config/flow change, so no docs update needed. <details> <summary>Verification</summary> All from the worktree root on `@argent/tool-server`, at base `e353e9bd8`. Mutation (`stdoutBytes += chunk.length`, `stderrBytes += chunk.length`, `bytes: raw.length`) with the tests as they stand on `main`: ``` Test Files 2 passed (2) Tests 18 passed (18) ``` Same mutation, with this change: ``` × reports an unparseable frame with byte length and a sanitised preview, then continues × rejects + reaps when output exceeds the cap, classified as `subprocess` × rejects + reaps when STDERR exceeds the cap, classified as `subprocess` AssertionError: expected 13 to be 15 AssertionError: expected 'timeout' to be 'subprocess' AssertionError: expected 'timeout' to be 'subprocess' Tests 3 failed | 15 passed (18) ``` Source restored, everything green: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - `434 passed (434)` files, `6171 passed | 1 skipped` - `npx prettier --check` on both files - clean `npx eslint` on the two files could not run locally: it fails resolving `packages/docs/tsconfig.json` -> `@docusaurus/tsconfig`, a pre-existing local-install gap unrelated to this diff. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4a5bee4a50 |
fix(tool-server): stop boot-electron piping the app's stdout into nothing (#1131)
Fixes #905 **Cause:** `bootElectronApp` spawned the app with `stdio: ["ignore", "pipe", "pipe"]` and never read stdout. An unread pipe is buffered, not dropped, so once the OS pipe buffer fills the app blocks on its next `write(1, ...)` and CDP stops answering. The same spawn sets `ELECTRON_ENABLE_LOGGING: "1"`, which is what makes it write there. **Fix:** `stdio: ["ignore", "ignore", "pipe"]`. stderr keeps its pipe - the existing forwarding listener drains it. **Verified:** new `boot-electron-stdio.test.ts` asserts the spawned stdio and then spawns a real child under those exact options that writes 2 MiB to stdout; it must exit. Both cases fail on the unfixed line and pass with it. Full `@argent/tool-server` suite green (369 files, 4834 tests). No docs change: this is the tool-server's internal spawn, no tool, CLI, config key or flow-file surface moves. <details> <summary>Discrimination proof and sibling spawn sites</summary> Reverting only the source line, both cases fail: ``` FAIL test/boot-electron-stdio.test.ts > leaves stdout unpiped and keeps stderr piped expected [ 'ignore', 'pipe', 'pipe' ] to deeply equal [ 'ignore', 'ignore', 'pipe' ] FAIL test/boot-electron-stdio.test.ts > lets a child that floods stdout run to completion AssertionError: expected false to be true (the 2 MiB child never exits) Tests 2 failed (2) ``` With the fix: `Tests 2 passed (2)`; package suite `Test Files 369 passed, Tests 4834 passed | 1 skipped`. Also ran: `npx tsc --build`, `npm run typecheck:tests -w @argent/tool-server`, `npx prettier --write` on both files. Root eslint could not run in this checkout (`packages/docs/tsconfig.json` needs `@docusaurus/tsconfig`, which the root install skips). Other spawns that pipe a stream, and why they are left alone: - `argent-tools-client/src/launcher.ts` already calls `child.stdout?.resume()` once readline stops consuming - the precedent this fix follows. - `blueprints/simulator-server.ts` and `blueprints/android-devtools.ts` read stdout with readline and `rl.close()` it at settle, leaving a piped stdout unread for the rest of a long-lived child. Same end state, but whether either child writes to stdout after its ready marker is unverified without a device, so it is not changed here. - `boot-device.ts` (Windows) points stdout at a real log fd, `preview-window.ts` ignores stdout, and the `execFile`-style spawns are short-lived with a reader. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
643525f372 |
fix(tool-server): bound a CDP HTTP probe that no caller has armed a signal for (#1132)
Fixes #895 **Cause** - the CDP discovery helpers take an optional `AbortSignal`; `connectCdp`, the tab manager and the Electron ready-wait pass none, so a debug port that completes the TCP handshake and never answers rides undici's 300s headers timeout, past the 30s MCP per-attempt cap. **Fix** - `fetchJson` arms `AbortSignal.timeout(5s)` when the caller supplies no signal, and classifies the resulting `TimeoutError` as `CHROMIUM_CDP_UNREACHABLE` with `network_failure: "timeout"`, so the caller gets the `cdp_unreachable` guidance instead of an aborted call. Fixed at the shared `fetchJson` rather than inside `connectCdp`, so every unsignalled call site is bounded at once: `connectCdp` (x2), `tabs.ts` (`listPageTargets`, `browserWebSocketUrl`) and `boot-electron`'s `waitForCdpReady` (whose 30s deadline was never re-checked once a probe stalled). Callers that arm their own signal - `probePort` at 800ms, `flow-run`'s liveness re-probe - are unchanged. Left alone: `CDPClient.connect`'s WebSocket handshake has no timeout either, but it is reached only after the port has already answered CDP twice - a different failure, worth its own issue. **Docs** - none needed: no tool, CLI flag, config key, flow directive or user-facing capability changes. <details> <summary>Repro and verification</summary> Repro against a bare `net` server that accepts and writes nothing, driving `connectCdp`: ``` before: STILL PENDING at 20001ms after: threw after 5005ms: FailureError Chromium CDP discovery: GET http://127.0.0.1:53169/json/version timed out. Something is holding port 53169 without answering CDP. ``` The new case in `failure-classification.test.ts` discriminates - with the source change reverted it does not fail on an assertion, it hangs: ``` FAIL packages/tool-server/test/failure-classification.test.ts > classifies a port that accepts and never answers as CHROMIUM_CDP_UNREACHABLE Error: Test timed out in 20000ms. Error: Hook timed out in 10000ms. (afterEach, waiting on the stalled socket) ``` Checks, all from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - 368 files, 4833 passed, 1 skipped - `npx prettier --write` on both changed files - `npx eslint` on the changed source - clean </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d6f6d3dc9c |
optimization(android-emulator-setup+metro-debugger): simplify instructions (#1080)
# Why Some instructions are vague, duplicated, or they can be easily derived from the context. # What This MR refines android-emulator-setup and metro-debugger skills. # Note Not tested. |
||
|
|
0896ea3417 |
fix(registry): snapshot the listener set before an emit walks it (#1136)
Fixes #870 **Cause:** `TypedEventEmitter.emit` iterated the live `Set`, so a listener detached by an earlier listener was skipped, and one attached by an earlier listener ran inside the same emit. Node's `EventEmitter` does neither - it snapshots first. **Fix:** iterate a copy of the set (4 production lines). **Class:** `packages/registry/src/event-emitter.ts:30` was the only site iterating a mutable listener collection in the repo. `lens-pty.ts`'s `observers` array is append-only (no detach API), so it cannot hit this. **Docs:** none needed - no tool, CLI, config key, flow file or user-facing capability changed. <details> <summary>Verification</summary> New test `packages/registry/tests/event-emitter.test.ts` pins both directions. Reverting only the source fix (`git stash push -- packages/registry/src/event-emitter.ts`) makes both cases fail: ``` FAIL tests/event-emitter.test.ts > still calls a listener an earlier listener detached AssertionError: expected [ 'A' ] to deeply equal [ 'A', 'B' ] FAIL tests/event-emitter.test.ts > defers a listener an earlier listener attached to the next emit AssertionError: expected [ 'C', 'LATE' ] to deeply equal [ 'C' ] Test Files 1 failed (1) Tests 2 failed (2) ``` With the fix restored: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/registry` - clean - `npm test -w @argent/registry` - 8 files, 122 tests passed - `npm test -w @argent/tool-server` - 368 files, 4832 passed / 1 skipped (the main consumer of this emitter, run against the rebuilt registry dist) - `npx prettier --write` on both changed files - unchanged </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f4ff77e9e8 |
fix(debugger): stop LogFileWriter retaining console lines no reader can reach (#1126)
Fixes #892 **Cause** - when `LogFileWriter`'s file cannot be created, `ready` stays false for the writer's life (nothing reopens it), so `write()` pushed every formatted line into `writeBuffer`. Both drains are closed in that state: `flushBuffer()` returns on `!this.ready`, and `readAll()` returns `[]` before reaching it. Up to `MAX_ENTRIES` (50 000) lines per wedged session, unreadable and never freed. **Fix** - drop the dead buffering path; a line with no file is not retained. Entry counts, level tallies and clusters are tracked separately, so `getStats()` / `getClusters()` / `debugger-log-registry` are unchanged, as is `readAll()`, which already returned `[]` in this state. `writeBuffer` / `flushBuffer` existed only in this file - no sibling writer has the same shape. No docs change: no tool, CLI flag, config key, flow directive or user-visible behaviour changed. <details> <summary>Verification</summary> New test in `packages/tool-server/test/debugger/log-file-writer.test.ts` chmods a temp-`HOME` `~/.argent/tmp` to `0555`, writes one entry, and asserts the writer holds no line array while the counts and clusters still record it (skipped on Windows and under uid 0). Without the source fix: ``` FAIL packages/tool-server/test/debugger/log-file-writer.test.ts > LogFileWriter whose log file cannot be created > keeps no copy of a line it could not write AssertionError: expected [ '[L:0] 2024-03-09T16:00:00.000Z LOG…' ] to deeply equal [] Tests 1 failed | 19 passed (20) ``` With it: ``` npx tsc --build # clean npm run typecheck:tests -w @argent/tool-server # clean npm test -w @argent/tool-server # 368 files, 4833 passed | 1 skipped npx prettier --write <changed files> # unchanged ``` </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
46d968abbf |
style(docs): reflow the tool-server env-var table (#1153)
`prettier --check .` fails on `main`, so every push since #1151 has a red **Format** job. #1151 shortened the `ARGENT_SCREENSHOT_SCALE` description. That cell was the widest in the column, so the whole table was left padded to a width nothing occupies any more. Reflowed it with Prettier; whitespace only. | Before | After | | --- | --- | | `Format` ❌ on `main` (4 consecutive pushes) | `npx prettier --check .` → exit 0 | <details> <summary>Verification</summary> ``` $ npx prettier --check . # the exact CI step Checking formatting... All matched files use Prettier code style! EXIT=0 ``` Re-running `prettier --write` produces no further change (idempotent). `git diff -w` reduces to the separator row alone; no cell content changed. Docs still build and the table still renders: ``` $ npx docusaurus build # packages/docs [SUCCESS] Generated static files in "build". BUILD_EXIT=0 $ # parsed build/docs/reference/configuration/index.html rows=8 (header + 7), 3 cells each, all values intact ``` </details> |
||
|
|
c841def5f2 |
fix(native-devtools): close the TCP listener when startProxy throws (#1127)
Fixes #882 **Cause** - the TCP branch of the `native-devtools` factory binds an ephemeral 127.0.0.1 listener and then `await`s `host.startProxy(...)` outside any try/catch; on an `ios-remote` device that call rethrows when the tunnel is refused, and the registry sets `node.instance` only after `factory()` resolves, so `dispose` (which would `server.close()`) never runs. The listener, its fd and its port leak, once per retry. **Fix** - close the server and rethrow, matching what `dispose` does and what the unix branch (`bindNativeDevtoolsUnixSocket`) already does on a bind error. **Verified** - new test drives the factory with a remote host whose `startProxy` throws, then rebinds the port it was handed: EADDRINUSE before the fix, free after. Full `@argent/tool-server` suite green (434 files / 6164 tests) on the branch with main merged in, `tsc --build` and `typecheck:tests` clean. Siblings from the same review are filed and left to their own issues: #883 (ax-service), #884 (chromium-server), #885 (android-devtools, tv-control). No docs change: no tool, CLI flag, config key or flow-file surface moves. <details> <summary>Repro on the unfixed source</summary> ``` $ git stash push -- packages/tool-server/src/blueprints/native-devtools.ts $ npx vitest run packages/tool-server/test/blueprints/native-devtools-tcp-proxy-leak.test.ts FAIL native-devtools-tcp-proxy-leak.test.ts > closes the listener when startProxy throws AssertionError: promise rejected "Error: listen EADDRINUSE: address already… { …(5) }" instead of resolving Caused by: Error: listen EADDRINUSE: address already in use 127.0.0.1:51306 ``` With the fix restored: 1 passed. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
08188aad11 |
fix(tool-server): name the bundleId alphabet launch-app enforces on Chromium (#1129)
Fixes #901 **Cause.** `launch-app`'s `bundleId` description called the value an "arbitrary tag" on Chromium, but the field's `.regex(BUNDLE_ID_PATTERN, …)` is validated by zod before the platform dispatch runs - so Chromium is bound by it exactly as iOS and Android are. An agent that took the description at its word and passed a display name (`"My App"`) got a rejection naming a rule the description said did not apply. **Fix.** The Chromium clause states the alphabet the regex enforces instead of claiming the value is free-form. `BUNDLE_ID_PATTERN` and the rejection message are untouched - the value really is inert on Chromium, only not unconstrained. **Class check.** The description is the sole site: over every advertised tool schema, `launch-app.bundleId` is the only pattern-constrained parameter whose description promised an unconstrained value (`restart-app` / `reinstall-app` / `settings-permissions` `bundleId`, both `activity` fields and `profiler-load.session_id` all name a form). The new test walks the whole catalogue, so a future one has to as well. **Docs.** No docs change needed - `reference/tools.mdx` lists `launch-app` by one-line summary and documents no parameter constraints. <details> <summary>Verification</summary> The test is appended to the existing catalogue-wide schema contract, not a new file. Discrimination proved by stashing the source change and re-running: ``` × launch-app.bundleId: states a constraint rather than promising an unconstrained value Tests 1 failed | 84 passed (85) ``` With the fix in place, from the worktree root: ``` npx tsc --build OK npm run typecheck:tests -w @argent/tool-server OK npm test -w @argent/tool-server 368 files, 4839 passed | 1 skipped npx prettier --write <changed files> unchanged ``` `npx eslint` could not run in this checkout (`packages/docs/tsconfig.json` cannot resolve `@docusaurus/tsconfig` without a separate `npm ci` in that workspace, a pre-existing local-environment issue); the ESLint CI job covers it. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c804f88aeb |
fix(tool-server): give the CDP binding timeout the same recovery guidance as its sibling (#1114)
Fixes #947 **Cause.** `DEBUGGER_CDP_BINDING_TIMEOUT` rejected with a bare `Binding response for requestId=... timed out`, while the sibling `DEBUGGER_CDP_REQUEST_TIMEOUT` in the same file carries a full paused-vs-frozen recovery. It is the timeout where *paused* matters most: `evaluateWithBinding` dispatches the script with `awaitPromise: false`, so a runtime stopped at a breakpoint accepts the evaluate and never runs the callback. **Fix.** Give the binding timeout the same shape of guidance - check/resume the debugger, else restart the app, do not retry in a loop (each attempt burns the full timeout). **Deliberately not done:** the issue also suggests mapping the code into `NOT_CONNECTED_CODE_MAP`. That map is only consulted by `debugger-status` / `debugger-log-registry`, and neither calls `evaluateWithBinding` (only `debugger-component-tree` and `debugger-inspect-element` do), so the entry would be unreachable. The message is the only surface an agent actually sees. **Class check:** `evaluateWithBinding` is the only other timeout surface in `cdp-client.ts`; `chromium-server/cdp-session.ts` has no bare timeout message. The wording stays platform-neutral ("restart the app", not `restart-app`) because `cdp-client` is shared with the Chromium blueprint. **Docs:** none needed - no tool, CLI flag, config key or flow directive changed; this is an error-message string. <details> <summary>Verification</summary> Test added to `packages/tool-server/test/metro/cdp-client.test.ts`: the fake CDP server answers the evaluate and never fires the binding, then the rejection is asserted for the code and each guidance clause. Discriminates - with the source change stashed: ``` FAIL test/metro/cdp-client.test.ts > CDPClient > failure signal classification > an unanswered binding rejects with DEBUGGER_CDP_BINDING_TIMEOUT and paused-vs-frozen recovery AssertionError: expected 'Binding response for requestId=req-t …' to match /paused at a breakpoint/ + Received: "Binding response for requestId=req-t timed out" ``` Ran from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - 368 files, 4833 passed, 1 skipped - `npx prettier --write` on both changed files - unchanged - `npx eslint` on the changed src file - clean (the test file only hits the known local `packages/docs` tsconfig resolution error) </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b926abcb84 |
docs(skills): list the log-file level tokens the writer actually emits (#1123)
Fixes #912 **Cause:** the flat-log table listed `WARN `, which `LogFileWriter` only writes for the level `warn` - a level CDP never sends. `console.warn` arrives as `warning` (written `WARNING`), `console.assert` as `assert` (`ASSERT`), neither documented. An agent grepping §5's documented token finds nothing and reports no warnings. **Fix:** the `<LEVEL>` row now lists the tokens the writer emits and states the rule (uppercased CDP level, padded to at least 5 chars, never truncated). `LEVEL_DISPLAY` is gone: every key mapped to exactly what the `toUpperCase().padEnd(5)` fallback already computes, so it changed no output - it only kept `warn: "WARN "` alive as the token this table was copied from. | before | after | | --- | --- | | `` `ERROR`, `WARN `, `LOG ` `` - Uppercase, padded to 5 chars | `` `ERROR`, `WARNING`, `LOG `, `INFO `, `DEBUG`, `ASSERT` `` - Uppercased CDP level, padded to at least 5 chars, never truncated | **Verified:** new test drives the real `LogFileWriter` and pins the table to what lands on disk; it fails on the old row (3/3) and passes on the new one. Full `@argent/tool-server` suite, `tsc --build`, `typecheck:tests`, prettier all green. Docs site needs no update - the level column is documented only in this skill; `argent-react-native-app-workflow` §4.2 defers to it rather than restating it. <details> <summary>Checks</summary> Discrimination (docs change stashed, test kept): ``` FAIL documents the token console.warn produces expected [ 'ERROR', 'WARN ', 'LOG ' ] to include 'WARNING' FAIL documents the token console.assert produces expected [ 'ERROR', 'WARN ', 'LOG ' ] to include 'ASSERT' FAIL documents no token a CDP level cannot produce `WARN ` is documented but never written Tests 3 failed (3) ``` Restored: `Tests 3 passed (3)`. Package suite: `Test Files 1 failed | 368 passed`, `Tests 1 failed | 4834 passed` - the one failure is `flow-type-focus.test.ts` asserting a 500 ms settle and measuring 499 ms; it passes on its own re-run and touches nothing in this change. ``` npx tsc --build # clean npm run typecheck:tests -w packages/tool-server # clean npx prettier --write <changed files> # clean ``` </details> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
59daf7c67d |
fix(screenshot): give ARGENT_SCREENSHOT_SCALE the 0.01 floor its parameter enforces (#1151)
Fixes #816 **Cause** - `getScreenshotScale()` guarded on `n > 0 && n <= 1`, while the `scale` parameter of the same tool is `min(0.01).max(1.0)`; a value in `(0, 0.01)` was accepted and shrank a capture towards zero pixels, so `screenshot-diff` refused the pair and reported `dimension_mismatch` rather than a configuration problem. An out-of-range value was also swallowed with no warning, so `ARGENT_SCREENSHOT_SCALE=30` silently read as the default. **Fix** - the guard is now `n >= 0.01 && n <= 1`, and a rejected value is reported once per distinct value on stderr, naming the value and the accepted range. **Verified** - the three new floor cases and the warning assertion fail on the unfixed function and pass with it; full `@argent/tool-server` suite green. Docs: `packages/docs/docs/reference/configuration.mdx` - the `ARGENT_SCREENSHOT_SCALE` row said `0` to `1` and a `0.3` default; it now says `0.01` to `1`, default `0.25`, and that an out-of-range value is ignored with a warning. No other reference or feature page mentions the variable. Class check: `getScreenshotScale()` is the only env-to-scale parser in the tool-server (iOS, Android, tvOS and Vega all read it), so there is no sibling site to fix. <details> <summary>Discriminating-test proof and checks</summary> Source fix reverted, test file kept: ``` FAIL test/screenshot-default-scale.test.ts > falls back to 0.25 below the floor for "0.009" expected 0.009 to be 0.25 FAIL test/screenshot-default-scale.test.ts > falls back to 0.25 below the floor for "1e-3" expected 0.001 to be 0.25 FAIL test/screenshot-default-scale.test.ts > falls back to 0.25 below the floor for "0.0001" expected 0.0001 to be 0.25 FAIL test/screenshot-default-scale.test.ts > names the ignored value and the accepted range, once per value expected "warn" to be called 1 times, but got 0 times Test Files 1 failed (1) Tests 4 failed | 9 passed (13) ``` Fix restored: 13 passed. Checks run from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - 368 files, 4837 passed, 1 skipped - `npx prettier --check` on the three changed files - clean `npx eslint` on the changed files could not run locally: the root install skips `packages/docs`, so its `tsconfig.json` fails to resolve `@docusaurus/tsconfig` and the shared project parse errors out. Same reason `npx docusaurus build` was not run for the one-cell docs table edit. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cee2433758 |
test(tool-server): bind the ten remaining test servers to loopback (#1142)
Fixes #853 **Cause:** `listen(0)` with no host binds every interface, so ten ephemeral-port test servers were LAN-reachable for as long as a suite ran. **Fix:** pass `"127.0.0.1"` at all ten sites, matching the twenty-two siblings that already do. Adds a guard test that scans `src/` and `test/` for any `.listen(0)` missing the host, so a new one fails CI. No sites left behind: `grep -rn "\.listen("` over every package turns up only these ten, the twenty-two already-loopback sites, the unix-socket listens in `native-devtools.ts` / `ax-service.ts` (no host applies), and `src/index.ts` which already passes `HOST`. **Docs:** none needed - test-only, no tool, CLI, config key, flow file, or user-facing capability changes. <details> <summary>Verification</summary> Guard test discriminates - with the ten fixes stashed it names every one of them: ``` + [ + "test/http-upload.test.ts:167", + "test/debugger/debugger-status-not-connected.test.ts:76", + "test/debugger/log-registry-not-connected.test.ts:73", + "test/debugger/metro-cdp-harness.ts:90", + "test/metro/integration.test.ts:154", + "test/metro/legacy-runtime-guards.test.ts:68", + "test/metro/multi-device.test.ts:90", + "test/metro/teardown-log-history.test.ts:80", + "test/metro/vega-target-routing.test.ts:98", + "test/network/network-integration.test.ts:327", + ] ``` `http-upload.test.ts` needed two extra lines: `listen(port)` binds synchronously, but `listen(port, host)` defers behind a DNS lookup, so `server.address()` came back `null`. It now awaits `listening`. The client `http.request` had no `host` (defaulting to `localhost`), and its `error` handler is a no-op, so a failed dial would have made the assertion pass vacuously - pinned to `127.0.0.1`. Probed that the request still reaches the server and a partial file is really created before cleanup: ``` PARTIALS ["argent-upload-d567a1aa-75a0-44e3-9e06-6d42544be1ee.tar.gz"] ERR Error: socket hang up ``` Ran from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - 369 files, 4833 passed, 1 skipped - `npm run knip` - clean - `npx prettier --write` on the changed files - no diff </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e353e9bd8d |
docs(skills): stop promising clusters[].message is an exact grep needle (#1107)
Fixes #1001 `argent-metro-debugger` called `clusters[].message` "the exact text which you may look for". It is a truncated (200 char), grouped (first 80 chars) prefix that keeps newlines the flat log line collapses - so `grep -F` of it can match nothing while `count` says N. Prose only, no code change. Nothing in `packages/docs/` claims anything about `clusters`. <details> <summary>Verification</summary> Three tests added to `log-file-writer.test.ts` pin the grouping, the truncation and the newline mismatch. They pass on unmodified source (the change is prose), so each was mutation-tested: | Mutation in `log-file-writer.ts` | Result | | --- | --- | | `CLUSTER_KEY_LENGTH = 80` -> `200` | 1 failed | | `entry.message.slice(0, 200)` -> `slice(0, 400)` | 1 failed | | `entry.message.slice(0, 200)` -> `flatMessage.slice(0, 200)` | 1 failed | `npx tsc --build`, `npm test -w @argent/tool-server` and prettier all clean after merging `main`. </details> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e8bc673714 |
docs(skills): drop the breakpoint capability argent has no tool for (#1128)
Fixes #906 **Cause:** the cross-skill index in `argent-test-ui-flow` listed **Breakpoints** first in the `argent-metro-debugger` row. `packages/tool-server/src/tools/debugger/` ships seven tools and none sets, lists, removes or steps over a breakpoint - no `Debugger.setBreakpoint*` / `pause` / `resume` / `step*` call exists outside a test mock. An agent routed there by that row finds no tool and no explanation. **Fix:** the row names three capabilities that do exist. A test parses the row and rejects any capability with no tool behind it. | before | after | | --- | --- | | `argent-metro-debugger` - Breakpoints, console logs, JS evaluation | `argent-metro-debugger` - Console logs, JS evaluation, component inspection | **Verified:** the new test fails on the old row (`"Breakpoints" is advertised to agents but no argent tool implements it`) and passes on the new one; full `@argent/tool-server` suite, `tsc --build`, `typecheck:tests` and prettier all green. Docs site needs no update - `packages/docs/` never mentions breakpoints, and the only other index row for this skill (`argent-react-native-app-workflow` line 243) already lists component inspection, console logs and JS evaluation. <details> <summary>Checks</summary> Discrimination (skill row stashed, test kept): ``` FAIL test/debugger/skill-index-capabilities.test.ts > advertises only capabilities the row's vocabulary covers AssertionError: "Breakpoints" is advertised to agents but no argent tool implements it: expected false to be true Tests 1 failed | 1 passed (2) ``` Restored: `Tests 2 passed (2)`. ``` npx tsc --build # clean npm run typecheck:tests -w packages/tool-server # clean npm test -w packages/tool-server # Test Files 369 passed, Tests 4834 passed | 1 skipped npx prettier --check <changed files> # clean ``` Class check - `grep -rn -i breakpoint packages/skills packages/docs` now returns nothing; the surviving mentions in `packages/tool-server/src` are state descriptions in timeout guidance ("may be frozen, or paused at a breakpoint"), about a breakpoint the user set in their own devtools. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2e86b1a3e0 |
docs(skills): leave one rule for the debugger console log file (#1121)
Fixes #907 **Cause.** `argent-metro-debugger/SKILL.md` offered `Grep`/`Read` on the `debugger-log-registry` log file in its two lookup tables and its numbered workflow, while the "Never `Read` the log file directly" rule sits further down under file-format tips - so the agent hits the permission before the prohibition. That file holds up to 50 000 unbounded console lines (`MAX_ENTRIES` in `log-file-writer.ts`), which is exactly the context burn the rule exists to prevent. **Fix.** Drop `Read` from every permissive mention, leaving `Grep` and the single prohibition. Same shape fixed in the two siblings that repeat the sentence: `argent-react-native-app-workflow/SKILL.md` and `argent-react-native-profiler/references/diagnostic-tools.md`. A repo-wide grep finds no other occurrence. **Test.** A new invariant in `packages/argent-installer/test/skills-log-file-guidance.test.ts` scans every bundled skill markdown file and fails on any line that offers `Read` for the log file - matching on the line or on the heading chain above it, so the workflow step is covered too. **Docs.** No tool, CLI, config key or flow file changed - the skills are themselves the user-facing prose, so `packages/docs/` needs no update. <details> <summary>Verification</summary> Discriminating run - source fix stashed, test kept: ``` $ git stash push -- packages/skills/skills && npx vitest run test/skills-log-file-guidance.test.ts --root packages/argent-installer + "argent-metro-debugger/SKILL.md:52: | `debugger-log-registry` ... Then use `Grep`/`Read` on the flat log file for details. ..." + "argent-metro-debugger/SKILL.md:92: 2. **Search the file** using `Grep` or `Read` with patterns from the response." + "argent-metro-debugger/SKILL.md:133: | Console log overview | `debugger-log-registry` (summary + log file path for `Grep`/`Read`) |" + "argent-react-native-app-workflow/SKILL.md:162: ... then `Grep`/`Read` to search. ..." + "argent-react-native-profiler/references/diagnostic-tools.md:27: ... Use `Grep`/`Read` on the log file to filter by level ..." Tests 1 failed (1) ``` Restored, all green: ``` $ npx tsc --build # exit 0 $ npm run typecheck:tests -w @argent/installer # exit 0 $ npm test -w @argent/installer # Test Files 18 passed, Tests 556 passed $ npx prettier --write <changed files> # clean ``` </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7b8c38b49b |
docs(skills): say the [L:<id>] log anchor must be matched literally (#1120)
Fixes #916 **Cause:** the flat-log table calls `[L:42]` a "unique grep anchor" and never says to match it literally - unescaped, `[L:42]` is a character class, and since every log line starts with `[L:`, it matches the whole file. **Fix:** table cell points at the search rule, plus one bullet telling the agent to use `grep -F` (or `\[L:42\]`) for bracketed text like `[L:<id>]` and `[object Object]`. **Verified:** wrote 46 entries through the real `LogFileWriter` and ran both engines against the file - `grep`/`rg` with the bare anchor return 46 lines, `-F` returns 1. Prose-only change to one SKILL.md, so no test applies; `npx tsc --build` and `prettier --check` are green. No docs-site page documents the log format or the anchor, so `packages/docs` needs no update. <details> <summary>Repro output</summary> ``` $ head -2 repro.log [L:0] 2026-03-17T14:31:00.000Z LOG - | filler 0 [L:1] 2026-03-17T14:31:00.000Z LOG - | filler 1 lines: 46 grep -c '[L:42]' -> 46 grep -cF '[L:42]' -> 1 grep -c '\[L:42\]' -> 1 rg -c '[L:42]' -> 46 rg -cF '[L:42]' -> 1 grep -c '[object Object]' -> 46 grep -cF '[object Object]'-> 1 ``` Entry 42 is the only `TARGET ENTRY FORTY TWO` line; `[object Object]` appears in exactly one message. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c8d564497d |
fix(tool-server): bound the device-shutdown subprocesses (#1118)
Fixes #945 **Cause:** the four shutdown spawns in `utils/device-shutdown.ts` (`xcrun simctl shutdown`, `adb emu kill`, in both `shutdownOwnedDevice` and `shutdownDevice`) were awaited with no `timeout`, so a wedged adb daemon or CoreSimulatorService leaves the await unsettled forever - no error, no reap, teardown hangs. **Fix:** pass `{ timeout: 30_000, killSignal: "SIGKILL" }` at all four sites - 30s is `runAdb`'s ceiling, SIGKILL because both an adb client blocked on its daemon and a simctl blocked on CoreSimulator ignore execFile's default SIGTERM (`ADB_KILL_SIGNAL` / `SIMCTL_KILL_SIGNAL` document this). Docs: none needed - no tool, CLI, config key or user-facing behaviour changes. <details> <summary>Scope, tests, checks</summary> **Left alone (different shape, not the same class):** `tools/devices/boot-device.ts` `shutdown`/`boot`/`bootstatus`, `restart-app`, `reinstall-app` - tool-level flows with their own error handling and much longer legitimate budgets; a single 30s shutdown ceiling would be wrong there. **Test discriminates.** `test/device-shutdown.test.ts` now asserts the options object on every dispatch. With the source change stashed, 5 of 13 fail - one per production call site: ``` × iOS -> xcrun simctl shutdown <udid> × Android -> resolved adb -s <serial> emu kill × Android with unresolvable adb -> falls back to bare "adb" × iOS -> ok:true and simctl argv × Android emulator -> ok:true and resolved adb emu kill Tests 5 failed | 8 passed (13) ``` Restored: 13 passed. **Checks (worktree root):** - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - 368 files, 4832 passed, 1 skipped. One run out of four had a single unrelated flake that did not reproduce on re-run. - `npx prettier --write` on both changed files - `npx eslint` on both changed files - only the pre-existing `packages/docs` tsconfig resolution error this checkout has </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Device shutdown commands now stop waiting after 30 seconds if the underlying process becomes unresponsive. - Stalled shutdown processes are forcefully terminated to prevent shutdown operations from hanging indefinitely. - Applies consistently to both iOS simulators and Android emulators, including user-initiated and automatic shutdown flows. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8b2ca0511d |
test(configuration-core): guard the detached tmpdir removal in secrets tests (#1086)
Fixes #1075 **Cause** - both `argent-detached-` tests removed their temp dir as the last unguarded statement of the test body, so a failing assertion above skipped it (the file's `afterEach` covers `homeDir`/`projectDir` only). **Fix** - `try/finally` around each body, the form `flags.test.ts` in the same package already uses. **Verified** - with both assertions deliberately broken and a private `TMPDIR`: 2 stranded `argent-detached-*` before, 0 after, same 2 test failures either way. Package suite green (122 passed), `tsc --build`, `typecheck:tests`, prettier. Class: the same shape in `argent-installer/test/mcp-configs.test.ts` (11 sites) and `tool-server` is tracked separately by #1074 and #1077, left to those. No docs change - test-only. <details> <summary>Repro output</summary> ``` --- unfixed: Tests 2 failed | 17 passed (19) --- unfixed: residue: 2 --- fixed: Tests 2 failed | 17 passed (19) --- fixed: residue: 0 ``` ``` npm test -w @argent/configuration-core Test Files 5 passed (5) Tests 122 passed (122) ``` </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved test cleanup to ensure temporary files and directories are removed even when assertions fail. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b570f4c484 |
perf(ci): take typechecking off the unit-test critical path (#1110)
`Unit Tests` is the entire PR wall clock: **468s of run + 61s of
queue**, while the other five gating jobs finish in 7-118s. Typechecking
is 64s of that, sits in series ahead of the tests, and needs none of
their output.
| | before | after |
|---|---|---|
| typecheck (64s) | serial, inside the test job | own job, runs
alongside - **116s, parallel** |
| npm cache | none on any gating job | `cache: npm`, except knip |
| superseded PR runs | keep holding a runner | cancelled |
This is scaffolding, not the fix. The job is dominated by
`tool-server/test/flows`, and that needs its own PR - measurements and a
rejected shortcut below.
`Typecheck` is a new check. It needs adding to the required status
checks on `main`, or typecheck failures stop blocking merge.
<details>
<summary>Where the 8 minutes is</summary>
`Unit Tests` run `34367669322`, per-step:
| step | time |
|---|---|
| setup + checkout + node | 8s |
| npm ci | 12s |
| tsc --build | 29s |
| Typecheck tests (13 serial `tsc`) | 62s |
| **Run tests** | **350s** |
| everything else | 5s |
`Run tests`, per package: tool-server 317.7s, the other 12 packages 28s
combined.
Inside tool-server (368 local test files): `test/flows` is 591s across
54 files, everything else 51s across 314. Half the suite is 5 files; 80%
is 13, all flows.
Those files drive the flow engine through its real `SETTLE_TIMEOUT_MS`
(3000), `DEFAULT_ACTION_TIMEOUT_MS` (7500) and `POST_LAUNCH_SETTLE_MS`
(1500). The five worst run in isolation at **87.4s wall, 7% CPU**.
`flow-gesture-settle.test.ts` is 37 tests at a 3.02s median - the settle
window waited out per test.
</details>
<details>
<summary>npm cache, per job (install step, against the uncached
baseline)</summary>
| job | before | after |
|---|---|---|
| ESLint (docs tree) | 26s | 16s |
| Prettier check | 17s | 11s |
| Unit tests | 12s | 10s |
| Knip | 6s | 8s + 1s save |
Knip installs with `--ignore-scripts` and pulls the least of any job
here, so restore and save cost more than the download they replace. It
keeps the uncached path.
</details>
<details>
<summary>Over-subscribing the worker pool: tried on this PR,
reverted</summary>
Flooring `maxWorkers` at 8 was in the first commit here. On this PR's
own CI run it halved the suite - **317.7s -> 152.1s** - and broke one
test.
`flow-composition.test.ts` went **26.5s -> 86.8s** and blew the 60s
budget it sets for itself. It pumps a fake clock in a busy loop rather
than sleeping, so it is CPU-bound where the rest of `test/flows` is
idle, and two workers per core starve it superlinearly. Suite-wide,
`import` went 106s -> 210s for the same reason.
Reverted in the second commit. Over-subscription is only a way to hide
the sleeping; once the flow tests stop waiting in real time, one worker
per core is the right number and nothing is starved.
</details>
<details>
<summary>What actually reaches 1 minute</summary>
Vitest parallelises per file and `flow-gesture-settle.test.ts` alone is
86.5s, so bin-packing the real per-file durations gives 161s at 4
workers and 87s at 8, 16, 32 or 64. **No runner size or CI matrix shard
beats ~87s of test time.**
Getting under a minute needs the flow engine's timing made injectable,
with those tests injecting small values. 13 flow files assert absolute
durations and need individual attention.
Two shortcuts do not work:
- Scaling the engine's timing constants 10x down reaches 131s of test
time and 52.9s wall at 4 workers, but is unstable: a rerun of the same
config hung 3 files to ~600s. The fixtures' own values (`readDelayMs =
400`, flow `timeout: 600`, `DEFAULT_LONG_PRESS_MS` 800) do not scale
with it, so the relationships invert.
- `vi.useFakeTimers({ shouldAdvanceTime: true })` passes 37/37 but gives
no speedup - `shouldAdvanceTime` advances fake time 1:1 with real time
by design.
`flow-composition.test.ts` already demonstrates the pattern that does
work: fake clock plus an explicit pump loop.
</details>
Not caching `dist/`: this repo has a history of dist-skew making
assertions pass vacuously, and a stale build artifact is a worse failure
than a slow job.
No docs update needed: CI-internal, no user-facing capability changed.
|
||
|
|
3e5f198326 |
test(tool-server): scope the home the external-device suites discover against (#1093)
Fixes #1072 **Cause** - `external-devices.test.ts` deletes both `ARGENT_DISABLE_DEVICE_PROVIDERS` and `ARGENT_DEVICE_PROVIDERS` in its `beforeEach`, so any test that writes no descriptor of its own has `discoverProviders()` read the developer's real `~/.argent/providers`. **Fix** - call `scopeTempHome()` at file top level, so that fallback is a fresh, run-owned directory. `external-attach.test.ts` has the identical hook and gets the same guard. **Verified** - planting a live provider descriptor claiming the suite's `IOS_UDID` fails 3 tests before, passes after; the new test fails deterministically with the `scopeTempHome` call removed, naming the real home in its message. Full `@argent/tool-server` suite green (6164 passed). No docs change: test-only, no tool, CLI, config key or user-facing capability moves. <details> <summary>Repro and check output</summary> Repro (a descriptor in `~/.argent/providers` claiming the udid the fixtures use, plus a live server on its `apiUrl` so it survives the discovery probe): ``` × returns nothing when no provider is registered at all × is the identity for an ordinary device and performs no I/O × leaves an ordinary udid and the default set untouched Tests 3 failed | 104 passed (107) ``` Same planted home, after: ``` Test Files 2 passed (2) Tests 119 passed (119) ``` The added test discriminates - with `scopeTempHome(HOME_PREFIX)` removed and no descriptor planted at all: ``` × discovers against a providers directory this run owns AssertionError: expected '/Users/<me>/.argent/providers' to contain 'argent-external-devices-home-' Tests 1 failed | 107 passed (108) ``` Checks from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - 433 files, 6164 passed, 1 skipped - `npx prettier --write` on the three files - unchanged Sibling suites left alone: the other nine tool-server files that delete the discovery guard (`list-devices`, `metro-port`, `external-js-debugger`, `external-native-devtools`, `native-profiler-external-device`, `android-profiler-external-device`, `launch-restart-external-claim`, `simulator-watcher-external-claim`, `boot-device-external-refused`) set `ARGENT_DEVICE_PROVIDERS` in the same hook, which replaces the directory scan outright. All nine stay green under the planted home. `argent-cli/test/providers-command.test.ts` and `device-providers/test/read.test.ts` already stub `HOME`/`USERPROFILE` themselves. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fd978f302d |
test(tool-server): stop a failed beforeEach from throwing again in teardown (#1091)
Fixes #1076 **Cause** - suites that redirect `os.tmpdir()` for a whole file hoist the restorer, and the scratch directory it points at, into uninitialized `let`s that `beforeEach` assigns partway through. Vitest still runs `afterEach` when `beforeEach` throws, so teardown calls `undefined()` and hands `undefined` to `fs.rm` - bogus `TypeError`s reported beside the real failure, and a reader chases the wrong one. **Fix** - initialize what the teardown reads (`= () => {}` / `= ""`; `fs.rm("", { force: true })` is a no-op), plus a guard in `tmpdir-env.test.ts` that scans the suite for the shape so a new `redirectTmpdir` caller cannot reintroduce it. **Verified** - injected a throw ahead of the assignment in each affected `beforeEach`: before, the teardown `TypeError` rode alongside the injected error; after, only the injected error is reported. The guard lists all 9 declarations against `origin/main` and passes with the fix. `npx tsc --build`, `typecheck:tests -w @argent/tool-server`, and the full package suite (433 files, 6164 tests) are green. Docs: none - test-only change, no tool, CLI, config key, flow file or user-facing capability touched. <details> <summary>Repro, before and after</summary> `packages/tool-server/test/react-profiler/dump.test.ts`, throw injected as the first statement of `beforeEach`: Before: ``` ⎯⎯⎯⎯⎯⎯ Failed Tests 17 ⎯⎯⎯⎯⎯⎯⎯ Error: SIMULATED mkdtemp failure before assignment TypeError: restoreTmpdir is not a function ``` With only the restorer initialized - the second half of the same hook still throws: ``` Error: SIMULATED mkdtemp failure before assignment TypeError: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined ``` After (same for `screen-recording`, `profiler-load-list-e2e`, `native-profiler-freshness-e2e`, `flow-visual`): ``` Error: SIMULATED failure before assignment ``` </details> <details> <summary>Guard against origin/main</summary> ``` - [] + [ + "android-perfetto/native-profiler-freshness-e2e.test.ts: let tempDir: string;", + "android-perfetto/native-profiler-freshness-e2e.test.ts: let restoreTmpdir: () => void;", + "android-perfetto/profiler-load-list-e2e.test.ts: let scratch: string;", + "android-perfetto/profiler-load-list-e2e.test.ts: let restoreTmpdir: () => void;", + "flows/flow-visual.test.ts: let tmpDir: string;", + "react-profiler/dump.test.ts: let restoreTmpdir: () => void;", + "react-profiler/dump.test.ts: let scratch: string;", + "screen-recording.test.ts: let restoreTmpdir: () => void;", + "screen-recording.test.ts: let scratch: string;", + ] ``` </details> <details> <summary>Class check - what was left alone</summary> - `packages/telemetry/test/index.test.ts:89` `let restoreOptOut: () => void;` - same shape, no window: the assignment is the first statement of the file's first `beforeEach`, so nothing can throw ahead of it. - Roughly 90 other hoisted `let`s across tool-server tests are read in `afterEach` without an initializer (`tmpDir`, `handle`, `exitSpy`, ...) in suites that never redirect the tmpdir. That is a repo-wide pattern for a lint rule, not for this fix; the guard is scoped to the `redirectTmpdir` suites the issue reports. - Local `npx eslint` on the changed files could not run: `packages/docs/tsconfig.json` fails to resolve `@docusaurus/tsconfig` because the root install skips that workspace. Pre-existing and unrelated; CI's ESLint job is green on this branch. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1e851f72a0 |
test(ios-device-runner): pin USERPROFILE alongside HOME so the fixtures redirect on Windows (#1090)
Fixes #1069 **Cause:** `os.homedir()` reads `USERPROFILE` on Windows and `HOME` elsewhere. The two `ios-device-runner` suites pinned `HOME` only, so on Windows the redirect was inert and they built into `%USERPROFILE%\.argent\ios-device-runner\` in the developer's real home. **Fix (test-only):** pin `USERPROFILE` at the same directory as `HOME` in both fixtures, and add it to the saved record so the existing delete-or-assign restore puts it back. This is what every other `HOME`-swapping suite in the repo already does (`avd-snapshot`, `android-binary`, `adb-resolve-avd-path`, ...); these two files were the only ones left. **Docs:** none needed - no tool, CLI, config key, flow file or user-facing capability changes. <details> <summary>Class check, discrimination proof and checks</summary> Every file assigning `process.env.HOME` in `packages/`, and whether it also pins `USERPROFILE`: ``` $ for f in $(grep -rl "process\.env\.HOME *=" --include="*.ts" packages/); do echo "$(grep -c USERPROFILE $f) $f"; done ... 0 packages/tool-server/test/ios-device-runner-artifact.test.ts 0 packages/tool-server/test/ios-device-runner-launch.test.ts ``` The other 20 already pin both, so the class is exactly these two files. Not moved onto `test/helpers/temp-home.ts`'s `scopeTempHome`: it is a file-level `beforeEach`, while both fixtures swap the home mid-test alongside `PATH` (and `ARGENT_IOS_RUNNER_PROJECT`), and `withEnsureEnv`'s per-test home name is its first argument at 11 call sites. Discrimination - with only the two `process.env.USERPROFILE = ...` assignments deleted, tests kept: ``` FAIL test/ios-device-runner-launch.test.ts > launchRunner > resolves with the launched child and per-device log and bundle paths AssertionError: expected 'PORT=unset ARGS=test-without-building...' to contain 'HOME=/var/.../argent-runner-launch-QkW7KQ USERPROFILE=...' + HOME=/var/folders/.../argent-runner-launch-QkW7KQ USERPROFILE=unset FAIL test/ios-device-runner-artifact.test.ts > ensureRunnerArtifact > redirects os.homedir() under both names it consults Test Files 2 failed (2) Tests 2 failed | 25 passed (27) ``` Restored, both pass. Checks run from the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - `Test Files 433 passed (433)`, `Tests 6164 passed | 1 skipped` - `npx prettier --write` on both files - unchanged </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d45306d7f3 |
test(tool-server): reap three temp dirs an assertion failure strands (#1089)
Fixes #1077 **Cause** - three tool-server tests remove their temp dir as the *last statement of the test body*, so any assertion above it throwing abandons the directory in the shared system temp dir. **Fix** - a module-scope list pushed to at the mint site and drained in an `afterEach` (the shape #1057 used), replacing the trailing `rm`. `render-annotations.test.ts` had no hook at all; `workspace-reader.test.ts`'s describe-level `afterEach` becomes redundant once `createTempDir` registers every dir it hands out, so it goes. Test-only. No tool, CLI, config key, flow file or user-facing capability changes, so no docs update. **Verified** - break one assertion per file, run under a scoped `TMPDIR`, list what survives. | file | before | after | |---|---|---| | `metro/source-resolver-empty-root.test.ts` | `real-app-ouDGWh` | *(none)* | | `react-profiler/render-annotations.test.ts` | `argent-render-annotations-…zmt1onhl4wj`, `…sriq0gyjyo` | *(none)* | | `workspace-reader.test.ts` | `ws-reader-test-SIWls6` | *(none)* | Same failure count on both sides (1, 2, 1) - only the leftovers differ. <details> <summary>Repro, both sides</summary> Mutations applied to each file (`git show HEAD:<path>` for the "before" column, the branch for "after"): ``` sed -i '' 's/expect(out).toContain("IN_PROJECT");/expect(out).toContain("NOPE");/' \ test/metro/source-resolver-empty-root.test.ts sed -i '' 's/expect(after50).toContain("(t=192.3s)");/expect(after50).toContain("(t=999.9s)");/; s/expect(report).not.toMatch(\/> After:\/);/expect(report).toMatch(\/> After:\/);/' \ test/react-profiler/render-annotations.test.ts sed -i '' 's/expect(snap.lockfile).toBe(lockName);/expect(snap.lockfile).toBe("NOPE");/' \ test/workspace-reader.test.ts ``` Each run, from `packages/tool-server`: ``` d=/tmp/scope/<label>; rm -rf $d; mkdir -p $d TMPDIR=$d TMP=$d TEMP=$d npx vitest run <file> ls -A $d ``` Before (unfixed, mutated): ``` unfixed_sr: Tests 1 failed | 4 passed -> node-compile-cache real-app-ouDGWh unfixed_ra: Tests 2 failed -> node-compile-cache argent-render-annotations-1788965985023-zmt1onhl4wj argent-render-annotations-1788965985027-sriq0gyjyo unfixed_wr: Tests 1 failed | 34 passed -> node-compile-cache ws-reader-test-SIWls6 ``` After (this branch, same mutations): identical failure counts, `node-compile-cache` only. </details> <details> <summary>Checks</summary> From the worktree root: - `npx tsc --build` - clean - `npm run typecheck:tests -w @argent/tool-server` - clean - `npm test -w @argent/tool-server` - `368 passed (368)` files, `4832 passed | 1 skipped` - `npx prettier --write` on the three files - unchanged Class sweep: scanned every `*.test.ts` under `packages/tool-server/test` for an `rm`/`rmSync`/`unlink` as the final statement of an `it` body. Besides these three, only `react-profiler/dump.test.ts` and `screen-recording.test.ts` match, and both already point `os.tmpdir()` at a per-test scratch reaped in `afterEach`, so their unlinks strand nothing. The same shape in other packages is #1074 / #1075 and is left to those. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Tests** - Improved test cleanup by consistently removing temporary and debug directories after each test. - Consolidated cleanup handling across source resolution, profiler annotations, and workspace reader tests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e5de0802ed |
test(installer): stop 11 foreign-config tests stranding their temp dir on a failed assertion (#1087)
Fixes #1074 **Cause** - 11 tests in `describe("installer preserves foreign MCP config")` remove their temp dir as the last unguarded statement of the test body, so any assertion above it throwing strands the directory; the file's `afterEach` only covers `tmpDir`. **Fix** - mint those dirs through a registry drained in the file's `afterEach`, the shape already used by `tool-server`'s `native-devtools-env.test.ts`. Test-only; no production code and no docs change (no tool, CLI, config key, flow file or user-facing capability moved). **Verified** - break the same assertions on both sides, run the file with `TMPDIR` scoped to an empty dir: identical 25 failures, 12 stranded dirs before, 0 after. Full installer suite 555 passed, `tsc --build` and `typecheck:tests` clean. <details> <summary>Mutation repro - before vs after</summary> Mutation (5 assertions, covering both the `argent-fc-` and `argent-fc-home-` sites): ``` sed -i '' \ -e 's/expect(adapter.remove(configPath)).toBe(true);/expect(adapter.remove(configPath)).toBe(false);/' \ -e 's/expect(after.mcpAllowlist).toContain("argent:\*");/expect(after.mcpAllowlist).toContain("nope:*");/' \ -e 's/expect(after.mcpAllowlist).toEqual(\["other:\*"\]);/expect(after.mcpAllowlist).toEqual(["nope:*"]);/' \ -e 's/expect(fs.existsSync(permPath)).toBe(false);/expect(fs.existsSync(permPath)).toBe(true);/' \ -e 's/expect(after).not.toHaveProperty("mcpAllowlist");/expect(after).toHaveProperty("mcpAllowlist");/' \ test/mcp-configs.test.ts d=/tmp/fc; rm -rf $d; mkdir -p $d TMPDIR=$d TMP=$d TEMP=$d npx vitest run test/mcp-configs.test.ts ls $d ``` Both runs: `25 failed | 214 passed (239)`. | | leftovers in the scoped temp dir | |---|---| | `main` | `argent-fc-{acrLbH,EkKAUK,O0pfPk,O55Nmh,XfBO0s,XOqFC4,yf8Zr9,Z1q7T8}`, `argent-fc-home-{F0R6QZ,shaUd9,v9FiKZ,zEhAc8}` - 12 | | this branch | *(none)* | Unmutated on this branch: `239 passed`, scoped temp dir empty. </details> <details> <summary>Checks</summary> ``` npx tsc --build # clean npm run typecheck:tests -w @argent/installer # clean npm test -w @argent/installer # 17 files, 555 passed npx prettier --write packages/argent-installer/test/mcp-configs.test.ts # unchanged ``` `npx eslint` on the file cannot run in this checkout - it fails at `packages/docs/tsconfig.json(2,14): File '@docusaurus/tsconfig' not found`, a pre-existing local-only issue unrelated to the diff. </details> <details> <summary>Class sweep</summary> Every other `rmSync` of a minted temp dir under `packages/argent-installer/test/` and the same `rmSync(dir, ...)` shape in `packages/tool-server/test/` already sits in a hook: `init.test.ts:86,152` (`afterEach`), `preview-ui-dotfile.test.ts:28` (`afterAll`), `vega-cli-timeout.test.ts:164` (`afterAll`), `native-devtools-env.test.ts:12` (`afterEach`, the registry pattern copied here). `mcp-configs.test.ts:1891` removes a pre-existing config file, not a minted dir. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved temporary directory cleanup during installer tests. * Temporary directories are now cleaned up automatically after each test, including when assertions fail. * Consolidated repeated cleanup logic for more consistent test isolation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
02fd22e9d6 |
chore: bump all versions to 0.25.0 (#1083)
Bumps every workspace package, `package-lock.json`, and both version fields in `server.json` from 0.24.0 to 0.25.0. - `npm version 0.25.0 --workspaces --no-git-tag-version --workspaces-update=false` - lockfile regenerated with `npm install --package-lock-only --ignore-scripts` on Node 24 / npm 11 (version-only diff) - `npm run check:versions` passes No docs update needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XdSDvFDvuU1iyz7HPjcHMV Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>v0.25.0 |
||
|
|
8dd2621968 |
docs: cut the README to the essentials (#1082)
The README duplicated the docs site - install prerequisites, a Linux emulator tuning essay, the full CLI and editor tables - burying what Argent actually is. 17.4 KB -> 3.5 KB (5.0x), no information lost: everything cut already lives in the docs, now linked. | | Before | After | | --- | --- | --- | | Size | 217 lines / 17404 chars | 43 lines / 3477 chars | | Sections | 9 (incl. a ~4500-char Linux emulator section, CLI table, editors table) | 6 | | Media | header, 3 ads, showcase gif, SWM logo | unchanged | | Legal | Privacy + License | unchanged (License verbatim) | <details> <summary>Where the removed content lives</summary> | Removed | Docs page | | --- | --- | | Prerequisites, Linux emulator tuning, `--local` team install, manual install | [fundamentals/installation](https://docs.swmansion.com/argent/docs/fundamentals/installation) | | Supported platforms table | [fundamentals/supported-platforms](https://docs.swmansion.com/argent/docs/fundamentals/supported-platforms) | | CLI reference table | [reference/cli](https://docs.swmansion.com/argent/docs/reference/cli) | | Supported editors table | [reference/editors](https://docs.swmansion.com/argent/docs/reference/editors) | Verified: `prettier --check` passes; all five outbound links return 200; `LICENSE.txt`, `Telemetry.md` and `assets/showcase.gif` exist at the referenced paths. No docs changes needed - the docs site already covered every removed section. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01YNGjiLLCf4cg3LXx4SCeUC Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9d512ce933 |
optimization: simplify flow-run description (#954)
# Why It is reported that benchmark results are getting worse and the `flow-execute` description has the largest optimization potential. The description field of MCP tools should describe what the tool does and when to call it, not reveal implementation details. <img width="1195" height="382" alt="Screenshot 2026-08-25 at 12 45 30" src="https://github.com/user-attachments/assets/d83ba9c5-5e4f-4c65-ba3a-4af0588e5faa" /> # Warning Not tested. I'd have to run this change twice against the benchmark repo but that feels like overkill. I am not sure about behavioral regressions, relying on CI here. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Documentation** - Updated the Flow YAML reference to streamline directive documentation. - Revised the flow execution tool’s description with a concise overview of supported flow sources and per-step reports. - Clarified documentation around flow behavior, including warning handling and project-root configuration. - Removed outdated or overly detailed statements from the published guidance to keep the reference accurate and easier to scan. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
3ca9cae91d |
📝 CodeRabbit Chat: Disable CodeRabbit High-Level Summaries (#1081)
Code changes was requested by @latekvo. * https://github.com/software-mansion/argent/pull/1066#issuecomment-5601188159 The following files were modified: * `.coderabbit.yaml` Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
16ad44bb0e |
test(telemetry): pin the wire format and what a dead collector costs (#736)
Two test files that look at what #570's exporter actually *does*, plus the configuration holes they exposed. The suite on #570 proves the exporter is **configured** right - the url, the headers, the timeouts, all read off the constructor. Nothing looked at the request that comes out of it, or at what happens when the collector does not answer. Both gaps are invisible from inside Argent by design: a failed export never reaches the caller, and an OTLP exporter counts any 2xx as delivered. | | | |---|---| | **`otel-wire.test.ts`** | Captures the real exporter's request off a loopback socket: uncompressed OTLP/JSON on `POST /v1/logs`, where each field the ingestion schema reads actually sits, the batch-size cap, and connection reuse. | | **`otel-unreachable.test.ts`** | Drives the same exporter at a silent socket, a refused port, a 503, a 401 and a 204, and asserts each drain finishes inside its deadline and what the collector really received. | | **`src/otel.ts`** | Two holes the tests exposed: `compression` and the six OTLP certificate variables are now settled in code instead of left to the environment. | **No docs update needed** - no user-facing capability, CLI surface, config key, flow file or MCP tool changed. <details> <summary><b>The two source fixes</b></summary> **`compression` was the one OTLP knob still left to the environment.** With `OTEL_EXPORTER_OTLP_COMPRESSION=gzip` (or its `_LOGS_` variant) set - and a machine already running OpenTelemetry has it set for its own collector - argent gzips its batches. Nothing downstream notices: a gzipped 2xx is a delivered batch, while the request this suite and the deploy-time smoke test are written against quietly stops being the request argent sends. `createExporter` now passes `compression` explicitly, which beats both variables. Unlike the header channel, where the SDK *merges* whatever the code does not set and the variables have to be cleared outright, one explicit value settles this. **The certificate variables could hang the command outright.** The SDK resolves `OTEL_EXPORTER_OTLP_{,LOGS_}{CERTIFICATE,CLIENT_CERTIFICATE,CLIENT_KEY}` with a synchronous `fs.readFileSync` while the exporter is constructed, and only then discards the https agent it built from them in favour of the explicit `httpAgentOptions`. The file is read for nothing - and a path that never answers hangs the command that emitted the event, uninterruptibly, on `getClient()`'s path. Measured with a fifo that has no writer: `createExporter` never returned (killed at 12s); with the variables cleared, 1.0 ms. They join the header pair in the list cleared across the constructor and restored on the way out. Some source prose was also wrong about the SDK and is corrected: it does **not** reject a null attribute - it serializes one as an empty OTLP value, `{"key":"cloud_agent","value":{}}` with `droppedAttributesCount: 0`, stored as and unrecoverable from a property that really was empty. That is the actual reason `toAttributes` drops the key. </details> <details> <summary><b>What the tests pin, and what they deliberately do not</b></summary> The wire test **mirrors** `OtelClient.emit` rather than calling it: `getClient()` resolves its endpoint from the hard-coded `OTLP_LOGS_ENDPOINT`, and that being unredirectable is the anti-exfiltration property the client is meant to have, so there is deliberately no seam to aim it at a test server. `otel-endpoint.test.ts` covers the other half against the real emit path - `SERVICE_NAME`, the logger name, event-name-as-body, the dropping of null/undefined properties - so a rename is caught there rather than in the mirror. What enforces the drain budgets is the exporter's `timeoutMillis`, *not* the processor's `exportTimeoutMillis`, which `forceFlush()` awaits straight past. Measured against a silent socket: `exportTimeout` 200 / `timeoutMillis` 5000 drains in 5012 ms, the reverse in 202 ms. 503 is the one collector state where the batch is sent twice, and the request count is the only thing that tells a retried failure from a rejected one - the SDK reports the class, not the code. The retry itself is only reachable while the next backoff fits in what is left of `timeoutMillis`, so the jitter is pinned to its low draw and the assertion turns on the round trip alone. `httpAgentOptions.timeout` is the one option with no behavioural coverage: it covers a socket stuck in *connect*, which needs an address whose packets are dropped and is not portably reproducible here. It stays pinned at the constructor and the docstring says so. Its `keepAlive` half **is** covered, on the two batches of a single drain. Real deadlines cost real time: `otel-unreachable.test.ts` waits ~5.1 s, taking the package suite from ~0.9 s to ~5.3 s. Inherent to driving real deadlines against real sockets. </details> <details> <summary><b>Verification</b></summary> - `npm test -w @argent/telemetry` - **336 passed**, 19 files - `typecheck:tests`, `eslint --max-warnings 0`, `prettier --check`, `knip`, `tsc --build` all clean; CI green - **Mutation-tested**, whole-suite counts rather than one test each: | mutation in `src/otel.ts` | tests red | |---|---| | `compression` pin dropped | 2 | | `compression` pinned to `"gzip"` | 5 | | any one of the 8 cleared env-var names dropped | 1 each | | env-clearing loop deleted | 2 | | the `finally` that restores it deleted | 1 | | `timeoutMillis` dropped | 5 | | `httpAgentOptions` dropped, or just its `timeout` | 1 | | `keepAlive` flipped to `false` | 2 | | `installDiagLogger`'s `!isDebugEnabled()` guard removed | 1 | | `maxExportBatchSize` 20 → 19 or 25; `scheduledDelayMillis` changed | 2 | | `SERVICE_NAME` renamed | 2 | | `LOGGER_NAME` changed | 1 | | record body no longer the event name | 4 | | `severityText` INFO → WARN | 1 | | null/undefined property skip removed | 2 | | `url` given a suffix the config did not set | 9 | - **Absence assertions carry positive controls.** The certificate test reads its answer out of a `Failed to read ` prefix that lives in a transitive dependency, and the 204 case asserts an empty diag channel; both would pass just as well against a dead observer. Each now proves its own observer live first. - **Flake-tested.** The 503 case's exact two-request assertion used to turn on the jitter draw: standing a 500 ms collector delay in for a loaded runner made it fail 20/20 (13 on the request count, 7 on `Timeout` masking the error class). With the jitter pinned, 20/20 pass. </details> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved telemetry export reliability by preventing unintended compression and certificate-related environment settings from affecting exporter configuration. - Ensured telemetry attributes with null or undefined values are omitted rather than serialized as misleading empty values. - Improved handling of unreachable or unresponsive telemetry collectors with bounded failures and appropriate diagnostic reporting. - Preserved connection reuse and batching behavior for more efficient OTLP delivery. - **Tests** - Added comprehensive coverage for OTLP formatting, authorization, batching, compression, certificates, connection handling, and failure scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
82db9e2f9f |
test: stop tests leaving files in the shared system temp directory (#1057)
Three issues, one theme: tests and the shared system temp directory. - **#858 / #854** were live leaks — three call sites captured a real temp path and never removed it. One reaches the cleanup its file already had; the other two get a cleanup the file lacked. - **#836** turned out to be **already fixed on `main`** by #714, which scoped those three tests' `TMPDIR` to a directory they own. This PR verifies that rather than changing it — reproduction and mutation evidence below. - Sweeping the same shape across the two packages turned up **six more test files** doing it, worth **44** leftover entries per run. - A third shape, in **two** more files: `resolveFileInputs` hands its caller a `cleanup` for whatever it materialized — the dispatcher's job in production. Five call sites kept the result and never called it, stranding 5 dirs per run. A scoped-`TMPDIR` run of all 12 affected files leaves **one** entry behind: an empty `argent-chromium-media/`, production's own fixed staging dir. 173 tests, no other file or directory. Fixes #858 Fixes #854 Closes #836 <details> <summary>#858 — before/after temp scope</summary> ``` rm -rf /tmp/scope && mkdir -p /tmp/scope TMPDIR=/tmp/scope TMP=/tmp/scope TEMP=/tmp/scope \ npx vitest run --root packages/tool-server test/chromium-server-screenshot.test.ts ``` | site | before | after | |---|---|---| | `chromium-server-screenshot.test.ts` | `argent-chromium-media/argent-screenshot-test-1788803062089-38318.png` | *(no PNG)* | | `chromium-cdp-blueprint.test.ts` | `argent-chromium-media/argent-screenshot-chromium-cdp-52204-1788803091335-39401.png` | *(no PNG)* | Both files keep an empty `argent-chromium-media/` — that is production's fixed staging dir (`src/chromium-server/cdp-session.ts:218`), shared with any live chromium-server on the machine and not per-run garbage. A test `rm`ing it would be the machine-global-resource defect #714 removed elsewhere, so it is left alone deliberately. </details> <details> <summary>#854 — before/after temp scope</summary> ``` TMPDIR=/tmp/scope … npx vitest run --root packages/argent-mcp test/content.test.ts ``` Before (`34 passed`, two directories left): ``` argent-mcp-content-kPfgYa/context.diff.png argent-artifacts/test-temp-dir-hygiene-037c31/20260907-174453-39458/home-diff.png ``` After: **empty**. The `mkdtemp` moved onto a `beforeEach`/`afterEach` pair mirroring the sibling describe, and `flowRunToMcpContent` now pins `ARGENT_ARTIFACTS_DIR` the way the artifact describe above it already did. The unconditional `delete process.env.ARGENT_ARTIFACTS_DIR` is kept, not converted to a save/restore: it is what keeps any future unpinned write landing in `tmpdir` rather than a developer's configured artifacts directory. The pin is not hygiene-only — it is asserted. Removing `process.env.ARGENT_ARTIFACTS_DIR = root` fails the test: ``` - diff: /tmp/scopeM4/content-flow-artifacts-LZKPAl + diff: /tmp/scopeM4/argent-artifacts/test-temp-dir-hygiene-037c31/20260908-100837-89701/home-diff.png ❯ test/content.test.ts:627 expect(artifactText?.text).toContain(`diff: ${root}`) ``` </details> <details> <summary>#836 — why no code changed, and the evidence</summary> (This PR does change `file-inputs.test.ts`, but for the unrelated third shape above — not for the sites #836 cites.) The issue cites `readdir(os.tmpdir())` at `file-inputs.test.ts:180-197` and `flow-visual.test.ts:586-602`. On current `main` neither exists: #714 replaced both (and the `http-upload.test.ts:154-158` sibling the issue also flags) with a per-test scratch directory the test creates, points `os.tmpdir()` at via `redirectTmpdir`, and removes. There is now **no** `readdir` of `os.tmpdir()` anywhere in either package. The per-path assertion the issue asks for is not reachable at these three sites — every name is a `mkdtemp` random suffix or a server-side `randomUUID`, so the test cannot know the path. Scoping the tmpdir to a directory the test owns is the same guarantee, and is what landed. **Reproduced the reported failure** by reverting each site to the global-listing shape, then running two simultaneous instances in a loop: | file | pre-#714 shape | current | |---|---|---| | `file-inputs.test.ts` | **1 / 12** — `expected [ 'argent-file-input-00mPU7', …(1371) ] to deeply equal [ …(1370) ]` | **0 / 12** | | `flows/flow-visual.test.ts` | **12 / 12** — `expected [ 'argent-flow-crop-00R9XZ', …(4853) ] to deeply equal []` | **0 / 12** | | `http-upload.test.ts` | — | **0 / 12** | **Mutation-tested all three assertions** — each still catches a real leak; production cleanup broken, test run, source restored: | assertion | mutation | result | |---|---|---| | `file-inputs.test.ts:249` | drop `await cleanup()` from `resolveFileInputs`' catch path | ✗ `+ [ "argent-file-input-amPJuK" ]` | | `flow-visual.test.ts:610` | drop `cleanupDiffDir(cropDir, …)` from the outer `finally` | ✗ `+ [ "argent-flow-crop-rWT9Bd" ]` | | `http-upload.test.ts:187` | drop `rm(tarPath)` from the `req.on("close")` handler | ✗ `+ [ "argent-upload-43d82021-….tar.gz" ]` | </details> <details> <summary>Sibling sweep — the same two shapes across both packages</summary> **Shape "assertion diffing a listing of `os.tmpdir()`": zero remaining.** No `readdir`/`readdirSync`/`glob` over `os.tmpdir()`, `tmpdir()` or a literal `/tmp` in either test tree. Every literal `/tmp` string left is a fixture handed to a mocked API; the three that do touch disk were checked by hand and are clean (`native-devtools-socket-bind.test.ts` mkdtemps and removes, `flow-utils.test.ts` is pure path math, `native-profiler-external-device.test.ts` writes only inside its own per-test `mkdtemp`). **Shape "a captured temp path never cleaned up": six files fixed.** Each gets one recording choke point plus an `afterEach`. Running those six under a scoped `TMPDIR` (`48 passed` both times): ``` before: 44 entries after: 1 entry ``` | file | sites | |---|---| | `screenshot-diff-tool.test.ts` | 9 | | `react-profiler/ast-index.test.ts` | 6 | | `event-log.test.ts` | 1 helper (4 dirs/run) | | `screenshot-diff.test.ts` | 1 helper | | `react-profiler/component-source-duplicates.test.ts` | 2 | | `ios-instruments/combined-report-frozen-anchor.test.ts` | 1 | | `flows/flow-visual.test.ts` | production's crop + diff dirs (5/run) | **Left deliberately (1 entry):** an **empty** `argent-chromium-media/` — production's fixed staging dir (`src/chromium-server/cdp-session.ts:218`), shared with any live chromium-server on the machine. A test `rm`ing it would be the machine-global-resource defect #714 removed elsewhere. Two further shapes turned up while checking that claim, both *production* dirs a test drove and then abandoned: | site | per run | why the test could not just `rm` it | |---|---|---| | `screenshot-diff-tool.test.ts` | 2 dirs + 2 PNGs | `resolveOutputDir` mints `argent-screenshot-diff/<hex>` before anything validates the call, so the path exists before the test can learn it — and on a throw it never learns it at all | | `flows/flow-visual.test.ts` | 5 dirs + 5 PNGs | `runSnapshot` leaves the file it registered as an artifact inside its `argent-flow-crop-*` / `argent-flow-diff-*` scratch dir; from there the dir belongs to whoever consumes the artifact, which here is the test | Both are fixed the same way, with `redirectTmpdir` (already used by seven other test files): point `os.tmpdir()` at a directory the test already owns and removes, so whatever production mints lands inside it whether the call returns or throws. The `<hex>` and `mkdtemp` suffixes are per-call and never shared, so nothing a concurrent tool-server owns is touched — verified with a decoy file planted under the shared root, which survives the run. Measured under a scoped `TMPDIR`, three consecutive runs each: `screenshot-diff-tool` 6 dirs + 6 PNGs → **0 + 0**; `flow-visual` 15 + 15 → **0 + 0**. The underlying production leak behind the first is still #790. `argent-mcp` is now clean end to end: every `mkdtemp` in the package has a matching `rm`. </details> <details> <summary>Checks</summary> Run from the worktree root, exit codes read unpiped: - `npm run format` → 0, no files rewritten - `npx tsc --noEmit -p packages/tool-server/tsconfig.test.json` → 0 - `npx tsc --noEmit -p packages/argent-mcp/tsconfig.test.json` → 0 - `npm run lint` → 0 - `npm run knip` → 0 - no `it.only` / `describe.only` in the diff Suites: `argent-mcp` **90 passed**; `tool-server` run as six shards under a scoped `TMPDIR`. `lens-tools-platform-gate.test.ts` times out at the 5 s default under full-suite load and passes in isolation (`2 passed`, 3.14 s). It is not in this diff and #714 recorded the same behaviour before it. </details> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Tests** - Improved test isolation by consistently cleaning up temporary files and directories after each test. - Added coverage verifying generated artifacts are stored under the configured artifacts directory. - Ensured screenshot, upload, event-log, profiling, visual-flow, and diff-test artifacts are removed reliably after execution. - Standardized temporary-directory handling and cleanup across test scenarios to reduce leftover test data and improve repeatability. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
86a2668fb0 |
test: stop the suite and the process environment leaking into each other (#1058)
Test env leakage, both directions. **Out** — six `argent-tools-client` suites pointed `HOME`/`USERPROFILE` at a temp dir, deleted the dir, and never restored, so a `--no-isolate` run handed every later file a home that no longer exists. Lifted the redirect into a shared `test/helpers/home-redirect.ts`; the three suites that already hand-rolled the save/restore moved onto it too. **In** — `packages/telemetry` had no `setupFiles` at all, so its unit tests read the developer's shell. Added `test/setup/clear-telemetry-env.ts` (ARGENT_ prefix sweep + `DO_NOT_TRACK` + the cloud-agent detector's markers), wired via `setupFiles`, following the `@argent/tool-server` precedent — and dropped the per-file guards it replaces. **Pinned, not just fixed** — a `test/setup/assert-env-restored.ts` in each of the three packages fails any suite that leaves a watched variable changed, so dropping a restore can no longer pass. Each watches `HOME` and `USERPROFILE`, plus `PATH` (`argent-tools-client`, `tool-server`) or `CI` and the five `OTEL_EXPORTER_OTLP_*` (`telemetry`) — the ones the sweep deliberately leaves ambient and individual tests then set by hand. Each of the three `clear-*-env` setup files has a pin test that re-imports it against planted sentinels; `packages/tool-server`'s existing copy gains the mixed-case one it was missing. `packages/tool-server` is in the diff because it has the same two gaps: it originated the prefix sweep (both pre-existing copies were case-sensitive, so they are fixed together) and it has the largest redirect surface of the three — 12 files hand-rolling the redirect plus 17 going through `scopeTempHome` — with no guard. It is already green under one; deleting `scopeTempHome`'s restore turns 17 files red. Two of its suites restored `HOME` by assigning back a saved value that can be `undefined`, writing the string `"undefined"`; the guard caught both on a host with no ambient `HOME`, and they are fixed here. `windows-e2e.yml` runs seven `tool-server` files through the config this branch edits, but neither that config nor `test/setup/**` was in its `paths:` filter — so the new setup file would have reached windows-latest unverified. Added both. No docs change: test infrastructure only, no user-facing behaviour. Fixes #1028 Fixes #839 Same-class problems found while reviewing this and filed rather than folded in: #1068 (`argent-cli` fails under `DO_NOT_TRACK=1`), #1069 (the `ios-device-runner` suites pin only `HOME`, so the redirect is inert on Windows — its second half, the restore that wrote the string `"undefined"`, is fixed here because the new guard caught it), #1070 (`useTempHome` shares one saved-`HOME` slot, so a nested `scopeHome()` restores a deleted dir), #1071 (`tool-server`'s sweep is prefix-only, so an exported `ADB_SERVER_SOCKET` reddens its suite), #1072 (`external-devices.test.ts` reads the developer's real `~/.argent/providers`) and #1073 (`flow-script-environment.test.ts` fails with no ambient `HOME`). <details> <summary>#1028 — probe results, six files</summary> `test/zz-home-probe.test.ts` from the issue body, run per suite. Vitest's sequencer orders by cached duration and falls back to file size, so the cache is cleared before each run to keep the tiny probe last: ``` rm -rf node_modules/.vite/vitest ../../node_modules/.vite/vitest npx vitest run test/<suite>.test.ts test/zz-home-probe.test.ts --no-isolate --no-file-parallelism ``` | suite | before | after | | --- | --- | --- | | `launcher-state` | 1 failed \| 23 passed — `argent-state-test-… exists=false` | 24 passed | | `launcher-spawn` | 1 failed \| 8 passed — `argent-spawn-test-… exists=false` | 9 passed | | `launcher-version-gate` | 1 failed \| 7 passed — `argent-version-gate-test-… exists=false` | 8 passed | | `launcher-duplicate-spawn` | 1 failed \| 10 passed — `argent-dup-test-… exists=false` | 11 passed | | `launcher-exit` | 1 failed \| 1 passed — `argent-exit-test-… exists=false` | 2 passed | | `link-config` | 1 failed \| 43 passed — `argent-link-config-test-… exists=false` | 44 passed | | `launcher-sweep` (control, fixed in #713) | passed | passed | | `launcher-ps-guard` (control, fixed in #713) | passed | passed | The probe itself is a diagnostic and is not part of the diff — `test/setup/assert-env-restored.ts` is the durable guard, and it needs no `--no-isolate` to fire. </details> <details> <summary>#839 — the stated repro is stale; here is what is actually left</summary> The issue was filed 2026-08-18. #714 landed 2026-08-19 and hand-enumerated the variables inside `index.test.ts` (a `CONSENT_ENV_KEYS` `beforeEach`) and `base-props.test.ts` (eight `delete`s). On `aa90873b5` all three named repros are green: ``` $ DO_NOT_TRACK=1 npx vitest run --root packages/telemetry → 319 passed $ ARGENT_TELEMETRY=0 npx vitest run --root packages/telemetry → 319 passed $ CURSOR_AGENT_WORKER_ID=x npx vitest run --root packages/telemetry → 319 passed ``` What remains is the structural gap the issue names: the protection is per-file and per-variable, nothing pins it, and `vitest.config.ts` still has no `setupFiles`. This PR moves the property to the setup file and deletes the two hand-rolled guards, so the setup file is what holds it — see the mutation section, where removing the `setupFiles` line reproduces the issue's 17 and 1 exactly. Post-change sweep, one hostile value per run, 329 tests each: `DO_NOT_TRACK=1`, `ARGENT_TELEMETRY=0`, `ARGENT_TELEMETRY_DEBUG=1`, `CLAUDE_CODE_ENVIRONMENT_KIND=byoc`, `CLAUDE_CODE_ENTRYPOINT=remote`, `CLAUDE_CODE_REMOTE_SESSION_ID=x`, `CURSOR_AGENT_WORKER_ID=x`, `CURSOR_WORKER_POOL_NAME=x`, `GITHUB_ACTIONS=true`, `GITHUB_ACTOR=copilot`, `GITHUB_WORKFLOW_REF=…copilot-swe-agent.yml@refs/heads/main`, `REPLIT_AGENT=1`, `ARGENT_HOST`/`ARGENT_PORT`/`ARGENT_AUTH_TOKEN`, `CI=1`, `CI=false`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`, the real CI combination, and all eleven at once — 329 passed every time. `GITHUB_ACTIONS` is a ci-info vendor input as well as a copilot-agent marker, so clearing it does move `isCi()` — but only its GitHub Actions branch, and an Actions run also sets `CI`, which `isCi()` answers ahead of any vendor check. `GITHUB_ACTOR` and `GITHUB_WORKFLOW_REF` appear in neither `ci-info/vendors.json` nor `ci-detect.ts`'s generic list, so they cost nothing. The rest of the ci-info surface and `OTEL_*` are deliberately left alone; the setup file says so. </details> <details> <summary>Mutation testing — every guard, applied by hand, verified to fail, reverted</summary> | mutation | result | | --- | --- | | drop `setupFiles` from `packages/telemetry/vitest.config.ts` | `DO_NOT_TRACK=1` → 19 failed (**17 in `index.test.ts`**, matching the issue, + the two registration pins) | | drop `CURSOR_WORKER_POOL_NAME` from `CLEARED_ENV_VARS` | drift test red | | move an `env.` read into `src/detect/vendor.ts` | drift test red, naming `detect/vendor.ts` | | rewrite a read as `env["X"]`, `const { X } = env`, `const { X }: NodeJS.ProcessEnv = env` or `const { X = "d" } = env` | drift test red | | add `cloud-agent-detect.ts` to `UNSWEPT_SRC_FILES` | drift test red | | revert `toUpperCase()` in any of the three sweeps | that package's mixed-case case red | | drop `restoreHome()` from one suite / from all nine | 1 file / 9 files red, each naming its deleted dir | | drop `scopeTempHome()`'s restore in `tool-server` | 17 files red | | drop the hand-rolled `CI` restore in `base-props.test.ts` | red, naming that file (green before; under `--no-isolate`, 7 unrelated failures in `otel-endpoint.test.ts`) | | narrow either guard's filter to variables that were already set | `assert-env-restored.test.ts` red | | replace the leaked half of the guard's message with a constant | `assert-env-restored.test.ts` red | | rename one function's `env` parameter in `cloud-agent-detect.ts` | drift test red — the read is found under the new binding | | narrow any guard's filter to a subset of `SCOPED_ENV_VARS` | `assert-env-restored.test.ts` red — the pin plants every entry | | drop the hand-rolled OTLP restore in `otel-endpoint-live.test.ts` | red, naming all four leaked variables | | move the guard's `AMBIENT` snapshot inside `assertEnvRestored` | `assert-env-restored.test.ts` red in all three packages | | delete the guard's `afterAll(...)` registration | `assert-env-restored.test.ts` red in all three packages | | drop `restoreHome()` from `artifacts.test.ts`'s two `afterEach` | red — the file-level guard catches a per-test omission | | drop ps-guard's `PATH` restore | red, naming the file | | empty `leakedEnvVars`, or drop a name from `SCOPED_ENV_VARS` | `assert-env-restored.test.ts` red | | replace the helper's delete branch with a plain assignment | `home-redirect.test.ts` red | | drop `USERPROFILE` from the helper | `home-redirect.test.ts` red | </details> <details> <summary>Local CI parity</summary> ``` npx vitest run --root packages/argent-tools-client 213 passed | 1 skipped npx vitest run --root packages/argent-tools-client --no-isolate 213 passed | 1 skipped npx vitest run --root packages/telemetry 329 passed npx vitest run --root packages/telemetry --no-isolate 329 passed npx vitest run --root packages/tool-server 6166 passed | 1 skipped npx tsc --noEmit -p packages/{argent-tools-client,telemetry,tool-server}/tsconfig.test.json clean npm run format / npm run lint / npm run knip clean ``` </details> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Tests** - Improved test isolation by consistently restoring temporary environment settings after test runs. - Added safeguards that detect leaked environment settings and flag incomplete cleanup. - Added coverage to ensure telemetry-related environment variables are cleared before tests and do not affect results. - Added cross-platform checks for environment variable handling, including mixed-case names. - Expanded validation for home-directory redirection and restoration behavior. - Added checks confirming environment cleanup runs in the correct order across test suites. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
5bc9c7be4c |
test: make four non-discriminating test sites fail on what they claim to pin (#1059)
Four tests could not fail on the thing they name: an assertion implied by the one below it, an assertion satisfied by the fixture's own filename, a clearance poll with nothing to clear, and `it.each` rows whose rendered name described only argv[0] - two of them stating the opposite of what the row asserts. Each site now fails when its claim is broken, proven by mutation. Tests only, no source change, so no docs update. Fixes #943 Fixes #942 Fixes #841 Fixes #861 <details> <summary>#943 — capability: toolId/platform were substrings of the path assertion</summary> `toContain("demo-tool")` + `toContain("android")` are both substrings of `tools/demo-tool/platforms/android.ts`, asserted on the next line. Replaced by the prose sentence, which names both outside the path. Mutation - drop the sentence naming the tool and the platform: ```diff - `Tool '${opts.toolId}' is not yet implemented on ${opts.platform}. ` + + `Tool is not yet implemented. ` + ``` | | result | |---|---| | before | `Tests 7 passed (7)` - green | | after | `AssertionError: expected 'Tool is not yet implemented. The cros…' to contain 'Tool \'demo-tool\' is not yet impleme…'` | The sibling "works without a hint" assertions are load-bearing and untouched. </details> <details> <summary>#942 — native-profiler: the wording assertion matched inside missing_cpu.xml</summary> `/missing|not found|unreadable/i` matched the fixture filename, so any product copy passed. The wording now has to land before the backticked path (a negated character class cannot cross into it), which no path can satisfy. Mutation - reword the ENOENT copy: ```diff - if (code === "ENOENT") return `not found at \`${filePath}\``; + if (code === "ENOENT") return `absent at \`${filePath}\``; ``` | | result | |---|---| | before | `Tests 1 passed (1)` - green (also green with the state word deleted outright) | | after | `AssertionError: expected '# iOS Instruments Analysis…' to match /-\s*\*\*cpu\*\*:[^…]*(?:missing\|not found\|unreadable)/i` | </details> <details> <summary>#841 — vega-cli: the output-cap reaps had nothing to observe</summary> `expect(await waitForClear(SENTINEL_OVERFLOW)).toBe(0)` reads 0 just as readily for a launcher that never spawned a worker. The cap trips on the first write, so there was no window to see one in: the fake now spawns its sentinel worker **before** the flood and holds the flood - and so the reap - until the test writes a gate file, having seen the worker. Both the stdout and the stderr twin. Gated rather than timed, so the observation is decided, not raced. Cost is ~+110ms and ~+50ms on the two tests (165/177ms to 278/226ms). Mutation - the flood branches' workers stop carrying the sentinel, reap path untouched: ```diff - const worker = spawn("sleep", [secs], { stdio: "ignore" }); + const worker = spawn("sleep", ["600.0"], { stdio: "ignore" }); ... - spawnSync("sleep", [secs]); + spawnSync("sleep", ["600.0"]); ``` | | result | |---|---| | before | `Tests 12 passed (12)` - green, and `pgrep -f '^sleep 600\.0$'` empty (the reap works; nothing observed it) | | after | both overflow tests `AssertionError: expected +0 to be 1` | Cross-check that the pair pins the production reap: with `reapOnce()` made a no-op in `vega-cli.ts`, both fail `expected 2 to be +0`. **Flake runs: 30 consecutive passes** (2 x 15, before and after the comment pass), `12 passed (12)` every time. </details> <details> <summary>#861 — four it.each rows named only argv[0] (plus a fifth site)</summary> A single `%j` against a multi-element row renders only the first element. Each row is now one argv, nested, so `%j` names all of it. `packages/argent-cli/test/tools-help.test.ts` ``` - prints usage for "--help" without contacting the tool-server <- duplicate - prints usage for "--json" without contacting the tool-server <- false: a bare --json prints no usage and does hit the server + prints usage for ["--help","--json"] without contacting the tool-server + prints usage for ["--json","--help"] without contacting the tool-server ``` `packages/argent/test/installer-help.test.ts` ``` - treats "--foo" as a help request <- rendered twice; a bare --foo is the opposite + treats ["--foo","--help"] as a help request + treats ["--foo","help"] as a help request - does not treat "--metro-port" as a help request + does not treat ["--metro-port","8082"] as a help request ``` Swept the class across every `it.each` array-literal table in the repo (154 sites). One more reproduces it - `packages/argent-cli/test/telemetry-command.test.ts`, fixed here: ``` - prints usage for status and writes nothing <- rendered twice - prints usage for disable and writes nothing <- false: a bare `disable` writes config + prints usage for ["status","--help"] and writes nothing + prints usage for ["status","-h"] and writes nothing + prints usage for ["disable","-h"] and writes nothing ``` Left alone: ~64 sites with a discriminating label column (`_label` first, house style) and 16 whose first column is a real discriminator - names unique and truthful in both groups. `providers-command.test.ts:440` drops a third "shape" column from its name but stays unique and true, so it is a legibility nit, not this defect. </details> <details> <summary>Checks run</summary> - `npm run format`, `npm run lint` (the latter needs `npm ci` inside `packages/docs` locally) - `npx tsc --noEmit -p packages/{tool-server,argent-cli,argent}/tsconfig.test.json` - `packages/tool-server`: `npx vitest run --shard=1/6` .. `6/6` - 6163 passed, 1 skipped - `npx vitest run --root packages/argent-cli` (619), `--root packages/argent` (80) - `vega-cli-timeout.test.ts` x30 consecutive, zero flakes </details> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved test parameterization so command-line help cases display complete argument lists accurately. * Strengthened error-message and profiler warning assertions to verify exact wording and placement. * Improved subprocess timeout and output-limit tests by synchronizing worker startup and cleanup checks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c3bb2990ea |
test(flows): stop pinning idle and type-focus waits to exact wall-clock boundaries (#1066)
Real-timer assertions in the flows suite were pinned to exact wall-clock boundaries, so a batch run reddened them for reasons unrelated to whatever was being tested. Each now pins the same property with slack, or with no clock at all — every one was mutation-tested to confirm it still fails loudly when the behaviour it guards is removed. Test-only; no docs change. Fixes #1000 Fixes #795 <details> <summary>Per-assertion: what it pins, and what changed</summary> | Site | Pinned property | Before | After | | --- | --- | --- | --- | | `flow-type-focus` "skips the focus poll…" | the fixed 500ms settle still runs on a source that cannot report focus | `gap >= 500` | `gap >= 450` | | `flow-type-focus` "waits for the tapped field…" | settle **plus** at least one 300ms focus poll | `gap >= 800` | `gap >= 720` | | `flow-idle-run` "holds a still screen…" | `stableFor` is served, not ignored | run timed, `[750, 2400)` | span between first and last tree read, `>= 750` | | `flow-idle-run` "settles inside the smallest timeout…" | the hold is counted across the polls, so the parser's floor is servable | run timed, `[750, 1000)` | read span `>= 750`; "inside the wait" carried by `warning === undefined` | | `flow-idle-run` "does not call a slow but answering source a wedged one" | an abandoned read is not a wedge when the source's own slowest answer explains it | 2300ms reads / 4600ms wait, 5s default test timeout | 2800ms / 5400ms, own 20s timeout | | `flow-idle-run` "does not report a tree-only settle…" | the tree-only settle owes the same hold | churn window clocked from before the run | clocked from the first read | | `flow-idle-run` H1/H2 "settled early and then moved / went blank" | the tree-only verdict is not a write-once latch | 4 quiet reads in a 2500ms wait | 3 quiet reads in a 6000ms wait, reads asserted | The two focus bounds had zero slack in the one direction that matters: a 500ms `setTimeout` measured on `Date.now()` spans 499ms roughly once in 200 runs (400 samples, idle machine: `499ms x2, 500ms x48, 501ms x289, 502ms x55, 503ms x2, 505ms x1`). 10% of slack keeps them failing by hundreds of milliseconds if either wait is dropped. The two idle `elapsed` bounds timed the whole run, so the flow's start-up — parse, device resolve — counted against the step's own budget. Timing between the reads the hold is actually measured across removes that, and the upper bounds went with it: every way out of the wait except the settle carries a warning, so `warning === undefined` already says it landed inside. The remaining three are fixtures, not assertions: each assumed a number of poll rounds, or a wall-clock window opened before the run, that a batch run does not serve. They now count reads, or anchor the window on the first read. </details> <details> <summary>Repro — before</summary> `npx vitest run packages/tool-server/test/flows` on `origin/main`: ``` FAIL flow-idle-run.test.ts > settles inside the smallest timeout the parser allows for the hold AssertionError: expected 2439 to be less than 1000 FAIL flow-idle-run.test.ts > does not call a slow but answering source a wedged one Error: Test timed out in 5000ms. FAIL flow-idle-run.test.ts > does not report a tree-only settle when the hold was never served AssertionError: expected 'settled on the UI tree alone — this s…' not to contain 'UI tree alone' FAIL flow-idle-run.test.ts > does not pass on a screen that settled early and then started moving again AssertionError: expected 'settled on the UI tree alone — this s…' to contain 'never held still' ``` #1000's own intermittency did not surface in 10 rounds of `npx vitest run --shard=3/3` (4 idle, 6 under a 10-way CPU load), so it was forced instead: the `setTimeout`-vs-`Date.now()` measurement above is the whole mechanism, and the assertion had no margin over it. </details> <details> <summary>Repro — after</summary> Six batch rounds of `npx vitest run packages/tool-server/test/flows`, on a machine carrying other work (load average 22-30 throughout). Rounds 2-6 were additionally narrowed to `--maxWorkers=3`, which is harsher than CI: the 13 `flow-composition` cases of #1067 pin one of the three workers for ~13 minutes and starve the rest. | round | workers | result for the assertions this PR touches | | --- | --- | --- | | 1 | default | all clean | | 2 | 3 | H1 starved to 3 reads | | 3 | 3 | all clean | | 4 | 3 | all clean | | 5 | 3 | all clean | | 6 | 3 | H2 starved to 3 reads | Every other failure in those rounds was the same 13 `flow-composition` timeouts (#1067), plus load artifacts in `flow-selector-scopes`, `flow-tool-step-tree-target-e2e`, `flow-launch-pins-tree-target` and `flow-gesture-settle` — none of whose assertions this branch touches. The two starved rounds were a real gap, not noise: H1 and H2 each need a fourth read to reach a verdict at all, and a 3500ms wait serves eighteen on an idle machine but three on a starved one. The case then saw a screen that never moved and passed the wait cleanly, so the assertion ran against `undefined`: ``` AssertionError: the given combination of arguments (undefined and string) is invalid for this assertion. ``` Each case now budgets for the starved run and asserts the reads it was served, so an environment that cannot reach the verdict says so: ``` AssertionError: reads served — under four, nothing moved to be judged: expected 3 to be greater than or equal to 4 ``` Confirmed by stretching each read to 1800ms, which starves the wait to three reads without touching the wait itself. </details> <details> <summary>Mutation tests (source broken, assertion must fail)</summary> Each mutation was applied to `flow-actions.ts`, the test run, then reverted. | Mutation | Assertion under test | Result | | --- | --- | --- | | delete `sleepOrAbort(TYPE_FOCUS_SETTLE_MS, …)` | both focus gaps | FAIL — `expected 0 to be greater than or equal to 450`; `expected 303 to be greater than or equal to 720` | | focus poll sleeps `0` instead of `POLL_INTERVAL_MS` | `>= 720` | FAIL — `expected 504 to be greater than or equal to 720` | | drop `&& heldForMs >= stableFor` from the settle | both idle read spans | FAIL — `expected 403 to be greater than or equal to 750` (both) | | drop `slowestAnsweredReadMs +` from the hung check | "slow but answering" | FAIL — step errors, `expected false to be true` on `r.ok` | | drop `&& now - treeSince >= stableFor` from `treeSettledAtLastRead` | "tree-only settle" | FAIL — `expected 'settled on the UI tree alone…' not to contain 'UI tree alone'` | | `if (lastRead === "timeout")` without `&& treeReadHung` | "still warns, rather than erroring" | FAIL — `expected 'error' to be 'pass'` | | make `treeSettledAtLastRead` a write-once latch | H1 "settled early then moved again" | FAIL — reports the tree-only settle | | drop `treeSettledAtLastRead = false` from the blank-read branch | H2 "settled early then went blank" | FAIL — reports the tree-only settle | The `treeReadHung` one did **not** fail at `timeout: 3000`: with the round period dividing the wait exactly, drift decided whether the closing read was abandoned at all. `3200` leaves the seventh read 200ms — less than the source needs — so the case ends on an abandoned read by arithmetic. </details> <details> <summary>Why the latch waits stop at 6000ms, and H3 is untouched</summary> The first attempt at these two used a 12000ms wait, which passed CI and broke the budget-tail case below them in the same file — that one has to fit three reads into a 2000ms window, and with 24s of polling ahead of it it saw two: ``` expected 'the screen came back with content on 2 reads in 2000ms…' to contain 'never held still' ``` Deterministic, not load: 4/4 runs on the branch, 0/4 on `origin/main`, and it passes in isolation. Bisected across the file: | latch wait | whole file | | --- | --- | | 2500 (main) | 46 passed | | 3500 | 46 passed | | 6000 | 46 passed | | 12000 | budget-tail fails every run | So the latch cases take 6000ms and no more. H3 itself is left exactly as it is on `main`: widening its wait scores its abandoned closing read as a wedged source (`HUNG_TREE_READ_MS`) rather than a step that ran out of time, and slowing its source starves it below the three reads a verdict takes — both were tried against CI and both moved the failure rather than removing it. </details> <details> <summary>Deliberately not changed</summary> - `flow-idle-run`'s `elapsed < 3000` bounds at "honours its timeout", "rides over a dropped capture" and "stops on abort": 1.8–2.2s of slack over a sub-second expectation already clears a scheduling delay of hundreds of milliseconds. - `flow-composition.test.ts` times out 13 cases at 60s each in the same batch run, on `origin/main` as well as here, while passing in 27s on its own. It is a batch artifact of a different kind and belongs in its own issue, not papered over from here. </details> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved idle-flow timing validation by measuring intervals between reads for more reliable settle detection. * Updated timing scenarios and thresholds to account for delayed reads, startup overhead, slow tree changes, and early-settle behavior. * Added checks to ensure sufficient reads occur before slow-flow warnings and to detect later movement or blanking. * Relaxed Android and iOS focus-wait timing thresholds to accommodate minor timer measurement jitter. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b6576ef60c |
optimization: move some instructions out of the argent-device-interact skill into reference files (#968)
# Why Skills are getting bloated affecting benchmark results. # What - added `<SKILL>/references/chromium.md` — no point of loading that information to the context when argent is used with iOS or Android - added `<SKILL>/references/secrets.md` — typing secrets is rather unusual. - reduced duplicated information for await-ui-element by redirecting to the tool's description # Note Not tested. Relying on CI. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Documentation** - Clarified supported device-interaction targets, including that TV targets are not supported. - Narrowed Chromium support to Electron apps and Chromium-based browsers with a CDP endpoint. - Added guidance for Chromium tabs, cookies, and web storage. - Documented secret placeholder resolution, safeguards, and submission requirements. - Updated UI-wait guidance for hidden elements, root containers, roles, and timeout notes. - Clarified platform-specific interaction behavior and tool usage. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
aa90873b59 |
feat(android): read the web DOM inside a WebView in describe (#1052)
Android `describe` rendered an `android.webkit.WebView` as a single opaque leaf labelled `[web-view]` and threw the subtree away. Chromium publishes the web DOM to the accessibility tree on both capture paths (the android-devtools helper and the `uiautomator dump` fallback), so the sentinel hid content the agent can act on. This keeps the WebView as a landmark and lets the normal trim run over the DOM beneath it. Supersedes #955, which reached the same diagnosis. The parser rules for the doubled in-app node, the web list clip and the text-run role are carried over from it; the rendered-output cap, the `truncated` flag plumbing and the flow-tree parity rework are not, so this stays confined to the Android parser and adapter (about 120 source lines) with no renderer, contract, skill or tool-description changes. ### What changes inside a WebView, and only there - **Doubled node merged.** An in-app WebView dumps as a nested pair: the app's view (resource-id, gesture flags), then Chromium's root web area under the same class carrying the page `<title>` as text. The inner half folds into the outer node, so the tree reads `WebView "<title>"`. A Chrome tab arrives as one node and is untouched. - **Web list clip.** A `<ul>` arrives as `android.widget.ListView` with `scrollable="false"`. Inside a WebView only the framework flag decides what clips, so a list never drops the items laid out around it, and the role is `List` rather than `ScrollView`. - **Text runs.** A labelled leaf `android.view.View` with no gesture flag is reported as `StaticText`. Outside a WebView a bare `View` keeps its role (Compose semantics nodes). - **Headings survive.** The WebView branch returns before the label dedupe, so an `h1` that repeats the page title is kept. ### The cold-read race is fixed, not documented Chromium builds a WebView's accessibility tree on the first request for it. Measured on API 35 / WebView 124: a read taken 6 s after page load still shows the WebView with no children; the page appears only after a read has asked for it — 10–400 ms later for a small in-app page, ~600 ms later for a Chrome tab over a long article. `describeAndroid` re-reads in 250 ms steps within a 1.5 s budget while the tree still has the unread shape, on both paths. A screen without a WebView never pays for it (one call, pinned by test). A WebView that stays empty after the budget is labelled `(no web content exposed)`. The unread shape is an `android.webkit.WebView` with no children, or — for a browser tab, which has no WebView view of its own before the page is published — Chrome's childless content view labelled "Web View" (English UI only; other locales see the tab the way they do today). ### Measured on a Pixel 9 / API 35 emulator with the branch tool-server | Screen | Before | After | |---|---|---| | In-app checkout form, cold launch | `WebView "[web-view] (no label)"` | full form in one call, 1.2 KB, 0.6 s | | 120-paragraph page | sentinel | the ten visible paragraphs, 1.8 KB (existing scroll clip bounds it) | | Wikipedia article in Chrome, helper attached after the page loaded | `FrameLayout "Web View"` | 4.7 KB, heading, links, lists, 1.4 s | | Settings home (native) | — | byte-identical to the previous parser | ### Testing - New `android-webview-describe.test.ts` on three live captures checked in as fixtures (in-app, Chrome, cold): merged landmark, `List` role, heading kept, re-read fires on the cold in-app shape and on Chrome's content-view placeholder, never on a native screen, and gives up within the budget. - `uiautomator-parser-v2-trim.test.ts`: the opaque-leaf case is replaced by the merge, the empty-WebView leaf, and a native `View` that must not be remapped. - Full tool-server suite, `tsc`, eslint, prettier, docs build. ### Docs No docs, skill or tool-description change. The page already names the accessibility tree as the source on Android; WebView content is now part of that tree and the agent needs no new instruction, since the cold read no longer produces an empty WebView. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01GZbkzeVC9faSxTFi4DbMuT <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Android WebView content is now captured and presented with meaningful controls, text, lists, and links. * Web content is consolidated into a clearer, unified accessibility view. * **Bug Fixes** * Improved Android screen descriptions when WebView content is initially unavailable by retrying the accessibility hierarchy. * Added safeguards for WebViews that never expose content, including a clear fallback message. * **Tests** * Expanded coverage for in-app WebViews, Chrome tabs, cold captures, and native Android UI filtering. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Hubert Gancarczyk <hubert.gancarczyk@swmansion.com> |
||
|
|
236db0077f |
fix(tool-server): support ios-remote in the await tools (#1051)
## Problem
The `await-ui-element` and `await-screen-idle` tools refused a remote
simulator. The tool server sent this error:
```
Tool 'await-ui-element' is not supported on ios-remote simulator (no ios-remote support declared).
```
Both tools poll the same accessibility tree as `describe`. The
`describe` tool reads that tree correctly on a remote simulator.
Therefore the tree source was never the problem.
## Cause
Each tool declares its platform support in a capability record. The
`describe` tool declares `appleRemote`. The two wait tools did not
declare it. The HTTP layer rejects the request before the tool starts.
Three more branches test `platform === "ios"`. That test is false for a
remote device. These branches select the tree source, the dependency
preflight, and the tvOS probe.
Nobody excluded the platform deliberately. Git history has no commit
that adds `appleRemote` to either file. The `appleRemote` concept
arrived on 2026-07-01. The `await-screen-idle` tool arrived two days
later.
## Effect on an agent session
The MCP layer calls `await-screen-idle` before each automatic
screenshot. On a remote simulator that call failed. The fallback then
slept for the full budget:
- 1500 ms for each tap or gesture
- 2000 ms for `open-url`
- 3000 ms for `launch-app` and `restart-app`
- 15000 ms for `run-sequence`
The user saw no error. The fallback exists for an old tool server, not
for a current server that refuses a device.
## Change
This change adds three lines to each tool:
1. The capability record declares `appleRemote: { simulator: true }`.
2. The tree fetch sends a remote device to `describeIos`. Before, a
remote device fell through to the Chromium branch.
3. The dependency preflight requires `sim-remote`, and not `xcrun`.
The `describe` tool uses the same three settings. The ax-service
blueprint sends the accessibility read through the sim-remote tunnel.
## Test result
I ran the branch tool server against a leased Argent Cloud runner. The
device was a remote iPhone 17 Pro on iOS 26.5. The app was Bluesky. The
app read its bundle from a local Metro server through the sim-remote
tunnel.
| ID | Case | Result |
| --- | --- | --- |
| T1 | `visible` by text | `success: true` |
| T2 | `exists` by identifier | `success: true` |
| T3 | `exists` by a second identifier | `success: true` |
| T4 | `text` condition matches the label | `success: true` |
| T5 | compound role and text selector | `success: true` |
| T6 | absent text gives `unmet` | `success: false` |
| T7 | wrong expected text gives `unmet` | note quotes the actual text |
| T8 | 5000 ms budget honoured | `elapsed: 5003` |
| T9 | `hidden` on an absent element | `success: true` |
| T10 | screen settles | `settled: true, polls: 2` |
| T11 | screen honours `minStableMs: 2000` | `waitedMs: 2265, polls: 8`
|
| T12 | wait across an app restart | `success: true, elapsed: 27301` |
| T13 | `hidden` after the restart | `success: true` |
| T14 | flow record boundary | UNKNOWN verdict |
All 14 cases passed. Two results give the strongest evidence:
- Case T12 blocked for 27.3 seconds across an app restart. The app
rebuilt its bundle, the element appeared, and the tool returned success.
- Case T6 returned `cause: "unmet"`. A failed tree read returns `cause:
"unreadable"`. Therefore the tool read the remote tree and judged the
condition.
### Unit tests
Five new unit tests drive both tools with a remote udid. They use a mock
accessibility service. Each test fails against `main` and passes with
this change.
Local checks:
- `npm run build`: clean
- `npm test -w @argent/tool-server`: 431 files, 6153 tests, 0 failures
- `npm run lint`: clean
- `npx prettier --check`: clean
## Note on flow records
The flow tools declare no capability. Therefore they accept a remote
device today. This change lets the re-probe reach `fetchTree`, which has
no support for `ios-remote`. The recorder catches that error. The
recorder then keeps the step and marks the verdict UNKNOWN, not
known-bad.
An earlier test asserted the refusal. This change inverts that test and
records the new behavior in its comment.
## Documentation
This change needs no documentation update. The tool reference lists
these two tools with a description only. That page has no platform
column, and no page describes remote simulators.
---------
Co-authored-by: Hubert Gancarczyk <claude-hubert.gancarczyk@swmansion.com>
|
||
|
|
0f90d182b2 |
feat(flows): record a script step by running it (#940)
#864 (the executor) and #865 (the `script:` step) are merged. This branch now sits directly on `main` as a single commit, so the diff and the commit list are this change alone. A `script:` step could only enter a flow by being typed into the YAML after the recording ended. The authoring skill said so in as many words, and listed it among the legal unrecorded insertions. That made it the one directive exempt from every rule the recorder exists to enforce, and it cost two things: - **The script never ran while the flow was authored.** It first executed during the closing replay, where a failure reads as a flow that was never right rather than a script that needs one more attempt. Worse, the device steps recorded after it were walked against backend state the author had established by hand — so the recording proved nothing about whether the script establishes that same state. - **The author never saw what the script returned.** A later release reads `{{output:user.id}}`; the only way to know that path exists is to have seen the document. This adds one MCP tool, `flow-add-script`, that runs a local `.mjs` and records the step it ran. **No new flow syntax** — the `script:` step is unchanged, and this is a second way for one to enter a flow file. ```text flow-add-script { name: "checkout", project_root: "/Users/dev/shop", path: "../../scripts/seed-order.mjs", timeout: 60000 } ``` ## What it adds - **One shared execution path.** Path resolution, the on-disk casing guard, the executor call and the verdict were private to `flow-run.ts`. They are now `runFlowScriptStep` in `flow-script-step.ts`, with the file-ref helpers in `flow-file-refs.ts`, and `runScriptStep` is that call plus its `ExecState` plumbing. This is the load-bearing change: "the recording runs the script exactly as replay will" is one function, not two code paths that agree today. - **A failure records nothing.** The opposite of `flow-add-step`, deliberately. A failed script did not establish the state the rest of the walkthrough would then be recorded against, so an appended red step costs every step after it, not just itself. The `message` also says whether a child actually *ran* — a path that resolved to no file forked nothing, while a script that threw halfway did not roll back — because the author's next move is a retry or a cleanup and they cannot pick without knowing. - **The returned document, as JSON text.** Text rather than an object because this is the first tool result carrying bytes the server did not author: every result is deep-walked for `__argentClientFile` and `__argentArtifact` directives, which match on shape alone and write or fetch files on the agent's machine. As a string it inherits none of that. Bounded at 64 KiB with `outputTruncated`, for the reason `log` is bounded. - **`{{output:` refused by `parseFlow`.** Surfacing the document invites an author to write a reference that has no meaning yet — it would be printed literally by a step that passes, green and wrong. The scan runs over exactly the field list the output release declares supported, so it is a seam that release converts from reject to resolve rather than scaffolding it deletes. Patterns are excluded: a `{{` in a regular expression is a quantifier the author meant. - **Two boundaries.** A `client`-mode recording is refused — its `.mjs` never left the agent's machine, the same anchor reason `run:`, `snapshot` and uploaded flows are refused. And the tool is `longRunning`, so the MCP adapter's 30s fetch abort cannot retry a call whose whole purpose is a side effect; the comment records what that flag does *not* close, and where the rest belongs. - **`flow-add-step` says when the call already ran.** The `{{output:` refusal is the one refusal this release adds on a path where the tool call has already fired, and its message asks for an edit-and-retry — which repeats the device action. Verified: a `keyboard` step recorded twice left `{{output:code}}{{output:code}}` in the field. That one refusal now carries what already happened. ## Documentation Rule 5 of `argent-create-flow` no longer lists `script:` as a legal unrecorded insertion — leaving it there would tell agents to keep doing the thing this PR removes. `live-authoring.md` gains the recorder contract, the return fields that are not self-evident, and the ordering rule: a setup script is recorded *before* the `restart-app` it prepares state for, because that is where it runs at replay. A leading script does not make the flow a fragment. `qa-flows` and `reliability-and-recovery` follow the count and the wording. ## Verification `npm run build`, `npx eslint . --max-warnings 0`, `npx prettier --check` over tracked files, and `npm run typecheck:tests -w @argent/tool-server` are green. `npm test -w @argent/tool-server`: 358 files, 4657 passed, 1 skipped. Exercised end to end against a live tool server built from this branch, not only in tests — recorded a flow, then replayed the saved YAML through `argent flow run` with no hand editing: ``` flow-add-script → status "pass", durationMs 124 log: "seeding order for the flow\nwarn: using the fake backend\n" outputJson: {"order":{"id":"ord_4417","total":39.5}, "user":{"id":"usr_9","email":"buyer@example.com"}} recorded: 1. script: ../../scripts/seed-order.mjs (timeout 30000ms) ``` ```yaml steps: - script: path: ../../scripts/seed-order.mjs timeout: 30000 - echo: order seeded; expect Home ``` ``` Flow "checkout" ✓ 1 script ../../scripts/seed-order.mjs │ seeding order for the flow │ warn: using the fake backend › order seeded; expect Home PASS — 1 passed, 0 failed, 0 errored, 0 skipped ``` The three refusals, live on the same recording: - A script that threw after creating records: `status "fail"`, `stepCount` unchanged at 1, and the message naming the side effects that were not rolled back. - A path resolving to no file: refused before any fork, no `durationMs`, and "Nothing ran, so there is nothing to clean up". - `flow-add-echo` holding `{{output:user.id}}`: refused with the author-facing message naming the step, the field and the later release. No device is involved at any point — a recording may open with a seeding script before it boots anything. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added the `flow-add-script` tool to run local `.mjs` scripts and record successful executions in flows. * Added script status, output, duration, logs, and failure details to recording results. * Added clearer handling for script cancellation, timeouts, missing files, and path casing. * **Documentation** * Updated flow guidance to clarify end-to-end classification and script setup or cleanup usage. * Documented script path requirements, failure conditions, and recording workflows. * **Bug Fixes** * Flows now reject unsupported output-reference templates with more precise diagnostics. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
9c4bc9ef5b |
feat(flows): run an external script as a flow step (#865)
Stacked on #864, which adds the executor. This branch adds the `script:` step itself: the YAML directive, the runner integration, the CLI and MCP rendering, and the authoring reference. ```yaml - script: { path: ../../scripts/seed-order.mjs } - script: { path: ../../scripts/seed-order.mjs, timeout: 60000 } ``` A `script:` step runs an external `.mjs` file from the project, in a fresh Node process. What the script does is its own business — seeding an order, creating a test account, cleaning up what a run left behind are the reasons flows have wanted one. It drives no device, so a flow of nothing but scripts runs with nothing booted. ## What it adds - **The directive.** The value is always a map; `path` is required and obeys the same name rules a `run:` target does, spelled for `.mjs`. It resolves against the directory of the flow file containing the step, so a fragment reaches the same script whichever flow composed it. A mis-cased path is refused rather than run, because macOS and Windows would open it and Linux CI would not. - **The report.** The script's stdout and stderr come back on the step and print under the step line, on a pass as well as a failure — it is the only record of what the step did to the backend. Both renderers (CLI, MCP) carry it, live and buffered. - **The fail/error split.** A `fail` is the script's own answer; an `error` is the host's. That is what lets CI tell a regression from the machine it ran on. - **Two boundaries.** An uploaded flow carrying a `script:` step is refused before anything runs — its `.mjs` never left the client. A flow whose script step sits beside a `run:` step still resolves a device. ## Review follow-ups The last three commits answer a review of the three above them. - A launch behind a leading `script:` was not read as the flow's leading launch, so a Chromium flow that seeded a backend first hoisted no boot, bound to whatever browser happened to be up, and passed a launch it never performed. The same lead-in walked a fragment past the `executionPrerequisite` refusal its `echo:`-led twin is given. Both readers now share one exhaustively-switched predicate. - Coverage: the whole failure-kind table, the mis-cased arm CI never ran, the two unreached `scriptFileProblem` arms, symlink resolution for a script path, and the CLI live-progress closure that no test had executed. - Docs: the reference claimed the log was secret-redacted (it is not), never mentioned the environment allowlist, the working directory, the time-limit ceiling or the log caps, stated a fail/error rule that was not the code's, and was missing from its own table of contents. The skills stated a closed list of legal unrecorded insertions that excluded the only way this step can ever be written. ## Verification `npm run build`, `npx eslint . --max-warnings 0`, `npx prettier --check .`, `npm run knip`, `npm run typecheck:scripts` and `npm run test:scripts` are green, as are the three `tsconfig.test.json` typechecks. Suites: tool-server 357 files / 4552 passed, `@argent/cli` 469, `@argent/mcp` 83, `@argent/registry` 90. Reproduced end to end against a real tool server and a real Electron app, not only in tests: a flow of `script:` then `launch:` now boots its own Chromium instance and reports it, where before it attached to a stray browser and reported a green launch for an app path that did not exist. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added support for local `.mjs` scripts as flow steps, including configurable timeouts, validation, cancellation, and device-free execution. - Script output is displayed in CLI runs and included in MCP flow results, with indentation and truncation notices. - Script failures now provide clearer diagnostics and verdicts. - Flow authoring tools recognize and guide creation of script steps. - **Documentation** - Added guidance for script syntax, execution limits, output handling, classification, and remote-run restrictions. - Updated flow creation and QA guidance to include script steps. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Hubert Gancarczyk <claude-hubert.gancarczyk@swmansion.com> |
||
|
|
c573e9c524 |
feat(telemetry): report device_kind next to platform on tool events (#1049)
## Why Telemetry cannot tell a physical iPhone from a simulator: `platform` is `ios` for both, and the tool-server's `kind` never reaches the events. Physical-iOS support shipped in 0.24.0 with no way to count its adoption except through `ios_device_*` failure stages on `tool:fail`. Physical Android phones have the same gap. ## What `tool:invoke` / `tool:complete` / `tool:fail` gain `device_kind: simulator | emulator | vvd | device | app`, derived from the same device id as `platform` (single classifier in `telemetry-platform.ts`, which `debugger:tool_outcome` and `lens:*` also go through for their platform). `platform: ios` + `device_kind: device` is a physical iPhone. The kind is emitted only for positively recognised id shapes, so the metric is not polluted by ids that are not devices: | id shape | platform | device_kind | |---|---|---| | simulator UUID / `remote:` | ios / ios-remote (tvos when the cache is warm) | simulator | | physical iPhone UDID | ios | device | | `emulator-NNNN` | android (android-tv when warm) | emulator | | USB serial, 6–20 alnum with a digit and a letter | android | device | | non-loopback `ip:port` | android | device (best effort) | | loopback `127.0.0.1:port` / `localhost:port` / `::1:port` | android | emulator | | anything else non-iOS-shaped (Metro 40-hex handle, `booted`, `Pixel_7`, IPv6, mDNS) | android | omitted | | `chromium-cdp-<port>` / `amazon-…` | chromium / vega | app / vvd | | `avdName`-only boot | android | omitted | A child sub-tool that names its own device replaces the parent's platform, kind and provider label wholesale, so an `avdName`-only child never reports the parent's `simulator` next to its own `android`. A logical-keyed Metro session strips the kind. `unknown` (in the registry `DeviceKind`) is rejected by the sanitizer like `platform: "unknown"`; no registry change. ## Querying - Physical iPhone adoption: `tool:invoke` where `platform = 'ios' and device_kind = 'device'`, distinct users over it. `isIosPhysicalUdid` is a positive match, so invoke is clean. - Physical Android: count on `tool:complete` (the device answered). `tool:invoke` / `tool:fail` still carry residual mistakes that happen to look like a serial (`Pixel7`), and a non-loopback `ip:port` may be a networked virtual device. ## Docs - `Telemetry.md` → 1.04, effective date 7 September 2026 (adjust to the release date on merge). Discloses the new category and, while touching that section, the external-provider label that #735 added without a notice line — happy to drop that second bullet if it should ship separately. - No docs-site change: `docs/reference/telemetry.mdx` links to `Telemetry.md` and has no per-property list. ## Notes - Pre-existing, unchanged: HTTP-edge failures carry no `tool_invocation_id`; `stop-all-simulator-servers` declares no capability, so its invoke row has neither field while its fail row has both. `device_kind` follows `platform` in each site. - Legacy 40-hex iPhone UDIDs already classify as `android` today; they now get no kind rather than a wrong one. - Design went through three adversarial review rounds before implementation and one on the diff; the fallback-bucket and child-attribution rules above came out of those. ## Verification - `packages/telemetry`: 319 tests, `packages/tool-server`: full suite 5670 passed / 1 skipped; `typecheck:tests` both packages; `eslint --max-warnings 0`; `knip`; prettier. - New: `test/telemetry-device-kind.test.ts` (the shape table above), plus cases in `http-tools-meta`, `http-platform-alias`, `sanitize`, `registry-listener`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01BSGpeAwvoCv2JAhbLsuto2 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Telemetry now records a device category—such as simulator, emulator, physical device, virtual device, or desktop app—alongside the platform. * Device categories are inferred from device information and may include a short label for external device providers. * Device category information is included in tool invocation, completion, and failure telemetry when available. * Device identifiers themselves continue to be excluded from telemetry. * **Documentation** * Updated the Argent Privacy Notice (Telemetry) to version 1.04, effective 7 September 2026. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |