mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
@pFornagiel/CI
289 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
12f6ff1115 | chore: bump to 0.7.1 | ||
|
|
8a3b5a1ff7 |
fix(react-profiler): self-bootstrap DevTools backend when no client is attached (#205)
## Summary `react-profiler-start` failed in any RN bridgeless dev build with no external React DevTools client (Fusebox React tab / `npx react-devtools`) connected — returning the misleading error *"No React renderer interface attached yet. Wait for the app to render its first commit and retry."* even when the app had rendered. This PR makes the tool self-bootstrap the DevTools backend so profiling works out of the box, and replaces the one-size-fits-all error with mode-specific actionable messages. Companion to #199 (schema fix). With both landed, `react-profiler-start` is end-to-end usable from MCP clients against a vanilla RN 0.76 dev app with zero extra setup. > [!IMPORTANT] > The bootstrap path mutates global runtime state (Bridge + Agent + hook subscriptions via `connectWithCustomMessagingProtocol`). Unit tests cover every reason branch, but the live behavior — especially coexistence with a later-attached `npx react-devtools` client and the absence of subscription leaks across repeated start/stop cycles — must be **smoke-tested against a working RN application** before this merges. ## Root cause argent's profiler delegates React commit capture to the in-app DevTools backend via `rendererInterface.startProfiling(...)`. The two maps on `__REACT_DEVTOOLS_GLOBAL_HOOK__` look similar but are populated by different actors: | Map | Populated by | When | |---|---|---| | `hook.renderers` | React itself via `hook.inject(renderer)` | First render | | `hook.rendererInterfaces` | DevTools backend's `attach(hook, id, renderer, global)` (in `react-devtools-shared/src/backend/renderer.js`) | When `initBackend(hook, agent, global)` runs | In an RN 0.76 bridgeless dev build, `setUpReactDevTools.js` runs at bundle load and calls `react-devtools-core.connectToDevTools({ host: 'localhost', port: 8097, websocket })`. That opens a WebSocket; `initBackend` only runs on `ws.onopen`. With no DevTools client listening on 8097, the connection never opens, `initBackend` never runs, `rendererInterfaces` stays empty — but `hook.renderers` is populated because React doesn't care about the backend. So `rendererInterfaces.size === 0` while `renderers.size === 2` is the steady-state of a dev build with no DevTools client. argent's pre-flight check saw the empty map and threw with a misleading message ("wait for first commit") that conflated three distinct failure modes: 1. App hasn't rendered yet (legitimate wait). 2. App rendered but no DevTools backend attached (this PR — the common case). 3. Production build / no `react-devtools-core` in bundle (different recovery). Direct CDP verification of the hook from the bug state: ```js { rIds: [1, 2], riIds: [], reactDevtoolsAgent: undefined } ``` ## Fix When the pre-flight detects `rendererInterfaceFound: false` with the hook present, `react-profiler-start` now runs a self-bootstrap script (`BOOTSTRAP_DEVTOOLS_BACKEND_SCRIPT`) that: 1. **Looks up `react-devtools-core` in the Metro module registry** by scanning `__r.getModules()` entries for a `verboseName` matching `react-devtools-core/dist/backend`. Handles both `Map<id, meta>` (modern Metro) and array-of-meta (older Metro) shapes — mirrors the pattern in `debugger-component-tree`. 2. **Calls `connectWithCustomMessagingProtocol`** (rdt-core ≥5.1) with no-op `onSubscribe` / `onUnsubscribe` / `onMessage` handlers. The function internally constructs a `Bridge` + `Agent` and runs `initBackend(hook, agent, window)`, which iterates `hook.renderers` and calls `attach()` on each — exactly the call path that populates `rendererInterfaces`. The fake wall discards every outbound message, which is fine because argent calls into the rendererInterface directly and never reads from the bridge. 3. **Returns one of seven distinct reasons**, each mapping (via `bootstrapFailureMessage` in `utils/react-profiler/devtools-bootstrap.ts`) to its own actionable error message: - `no-hook` → "production build" guidance - `no-renderers` → "wait for first commit" - `no-metro-modules` → bundle isn't Metro-style - `no-rdt-module` → react-devtools-core stripped from bundle (production) - `unsupported-rdt-version` → rdt-core <5.1 (RN <0.74) → fall back to `npx react-devtools` - `metro-scan-error` / `bootstrap-threw` / `bootstrap-no-effect` → unexpected, with verbatim error text After a successful bootstrap, `REACT_NATIVE_PROFILER_SETUP_SCRIPT` is **re-run** to install argent's `__argent_startWrapped__` wrappers on the freshly-attached interfaces. Without this re-run, `buildStartScript`'s post-start `__argent_isProfiling__` check would falsely report `startProfiling-threw` (the wrapper sets the flag; an unwrapped interface leaves it false even on a successful start). ### Idempotency / leak protection `initBackend` adds subscriptions to `hook.sub('renderer', ...)` on every call, so naive re-invocation would leak listeners. The script short-circuits with `reason: 'already-attached'` when `rendererInterfaces.size > 0`, so repeated `react-profiler-start` calls within a session never re-bootstrap. Verified empirically: listener counts stay flat across multiple start/stop cycles. ### Coexistence with external DevTools If `npx react-devtools` is later started while argent has already bootstrapped, RN's bundled reconnect path calls `connectToDevTools` again, which goes through `attachRenderer` → sees `rendererInterface != null` (argent's already there) → re-emits `renderer-attached` without re-attaching. Both backends coexist; argent talks to the renderer interface directly, so the active "agent" is irrelevant to profiling. ## Files changed - `packages/tool-server/src/utils/react-profiler/scripts.ts` — adds `BOOTSTRAP_DEVTOOLS_BACKEND_SCRIPT` (the injected IIFE that does the Metro scan + connect call). - `packages/tool-server/src/utils/react-profiler/devtools-bootstrap.ts` *(new)* — `BootstrapResult` type, `BootstrapReason` union, and the `bootstrapFailureMessage()` helper that maps each reason to its agent-facing error. Mirrors the `session-ownership.ts` convention (types + pure helpers as a sibling of `scripts.ts`, unit-testable without CDP / vitest mocking). - `packages/tool-server/src/tools/profiler/react/react-profiler-start.ts` — calls bootstrap on empty `rendererInterfaces`, re-runs setup, throws via the imported `bootstrapFailureMessage`. Also replaces two pre-existing error strings ("hook not present", `how_to_reclaim`) with the same agent-friendly phrasing used elsewhere in this PR. - `packages/tool-server/test/react-profiler/bootstrap-devtools-backend.test.ts` *(new)* — 12 unit tests covering every reason branch (Map + array Metro registries, require() throws, getModules throws, connect throws, connect is a no-op, idempotent already-attached path). ## Manual verification (iPhone Air, RN 0.76.7 bridgeless, no external DevTools) 1. Reload bundle → confirm `rIds: [1,2], riIds: []` (bug state). 2. Pre-fix: `react-profiler-start` returns *"No React renderer interface attached yet…"* 3. Post-fix: `react-profiler-start` returns `{ started_at, hermes_version: "for RN 0.76.7", detected_architecture: "bridgeless" }`. Hook now shows `riIds: [1,2]`, `__argent_startWrapped__: true` on each, `reactDevtoolsAgent` set, `__argent_isProfiling__: true`. 4. Forced a React state-hook dispatch during the session and stopped: `total_react_commits: 2`, `unattributed_ms: 8.41` — real commit data captured through the bootstrapped backend. 5. Second `react-profiler-start` in same session returns success via `already-attached` early-exit (verified directly against the script). Hook listener counts (`renderer`, `renderer-attached`, `react-devtools`) stable across multiple cycles — no subscription leak. 6. Simulated `no-renderers` by clearing `h.renderers` → got *"React has not rendered yet, so there is nothing to profile. Ask the user to wait until the app shows React content…"* |
||
|
|
74481155af |
fix: remove hardcoded model: haiku from argent-environment-inspector skill (#206)
Removes the `model: haiku` frontmatter field from `packages/skills/agents/argent-environment-inspector.md`. The hardcoded model caused non-Anthropic coding harnesses (e.g. Gemini) to fail when loading the skill, since `haiku` is an Anthropic-specific model identifier that those runtimes don't recognize. Dropping the field lets each harness use its own default model. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kacperkapusciak <39658211+kacperkapusciak@users.noreply.github.com> |
||
|
|
bea43f555a |
feat: per-step delayMs on flow steps (#198)
- Tool steps in a flow YAML may carry an optional `delayMs` that sleeps that long *before* the step runs; `flow-add-step` exposes the same field. - New `longRunning?: boolean` field on `ToolDefinition`; when set, the MCP fetch wrapper bypasses its 30s per-request timeout (`fetchTimeoutMs: null`) so long invocations aren't aborted. `run-sequence` and `flow-execute` opt in. |
||
|
|
8ce8f8e573 |
fix: 100% CPU usage on broken stdio (#191)
One of our users reported occasional 100% CPU usage stale MCP server process. I wasn't able to reproduce this bug directly in Codex. We only have non-codex repros that are able to consistantly reproduce a bug that *looks* like the one reported to us. The bug in question is: Our error handler causes errors which throws it into an infinite loop. <img width="713" height="251" alt="image" src="https://github.com/user-attachments/assets/4b9fad41-55d0-49ab-b19c-25b7f5c12d12" /> ### More details: When `argent mcp` runs as an orphaned process whose stderr pipe is broken (e.g. parent died), every `stderr.write` from the uncaughtException handler emits an async 'error' event. Without an 'error' listener that becomes another uncaughtException, runs the handler again, and so on forever. Move the handlers into `fatal-handlers.ts` and add three guards: - 'error' listeners on stdout/stderr exit cleanly when stdio is broken, before the failure becomes another uncaughtException - try/catch around `stderr.write` so a sync write failure exits instead of escaping - try/catch around the formatter so a throwing `.stack` getter or `toString` (the production trace pointed at defaultPrepareStackTrace) can't take down the handler Idempotent: a second `installFatalHandlers` call is a no-op. |
||
|
|
837d088744 |
test: cover #148 (android) (#186)
This PR adds tests for #148. ## Summary Moves the Android emulator branch's test coverage out of #148 so that PR can be reviewed without the test churn. This branch adds the test files back on top of the (now test-stripped) base branch. Tests inseparable from source changes (`describe-tool`, `workspace-reader`, `workspace-reader-integration`) and the `boot-simulator.test.ts` deletion stayed on #148 - base PR's CI passes on its own. ## Test plan - [x] `npx tsc --build` — clean - [x] `npx prettier --check .` — clean - [x] `npm test --workspaces --if-present` — 585/585 tool-server tests pass; all other workspaces green --------- Co-authored-by: Paweł Fornagiel <pawel.fornagiel2@gmail.com> |
||
|
|
85e7aa9612 |
fix(registry): emit JSON Schema type for ZodDefault fields (#199)
## Summary
`zodTypeToJsonSchema` in `packages/registry/src/zod-to-json-schema.ts`
did not handle `z.ZodDefault`. Any tool input field declared as
`z.X().default(...)` fell through to `{}`, dropping the type.
Downstream, the MCP harness sent values for those fields as strings, and
the tool-server's strict Zod validators rejected them with `Expected
number, received string` / `Expected boolean, received string`.
Also fixes a related issue in the same file: `isOptional` returned
`false` for `ZodDefault`, so defaulted fields were pushed into
`required` even though the caller doesn't have to provide them.
Tools that misbehave in practice today because of this and start working
after this fix:
- `stop-metro` — `port`
- `react-profiler-start` — `port`, `force`, `sample_interval_us`
- `debugger-component-tree` — `onScreenOnly`, `includeSkipped`
(Reproduced against the published `argent 0.6.1` MCP server while
profiling an Expo app — the `argent run <tool> --args '<json>'` CLI path
is unaffected because it doesn't go through the published JSON Schema.)
## Changes
- `packages/registry/src/zod-to-json-schema.ts`: handle `ZodDefault`
(recurse into `_def.innerType`, attach `default: defaultValue()`); treat
`ZodDefault` as optional.
- `packages/registry/tests/zod-to-json-schema.test.ts`: new file. Covers
`z.number()`, `z.boolean()`, `z.string().optional()`,
`z.number().default(...)`, `z.boolean().default(...)`,
`z.coerce.number().int().positive().default(...)`, and a mixed-object
happy path.
## Test plan
- [x] `npx vitest run` in `packages/registry` — 64 passed, 0 failed (4
files; the new test file contributes 7 cases).
- [ ] Reviewer sanity check: load the rebuilt MCP server and confirm a
tool with a `.default(...)` numeric/boolean field now publishes the
correct \`type\` in its input schema.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
60ae7cb767 | chore: bump versions to 0.7.0 | ||
|
|
ad502a46ed | chore: bump argent-private submodule | ||
|
|
d017ba340c |
feat: Android emulator support (#148)
### For reviewers - what to focus on: The diff is ~6k LoC; this breakdown should help triage where to spend attention: | Bucket | Lines | Effort | | --- | --- | --- | | **Net-new Android logic**: `boot-device.ts` (660) + `adb.ts` (399) + `uiautomator-parser.ts` (292) | 1351 | **Read carefully** | | Blueprint `DeviceInfo` refactor: `simulator-server`, `ax-service`, `native-devtools` | 365 | Read | | Tool `index.ts` / `types.ts` sprawl across ~20 tools (capability gates, `services()`, descriptions) | ~965 | Skim — watch for cross-file consistency | | `platforms/android.ts` impls (stub → real implementation) | ~426 | Skim | | Skill/docs prose, profiler rename ripple | ~1000 | Skim | | **`platforms/ios.ts` deletions** — 12 files, 0 additions; logic moved into unified handlers | 684 | **Skip — mechanical** | ## What works, what doesn't: ### Just works thanks to either sim server or metro: - `gesture-*` - `button`, `keyboard` - `screenshot` - `run-sequence`, `flow-*` - `debugger-*` - `react-profiler-*` ### Adjusted to work on android too: | tool | android | ios | | --- | --- | --- | | `describe` | `uiautomator dump` → `DescribeNode` | `AXServiceApi` | | `launch-app` | `cmd package resolve-activity` + `am start -W` | `simctl launch` | | `restart-app` | `cmd package resolve-activity` + `am force-stop` + `am start -W` | `simctl terminate` + `simctl launch` | | `reinstall-app` | `adb install -r` (+`-g`/`-d`) | `simctl uninstall` + `simctl install` | | `open-url` | `am start -a VIEW -d <url>` | `simctl openurl` | | `list-devices` | `adb devices` + `emulator -list-avds` | `xcrun simctl list --json` | | `boot-device` | snapshot probe → `-force-snapshot-load` hot boot with screencap-alive guard, falls back to cold boot on any failure | `simctl boot` + `bootstatus -b` | ### Will not work on android for now: - `native-devtools-*` (7 total) - `native-profiler-*` (3 total — was `ios-profiler-*`) - `profiler-stack-query`, `profiler-combined-report` (depend on `native-profiler-*`) - `paste` — unregistered on both platforms for now. --------- Co-authored-by: Paweł Fornagiel <pawel.fornagiel2@gmail.com> |
||
|
|
626c3274e2 |
chore: bump package versions to 0.6.1 (#193)
## Summary - Bumps all 9 published packages from `0.6.0` → `0.6.1`. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. - `packages/ui` (`0.0.1`) is on a separate version track and left as-is. - Lockfile updated via `npm install`; `npm run format` was a no-op. ## Test plan - [ ] CI green - [ ] Run the stable publish workflow against this commit and confirm publish succeeds |
||
|
|
9cd453b857 |
fix(skills): prefix bare skill references with 'argent-' (#183)
All skills are currently exposed as `argent-***`. In the markdown though, we refer to them as just `***`. This may confuse the agent when it tries reading `skills/***` instead of `skills/argent-***`. This PR changes all skill references in docs to the correct `argent-***` form. ## Why? There's no downside to this fix. Keeping it as-is potentially introduces some confusion especially if when the user is running a weaker model or if we encounter namespace conflicts. |
||
|
|
529fffb322 |
fix installer update version detection under npx (#188)
## Summary - fix `argent update` version detection when invoked via `npx @swmansion/argent`, so it compares against the actually installed global binary instead of the transient npx-cached package - add `getGloballyInstalledVersion()` that resolves the real global binary path (`which -a` / `where`), follows symlinks, and reads the owning `package.json` - add regression tests covering staged global-install layouts and npx-style transient paths to prevent false "already on latest" results |
||
|
|
55d1567d48 |
feat: improve iOS profiler startup, shutdown, and exit recovery (#181)
## Summary Refactor of the iOS profiler (`xctrace`-driven) session to make start, stop, and unexpected-exit handling proper, without the iOS hanging infinitely. ### Startup (`ios-profiler-start`) - **Locale ready signal**: subscribe-before-spawn to a Darwin notification (`com.argent.ios-profiler.started.<pid>.<ts>`) via `notifyutil`, passed through `xctrace --notify-tracing-started`. Falls back to the existing stdout substring match (`Starting recording` / `Ctrl-C to stop`) if `notifyutil` fails to spawn or register within 2 s. - **Startup timeout**: new 10 s cap on reaching the ready state — on timeout, `SIGKILL` the child so it cannot leak. - **Bounded `simctl` calls**: `detectRunningApp` now applies a 10 s timeout to both `xcrun simctl spawn ... launchctl list` and `xcrun simctl listapps`, with explicit error messages telling the user to verify the simulator is booted. - **Cold-start retry**: if xctrace's process resolver fails with `"Cannot find process matching name:"` (launchd has registered the bundle but xctrace's resolver hasn't caught up), the tool retries once after a 1.2 s backoff — up to 2 attempts total. On exhausted retries throws a classified error naming the cold-launch race and pointing at `app_process` as the override. Non-cold-start failures propagate immediately without retry. - **Exit listener installed**: every `xctrace record` child gets a long-lived `'exit'` handler (`handleXctraceExit`) that marks the session as `recordingExitedUnexpectedly` and stores `lastExitInfo`. ### Shutdown (`ios-profiler-stop`) - **SIGINT → SIGTERM → SIGKILL ladder** via `utils/ios-profiler/lifecycle.ts::shutdownChild` (30 s grace, 5 s term, 5 s kill). - **Returns a `warning`** when the ladder had to escalate past SIGINT, so callers know the trace bundle may be incomplete. ### Exit recovery - **New `recordingTimedOut` / `recordingExitedUnexpectedly` flags** on the session API. If `ios-profiler-stop` is called after either fired, the tool now exports whatever `.trace` bundle is on disk and returns it with a descriptive `warning` instead of throwing "no active session". - Covers the in-process 10-min recording cap, attached-app crashes, and simulator daemon hiccups. ### Dispose semantics - Registry teardown (process shutdown) skips SIGINT and goes straight to `SIGKILL` with a 1 s reap window. The explicit `ios-profiler-stop` contract is the only path that takes the slow finalise grace. ### Other - `utils/ios-profiler/run-with-timeout.ts`: `execSyncWithTimeout` wrapper with a 60 s default — guards every shelled-out `xctrace export` / `xctrace version`. - New reference doc `utils/ios-profiler/IOS_PROFILER_REFERENCE.md` covering the native foundations, capture flow, and pipeline. ## Tests New unit tests under `packages/tool-server/test/ios-instruments/` --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
dd702fc720 |
fix: detect missing global install in argent update (#180)
## Summary `npx @swmansion/argent update` falsely reported *"Already on the latest version"* after the user had removed the package via `npx @swmansion/argent uninstall`. `getInstalledVersion()` reads `package.json` from `PACKAGE_ROOT`, which under `npx` is the npx-cached copy of argent — always the registry's latest. The version compare then matches and short-circuits, hiding the fact that there is no global install at all. - Share `isGloballyInstalled()` between `init` and `update` via `utils.ts`; `update.ts` now routes through a fresh-install prompt (`Install @swmansion/argent@<latest> globally?`) when no permanent argent binary is on PATH. - Fix a Windows-only bug in the temp-runner check while the helper was being moved: `where argent` returns multiple lines, and the previous logic flagged the entire output as transient if *any* line contained an npx marker. Now each line from `where` / `which -a` is inspected independently, so a real global install is not masked by a concurrent npx invocation. - Add `isTempRunnerPath()` plus 5 unit tests covering npx, pnpm dlx, bunx, Windows dlx, and real global paths. ## Test plan - [x] `npm run build` (argent-installer) — passes - [x] `npm test` (argent-installer) — 231 passing (5 new) - [x] `npx prettier --write` on touched files — no diffs - [ ] Manual: `npm install -g @swmansion/argent` → `npx @swmansion/argent uninstall` → `npx @swmansion/argent update` should now offer to install the latest version instead of reporting "already on the latest" - [ ] Manual: `argent update` while globally installed and on latest still reports "already on the latest version" - [ ] Manual: `argent update` while globally installed and on an older version still prompts to update |
||
|
|
bb3f484d04 |
chore: bump package versions to 0.6.0 (#178)
## Summary - Bumps all 9 published packages from `0.6.0-next.2` → `0.6.0` (stable) in preparation for the `0.6.0` release. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. `packages/ui` (`0.0.1`) is on a separate version track and left as-is. - Lockfile updated via `npm install`; `npm run format` was a no-op. ## Test plan - [ ] CI green - [ ] Run the stable publish workflow against this commit and confirm publish succeeds |
||
|
|
f004c08a19 |
chore: bump package versions to 0.6.0-next.2 (#177)
## Summary - Bumps all 9 published packages from `0.6.0-next.1` → `0.6.0-next.2` in preparation for the next prerelease publish. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. `packages/ui` (`0.0.1`) is on a separate version track and left as-is. - Lockfile updated via `npm install`; `npm run format` was a no-op. ## Test plan - [ ] CI green - [ ] Run `Publish prerelease to npm (next)` workflow against this commit and confirm publish succeeds |
||
|
|
127a32a101 |
fix: installer throwing error (#175)
This PR fixes regression introduced in #162: ``` ◇ Add Argent tools to editor auto-approve lists? - recommended │ Yes node:internal/modules/cjs/loader:1459 throw err; ^ Error: Cannot find module '/Users/ignacylatka/.npm/_npx/9bf42f550c32750c/node_modules/@swmansion/argent/tool-server.cjs' at Module._resolveFilename (node:internal/modules/cjs/loader:1456:15) at defaultResolveImpl (node:internal/modules/cjs/loader:1066:19) at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1071:22) at Module._load (node:internal/modules/cjs/loader:1242:25) at wrapModuleLoad (node:internal/modules/cjs/loader:255:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 { code: 'MODULE_NOT_FOUND', requireStack: [] } Node.js v24.14.1 ``` ### Root cause `packages/argent-installer/src/mcp-configs.ts:22` resolves the bundled tool-server with: ```ts path.join(import.meta.dirname, "..", "tool-server.cjs") ``` That was correct in the pre-#162 layout where the file lived at `packages/mcp/dist/cli/mcp-configs.js` and `..` stepped from `dist/cli/` to `dist/`. After the refactor the file is inlined into `packages/argent/dist/installer.mjs` — a direct **sibling** of `dist/tool-server.cjs` — so `..` now climbs one level too far and resolves to `<pkg>/tool-server.cjs`. The codex adapter's `addAllowlist`/`removeAllowlist` calls `getAvailableToolIds()` → `execFileSync("node", [TOOL_SERVER_BUNDLE, "-t"])`, which is where the broken path is dereferenced. ### Fix Drop the `..`. Existing `packages/argent/src/bundled-paths.ts:7` already uses the sibling form, so this aligns the two. |
||
|
|
1f1f55f72e |
chore: bump package versions to 0.6.0-next.1 (#174)
## Summary - Bumps all 9 published packages from `0.6.0-next.0` → `0.6.0-next.1` in preparation for the next prerelease publish (the `0.6.0-next.0` train was blocked by the build-config and update-checker fixes in #172 and #173). - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. `packages/ui` (`0.0.1`) is on a separate version track and left as-is, matching #171. - Lockfile updated via `npm install` — only the 9 workspace `version` entries changed. ## Test plan - [ ] CI green - [ ] Run `Publish prerelease to npm (next)` workflow against this commit (after #172 and #173 land) and confirm publish succeeds |
||
|
|
22a4811cb1 |
fix(tool-server): use semver for update-checker comparison (#173)
## Summary - The homegrown `parseSemver` in `packages/tool-server/src/utils/update-checker.ts` only matched plain `MAJOR.MINOR.PATCH` and returned `null` for prerelease strings. After [#171](https://github.com/software-mansion/argent/pull/171) bumped the local version to `0.6.0-next.0`, `isNewerVersion` started returning `false` for **any** value of `latest`, because the *current* side failed to parse. Two consequences: - Users on the `next` dist-tag are never notified of new releases (silent feature degradation, not a crash). - The `detects an available update on startup` unit test broke because its asserted state now reads `{ updateAvailable: false, latestVersion: "99.0.0" }` instead of `{ true, "99.0.0" }` — see [run 25161184594](https://github.com/software-mansion/argent/actions/runs/25161184594/job/73755770427). - Replaces the regex parser with the `semver` package (already pinned at `^7.7.4` and present in the lockfile via `@argent/installer`). Adds it as a dep of `@argent/tool-server` along with `@types/semver`. - Keeps the existing UX rule: don't notify when upstream `latest` is itself a prerelease — npm's `latest` dist-tag should be stable, and we shouldn't push betas to stable users. `semver.gt` correctly treats a stable version as greater than its own prereleases, so a user on `0.6.0-next.0` will be notified when `0.6.0` ships. ## Behavior matrix | current | npm `latest` | before (broken) | after | |---|---|---|---| | `0.6.0-next.0` | `99.0.0` | not flagged | **flagged** | | `0.6.0-next.0` | `0.6.0` | not flagged | **flagged** | | `0.6.0` | `99.0.0` | flagged | flagged | | `0.6.0` | `1.0.0-beta.1` | not flagged | not flagged | | anything | invalid string | not flagged | not flagged | ## Test plan - [x] `npm test -w @argent/tool-server -- test/update-checker.test.ts` — all 10 tests pass (was 9/10). - [x] `npm test -w @argent/tool-server` — full suite 444/444 passes. - [x] `npx tsc --build` clean from the repo root. - [ ] CI green on this branch. |
||
|
|
be31093f6c |
chore: bump package versions to 0.6.0-next.0 (#171)
## Summary - Bump all published packages to `0.6.0-next.0` in preparation for the next prerelease train. - Affected: `@swmansion/argent`, `@argent/cli`, `@argent/installer`, `@argent/mcp`, `@argent/tools-client`, `@argent/native-devtools-ios`, `@argent/registry`, `@argent/skills`, `@argent/tool-server`. `@argent/native-devtools-ios` was at `0.5.1`; aligned with the rest. `packages/ui` (`0.0.1`) is on a separate version track and left as-is. ## Test plan - [ ] CI green - [ ] `npm next` prerelease publish workflow picks up the new version |
||
|
|
289fda7ea5 |
fix: opencode detect alternative filenames (#170)
OpenCode supports: - project-scope: `opencode.jsonc` and `opencode.json`; - global-scope: `config.json` (legacy/migrated), `opencode.json`, and `opencode.jsonc` This PR adds support for these alternative filenames. This PR also adds `jsonc` support. <img width="502" height="377" alt="image" src="https://github.com/user-attachments/assets/a0f1d437-93ce-4677-bc6f-b8d0c8427b57" /> |
||
|
|
aac6151cc9 |
fix: preserve old Zed config (#168)
Closes #165. `argent init` was wiping users' `~/.config/zed/settings.json` - Zed's settings format is JSONC, so `readJson` used `JSON.parse` and silently fell back to `{}` on any parse failure, after which the Zed adapter would overwrite the file with an argent-only stub. Co-authored-by: Filip131311 <contact@filipkaminski.com> |
||
|
|
62d0fe0c73 |
refactor: overhaul React profiler - DevTools backend, session ownership, richer data (#156)
## What this PR does
This refactor:
- replaces the DFS fiber-walking hook inside the app's JS runtime with
the **React DevTools backend** as the source of profiling data
- adds a session-ownership system so two tool-server instances can't
silently clobber each other
- gives agent a new status tool for recovery after interruptions
- improves what the profiling analysis report shows the agent
I realise this a big pull request, but changes began to affect each
other when it comes to registered scripts on the react-native side.
This, all-in-all, should however cover our needs when it comes to the
react profiler and if it proves sufficient - we will only have to
iterate on small fixes afterwards.
If I were you, I would skip over the implementation of scripts on the
native-side and focus on what the tool-server does when it comes to
profiling right now.
---
## Profiling workflow
The overall flow has not changed from the agent's perspective - agent
calls `react-profiler-start`, do something in the app, call
`react-profiler-stop`.
What changed is how the profiling data is collected.
**Before:** `react-profiler-start` injected a custom `onCommitFiberRoot`
hook that walked the live fiber tree at every React commit in DFS-like
way and appended records to a global `__ARGENT_DEVTOOLS_COMMITS__` array
in the app's JS heap. On stop, the array was fetched in chunks over CDP.
**Now:** `react-profiler-start` calls `ri.startProfiling(true)` on the
React DevTools backend's `rendererInterface`. React's own profiler
accumulates commit data internally. On stop, a single
`STOP_AND_READ_SCRIPT` call stops the backend and reads
`ri.getProfilingData()` in one round-trip. The DevTools backend data is
richer, more accurate, and doesn't require maintaining a bespoke
fiber-walking implementation.
---
## Session ownership
The profiler runs inside the app's JS heap. There's only one "slot": it
used to be that when two tool-server instances (or the same instance
after a process restart) both try to start profiling, the second call
silently overwrites the first and all data from the first session is
gone.
Right now, there is an approach which blocks silent overtake of the
profiling session.
When `react-profiler-start` runs, it:
1. Reads the current state from the runtime using `READ_STATE_SCRIPT` (a
pure, side-effect-free read).
2. If a session is already running, it reads the
`__ARGENT_PROFILER_OWNER__` object written into `globalThis`, which
contains `sessionId`, `startedAtEpochMs`, and `lastHeartbeatEpochMs`.
3. Classifies the existing session as **fresh** or **stale** using
`classifyStaleness()` (stale = no heartbeat for > 5 minutes).
If the session is fresh and you don't pass `{ force: true }`, start
refuses and returns an `already_running` payload instead of overwriting.
4. If the session is stale or `force: true`, it calls
`STOP_FOR_TAKEOVER_SCRIPT` to cleanly stop the prior session, then
starts a new one.
### Heartbeats
Each `sessionId` UUID is written to `__ARGENT_PROFILER_OWNER__` in the
runtime at start. While profiling, any side query tool
(`react-profiler-renders`, `react-profiler-fiber-tree`) that this same
tool-server process calls will bump `lastHeartbeatEpochMs` via
`HEARTBEAT_SCRIPT`. This keeps the session from being classified as
stale as long as the owning process is still making calls.
### What the agent sees when something goes wrong
Previously, if you called `react-profiler-start` into an already-running
session you'd get a silent clobber or a confusing Hermes error. Now:
- **Running session detected, still fresh:** `{ already_running: true,
owner: { sessionId, startedAtEpochMs, lastHeartbeatEpochMs },
age_seconds, how_to_reclaim }` — the agent can read the owner timestamp
and decide whether to tell the user to wait or force-reclaim.
- **Running session detected, stale:** automatic takeover with a log
note.
- **No owner record (e.g. another DevTools client started the
session):** also automatic takeover.
### New `react-profiler-status` tool
A read-only tool that reports the current state without starting or
stopping anything. Returns `session_status` as one of `"active"` /
`"taken_over"` / `"stopped"` / `"no_react_runtime"`, plus the full owner
record. Use this after a debugger disconnect, an unexpected error, or
when the agent restarts mid-session to decide whether to continue,
reclaim, or give up.
The agent has instructions to use the tool whenever it is interrupted
during the profiling by the user or otherwise. Will have to look into
how much this is actually used.
---
## What changed in the data we collect
The old hook read fiber fields directly (`fiber.type.displayName`,
`fiber.memoizedProps`, `fiber.selfBaseDuration`, etc.) at commit time.
This meant:
- Names for **transient components** (modals, tooltips, navigation
screens) that unmounted before stop were already lost - the fiber was
gone.
- Change-detection logic (which props changed, which hooks fired) was
reimplemented manually against the fiber internals.
The new approach reads `ri.getProfilingData()` which gives us the
DevTools backend's own commit log. Each commit entry has
`fiberActualDurations`, `fiberSelfDurations`, `changeDescriptions`,
`timestamp`, and `duration` - the same data React DevTools Profiler
shows in its browser panel.
### Fiber name cache for transient components
To address the lost-name problem, `FIBER_ROOT_TRACKER_SCRIPT` now hooks
`onCommitFiberRoot` and, immediately after the DevTools backend finishes
processing each commit (synchronously, while the fiber is still in
memory), populates `globalThis.__argent_fiberNames__` with `fiberID →
displayName` entries. Because fiber IDs are monotonically increasing and
never reused, this cache never goes stale. `STOP_AND_READ_SCRIPT` uses
this cache as a fallback for any fiber whose name
`getDisplayNameForElementID` can no longer resolve.
### Unattributed fiber tracking
When a transient fiber renders during the session but still can't be
named at stop time (not in the name cache, not in the DevTools instance
map), its work is now **surfaced rather than silently dropped**. The
stop tool computes `unattributedByCommit`: a per-commit tuple of
`[commitIndex, droppedFiberCount, droppedSelfMs]`. This flows through
to:
- The session metadata file on disk (`unattributedByCommit` array).
- The pipeline (`buildHotCommitSummaries` receives and indexes it).
- The `HotCommitSummary` type gains `unattributedMs` and
`unattributedFiberCount`.
- The generated report shows a warning line per commit with unattributed
work, e.g. `⚠️ 12ms unattributed — 3 fibers unmounted before stop`.
- `react-profiler-stop` returns top-level `unattributed_ms`,
`unattributed_fiber_count`, `unattributed_commit_count` in its response
so the agent immediately knows if there's a gap.
### Actual vs. self duration now both surfaced
The old report showed only `selfDuration` (exclusive render time). Both
are now captured in `HotCommitComponentEntry` and rendered in the report
as:
```
- `ComponentName` — 4.2ms self, 18.7ms w/children
```
A legend at the top of the report explains when to sum self and when not
to sum inclusive time. The summary table also shows both columns.
### Component name unwrapping
Display names from the DevTools backend can be wrapped:
`Forget(Memo(MyButton))`, `ForwardRef(Icon)`, etc. The pipeline's render
stage now strips these wrappers using `annotateComponentName()` and
appends a human-readable tag, so the report shows `\`MyButton\`
[React.memo + React Compiler]` instead of the raw internal string. The
raw DevTools name is still used in all tool-call suggestions (since
every pipeline stage keys on the original string).
---
## What scripts do what
All injected JS scripts now live in a single file —
`packages/tool-server/src/utils/react-profiler/scripts.ts` — grouped by
responsibility:
| Script | When injected | What it does |
|---|---|---|
| `REACT_NATIVE_PROFILER_SETUP_SCRIPT` | Every `react-profiler-start`
(idempotent) | Wraps `ri.startProfiling` / `ri.stopProfiling` to track
`isProfiling` state and `startedAtEpochMs` without clock skew; installs
the `__argent_profilerHeartbeat` helper |
| `FIBER_ROOT_TRACKER_SCRIPT` | On CDP connect (idempotent) | Intercepts
`onCommitFiberRoot` to populate the `__argent_fiberNames__` commit-time
name cache |
| `READ_STATE_SCRIPT` | Every `react-profiler-start`,
`react-profiler-status` | Pure read: returns `{ hookExists,
rendererInterfaceFound, isRunning, owner, nowEpochMs }` |
| `buildStartScript(ownerJson)` | `react-profiler-start` on the happy
path | Calls `ri.startProfiling`, writes owner record, returns
post-start verification flags |
| `STOP_FOR_TAKEOVER_SCRIPT` | `react-profiler-start` on the takeover
path | Stops the prior session cleanly before starting a new one |
| `HEARTBEAT_SCRIPT` | `react-profiler-renders`,
`react-profiler-fiber-tree` | Bumps `lastHeartbeatEpochMs` while the
owning process is still active |
| `STOP_AND_READ_SCRIPT` | `react-profiler-stop` | Stops the backend
profiler and reads `getProfilingData()` + name cache in one round-trip |
| `RESOLVE_FIBER_META_SCRIPT` | `react-profiler-stop` (if commits > 0) |
Walks the live fiber tree to collect `hookTypes`, `parentName`, and
`isCompilerOptimized` metadata per component |
|
||
|
|
c1fda3834b | fix: remove private vscode config from the code base (#167) | ||
|
|
dbf4de1ab0 |
refactor(tool-server): per-tool dirs + cross-platform dispatch (iOS only, Android-ready) (#164)
## Summary
Sets up the foundation for adding Android support without changing any
user-visible iOS behavior. iOS works exactly as before; Android handlers
are stubs that throw a clear \"not yet implemented\" message until
they're filled in.
### New primitives
- `Platform`, `DeviceKind`, `DeviceInfo`, `ToolCapability`,
`ToolDependency` (in `@argent/registry`)
- `classifyDevice(udid)` + `resolveDevice(udid)` (UUID shape → iOS, else
Android)
- `assertSupported(toolId, capability, device)` — throws
`UnsupportedOperationError`
- `ensureDep(s)` — host-binary preflight with install hints
- `dispatchByPlatform({ toolId, capability, ios, android })` — one-line
cross-platform `execute()`
### HTTP gating
`/tools/:name` now runs:
- Host-binary preflight (`requires`) → 424 with install hint
- Capability gate (`capability` + `udid`) → 400 with reason
### Per-tool directory layout
Every cross-platform tool now lives in its own directory:
\`\`\`
tools/gesture-tap/
index.ts ← ToolDefinition, capability, dispatchByPlatform
platforms/
ios.ts ← iOS impl (existing logic)
android.ts ← stub: throws \"not yet implemented\"
\`\`\`
Refactored 17 tools this way: gesture-{tap,swipe,custom,pinch,rotate},
screenshot, button, paste, keyboard, launch-app, restart-app,
reinstall-app, open-url, rotate, run-sequence, describe (with shared
contract + iOS adapters under platforms/).
### Tool call flow (iOS, today)
1. Agent calls \`gesture-tap { udid: \"<uuid>\", x, y }\`
2. HTTP zod-parses, then host-binary preflight, then capability gate
(passes — capability has \`apple\`)
3. Tool's \`execute()\` (built by \`dispatchByPlatform\`):
- \`resolveDevice(udid)\` → \`{ platform: \"ios\", kind: \"simulator\",
id }\`
- \`assertSupported\` → passes
- branches to \`tapIos(services, params, device)\`
4. iOS impl uses \`SimulatorServerApi\` exactly as before.
### Tool call flow (Android, future)
Same flow; the Android branch in \`dispatchByPlatform\` runs. Filling in
Android = three steps:
1. Implement the function in \`tools/<tool>/platforms/android.ts\`
2. Add \`android: { emulator: true, device: true, unknown: true }\` to
the tool's capability
3. Make \`services()\` platform-aware where the iOS service URN doesn't
apply (e.g. \`launch-app\` declares \`nativeDevtools\` for iOS only)
## Test plan
- [x] All 395 existing tests pass; 9 new tests for `device-info` +
`capability`
- [x] TypeScript clean across `@argent/registry` and
`@argent/tool-server`
- [ ] Manual smoke-test on a real iOS simulator (gesture-tap,
screenshot, describe, launch-app)
## Deferred to follow-ups
- **Per-tool capability declarations on iOS-only tools**
(`native-devtools/*`, `profiler/ios-profiler/*`, `network/*`,
`simulator/*`). They still work; the HTTP capability gate is a no-op
when no declaration exists. Adding them is mechanical and doesn't affect
iOS behavior.
- **`list-simulators` / `boot-simulator` rename to `list-devices` /
`boot-device`** — that's a behavior change, paired with the Android
impls.
- **Platform-aware `services()`** for tools whose iOS service has no
Android equivalent (e.g. `nativeDevtools` in `launch-app` /
`restart-app`). Needed when filling in Android impls.
|
||
|
|
e226fe5404 |
test: speed up long unit tests (#153)
## Summary
These tests were taking up too long.
| File | Before | After |
|------|-------:|------:|
| `fetch-timeout.test.ts` hanging-POST test | 6281ms | ~600ms |
| `workspace-reader.test.ts` (full file) | ~4500ms | ~140ms |
| `gather-workspace-data.test.ts > valid snapshot` | ~590ms | ~3ms |
**Package totals:** `packages/mcp` 6.44s → ~830ms,
`packages/tool-server` 5.29s → ~790ms.
### What was slow and why
- `fetchWithReconnect` is now exported from `mcp-server.ts` so the test
uses the real function. The config accepts an `expBackoffBase` and a
`fetchTimeoutMs` so the hanging-POST test shrinks the backoff base to
`5ms` and the per-attempt timeout to `100ms` (~575ms total)
- every `readWorkspaceSnapshot` call spawned 8 real subprocesses
(`node`, `npm`, `yarn`, `pnpm`, `bun`, `pod`, `eas`, `expo`) via
`runVersionCommand`. Added `vi.mock("node:child_process", ...)` that
stubs `execFile`.
|
||
|
|
de2d000300 |
feat(tool-server): prefix stdout/stderr lines with ISO timestamp (#155)
Adds timestamp to every `tool-server.log` line. `mcp-calls.log` already has timestamps, so no changes. The tz is UTC+0 because that's what we already use for the `mcp-calls.log` Works: <img width="1074" height="599" alt="image" src="https://github.com/user-attachments/assets/db7094f3-8957-4f37-8643-9b2e7c449a1e" /> --------- Co-authored-by: pFornagiel <pawel.fornagiel2@gmail.com> |
||
|
|
26da07d371 |
feat: add MCP config adapter for opencode (#152)
This PR adds MCP config adapter for OpenCode At one point OpenCode worked presumably because it read `.claude` or some other config (i've had like 13 of them on my machine). That doesn't seem to be the case anymore. before|after ---|--- <img width="508" alt="image" src="https://github.com/user-attachments/assets/615d07f6-aa92-49a4-b159-92d6b292d2c7" />|<img width="510" alt="image" src="https://github.com/user-attachments/assets/4dabcd1a-d920-4979-9e9e-31e45b12d1c2" /> <img width="400" alt="image" src="https://github.com/user-attachments/assets/8853ddf4-0001-4874-81b7-d2a88d3b6956" />|<img width="400" alt="image" src="https://github.com/user-attachments/assets/048928bf-7c59-4ac0-8483-04cc3b3edcda" /> --------- Co-authored-by: Filip131311 <contact@filipkaminski.com> |
||
|
|
6355620c4d |
refactor(mcp): split mcp package into argent, argent-cli, argent-installer, argent-mcp, argent-tools-client (#162)
## Summary - Splits the monolithic `packages/mcp` package into five focused packages: - `argent` — the user-facing CLI entry (`argent` binary, init/uninstall/update flows, bundled-paths, scripts). - `argent-cli` — CLI runner internals (flag-parser, run, server, tools). - `argent-installer` — `init`/`update`/`uninstall` flows, MCP config adapters, skills installer. - `argent-mcp` — MCP server itself (mcp-server, tool-mapping, auto-screenshot, content adapters). - `argent-tools-client` — HTTP client + launcher for the tool-server. - Adds Gemini agent definitions, VSCode MCP config, and per-package `tsconfig.json` / `vitest.config.ts`. - Updates root `tsconfig.json`, `scripts/dev.cjs`, CI workflows, and `package.json` to wire the new packages together. ## Test plan - [ ] `npm install` resolves the new workspaces cleanly - [ ] `npm run build` succeeds across all packages - [ ] `npm test` passes (renamed test files keep their assertions) - [ ] `npx argent init` still produces the expected MCP config in a sample project - [ ] `npx argent update` / `argent uninstall` exercise the installer package - [ ] CI workflows (`publish.yml`, `unit-tests.yml`) run on this branch |
||
|
|
99cd3870ba |
feat: Add CLI capability to argent (#161)
This PR adds a thin cli layer to argent by introducing 3 new commands: 1) `server` allows for checking the tools-server status 2) `tools` allows for tools discovery 3) `run` allows for running tools straight from the shell This is a baseline functionality needed for testing hypothesis that CLI with skills is more efficient then mcp and allow should allow easier integration with potential cloud infrastructure |
||
|
|
037c474956 |
Feat: Baseline web UI for the simulator (#158)
This is a very base line UI: - integrates into the existing simulator server on tools server - does not expose any endpoints via mcp |
||
|
|
acc5798185 |
feat(mcp): support progressive tool loading via _meta hints (#120)
* feat(mcp): support progressive tool loading via _meta hints Add optional alwaysLoad and searchHint fields to ToolDefinition, forward them through GET /tools, and emit them as _meta["anthropic/alwaysLoad"] / _meta["anthropic/searchHint"] in the MCP ListTools response. Claude Code's ToolSearch defers MCP tools by default; tools marked alwaysLoad stay in context on every turn, and searchHint feeds the BM25 ranker so deferred tools surface for relevant queries. Mark the core discovery + interaction tools as alwaysLoad so Claude never has to round-trip through ToolSearch to tap the screen: describe, debugger-component-tree, screenshot, gesture-tap, gesture-swipe, launch-app, run-sequence. Everything else defers. * feat(mcp): mark restart-app as alwaysLoad Tools that depend on controlling app startup (relaunch for clean state, refreshing native-devtools injection) need restart-app to be immediately callable rather than routed through a search round-trip. |
||
|
|
6c97269306 |
feat: add Get Started tip to end of argent init flow (#154)
* feat: add Get Started tip to end of argent init flow New users finished init and asked "what now?" — surface the "ask your assistant What can Argent do?" tip from the README as a final note before the outro so the next step is obvious. * style: make Get Started tip more prominent in init flow Bold colored title, bold cyan call-to-action indented on its own line, and a dimmed tagline below — so the "what now?" answer visually pops instead of reading as another summary line. * style: fold outro into Get Started note and make it more prominent * style: render Get Started as outro so the end of init flow is clear * style: restore boxed banner around Get Started tip, keep outro terminator * human fix |
||
|
|
927620cb82 |
fix: actually update skills on argent update and use semver-aware comparison (#151)
* fix: update skills during argent update and use semver-aware comparison
- Replace `installed !== latest` with `isNewerVersion` so a locally
installed prerelease (e.g. 0.5.4-beta on top of published 0.5.3) no
longer triggers a downgrade prompt during `argent update`.
- Replace the "Check if skills have updates?" prompt (which only ran
`npx skills check`) with an actual update that runs `npx skills update
-y`. Under --yes/-y the step runs unattended so CI and scripted
upgrades actually pick up new skill versions.
* Update skills unconditionally, scope to argent-owned skills, cover both scopes
- Drop the skills prompt entirely — the rest of the update flow already
refreshes MCP configs, allowlists, rules, and agents without asking, so
the skills step is now consistent. Stale skills silently break argent
behavior after a version bump, so the safe default is to always resync.
- Scope the update to argent-owned skills by reading bundled skill names
from SKILLS_DIR at runtime (new `listBundledSkills` helper). Third-party
skills the user installed separately are left untouched.
- Run against both -p and -g scopes instead of relying on `-y`'s
auto-detect, which only covers one scope and would miss whichever the
user didn't pick during init.
* Use `skills add` for argent skill resync, only in scopes that track them
The skills CLI's `update` command silently skips every entry with
sourceType="local" (which is how argent ships its bundled skills), so
`npx skills update` never actually re-synced them — the previous commit
was effectively a no-op. Switch to `npx skills add <SKILLS_DIR> --skill
'*' -y`, which hashes the source on disk and reinstalls whatever
changed after `npm i -g @swmansion/argent@new`.
To avoid creating a `skills-lock.json` in an unrelated working
directory, only resync a scope that already tracks at least one
argent-owned skill. Scope detection reads the skills CLI's lock files
directly: `./skills-lock.json` for project scope and
`$XDG_STATE_HOME/skills/.skill-lock.json` or `~/.agents/.skill-lock.json`
for global scope.
* Prune argent skills that are no longer shipped
`skills add` only scans the current SKILLS_DIR, so a skill that argent
ships in v0.5.3 but removes in v0.5.4 stayed in every lock file and in
every agent's skills/ directory — the agent would keep loading it.
After resync, diff the scope's lock against the bundled set and run
`npx skills remove` for each argent-prefixed entry that's no longer
bundled. Ownership detection is prefix-based (every skill argent has
ever shipped starts with `argent-`), which also lets us find orphans
that listBundledSkills() no longer knows about.
Replaces the previous hasSkillsInLock helper with
listArgentSkillsInLock, which returns the actual names so they can be
used for both scope detection and orphan computation.
* Extract argent-skills refresh into a shared module
Pulls the two-scope resync+prune block out of update.ts into
packages/mcp/src/cli/skills.ts, exposed as `refreshArgentSkills` (pure
data) and `formatSkillRefreshSummary` (display). update.ts shrinks
from ~80 lines of inline logic to a two-line call.
Also wires the helper into init.ts's upgrade prompt: when the user
accepts an in-init version bump, we now prune argent skills that the
new version dropped — previously orphans leaked forever because
init.ts never ran a remove, only a single-scope `skills add` in
Step 2.
The install action is deliberately not extracted here — update's and
init's install paths differ on stdio mode, failure handling,
killToolServer prerequisite, and the ARGENT_SKIP_POSTINSTALL env, and
unifying them would force behavior alignment that doesn't obviously
belong together.
* format
* Clean project lock after skills remove
The skills CLI's `skills remove` deletes the canonical skill directory
and per-agent symlinks for both scopes, but only removes the entry
from the GLOBAL lock (`~/.agents/.skill-lock.json`) — the project
`skills-lock.json` is left with a stale entry pointing at a skill
whose files no longer exist. The end-to-end test in
`scripts/test-skills-prune.sh` caught this: canonical dir and symlink
gone, lock still contained the orphan.
Without cleanup, every subsequent `argent update` would keep seeing
the orphan in the lock, try to prune it again, and report "pruned 1"
for something that's already been pruned. Fix it by stripping the
pruned names from the lock file after a successful `skills remove`.
Also commits `scripts/test-skills-prune.sh`, the e2e script that
uncovered this.
* Revert project-lock cleanup workaround
The previous commit worked around a bug in the vercel-labs/skills CLI
where project-scope `skills remove` cleans the canonical skill
directory and per-agent symlinks but leaves the `skills-lock.json`
entry behind. In practice this only matters cosmetically — the agent
never loads the orphan because its files are gone — so maintaining a
workaround in our code base for a bug that belongs upstream is more
churn than it's worth.
Reverts the `removeSkillsFromLockFile` helper and its call site in
`refreshArgentSkills`, plus the two tests that asserted on it.
Updates the e2e script to expect the stale lock entry for project
scope and only fail on missing canonical dir / symlink cleanup.
Global scope still gets the strict check — the skills CLI does clean
the global lock correctly there.
Issue to be filed upstream so they can fix it in the right place.
* Drop scripts/test-skills-prune.sh
The e2e script served its purpose uncovering the skills-CLI
project-lock quirk (now filed upstream as vercel-labs/skills#977) and
isn't needed as a committed artifact.
* Add --from tarball flag to argent update
Mirrors the `--from <path>` flag that already exists in `argent
init`: installs from a local tarball or filesystem path instead of
the registry, then runs the same refresh+prune as a normal update.
Intended for dry-running a locally-packed argent end-to-end before
publishing to npm.
Undocumented by design — it's a dev tool, not a user-facing feature.
Bypasses the registry probe entirely so testing doesn't depend on
what npm currently has as latest.
Extracts `extractFlag` from init.ts into utils.ts so both commands
share a single implementation.
* Revert "Add --from tarball flag to argent update"
This reverts commit
|
||
|
|
9fee899e5f | chore: bump package versions to 0.5.3 for mcp, registry, skills, and tool-server (#149) | ||
|
|
aaa1a21c7c |
fix: only prompt for update when remote version is strictly newer (#150)
Previously the CLI checked `latest !== version`, which triggered an update prompt (and recommended a downgrade) whenever a locally installed prerelease was newer than the published release. Switch to semver-aware comparison so update prompts only appear for genuine upgrades. |
||
|
|
5d7442896c |
feat: Show offline banner during argent init (#113)
* show offline banner during argent init Detect offline state via a DNS lookup of the npm registry at the start of `argent init` and render a red "Offline mode" note listing the steps that require network access (global install/update, `npx skills`). Skip the update-check spinner entirely when offline and default the skills prompt to the manual option so the user is not dropped into a failing npx run. Also pipe stderr on the `npm view` version lookup so its 404 output no longer leaks to the terminal. * soften offline skills wording to account for npx cache * hide offline skills warning when npx skills is already cached * extract offline banner into its own helper * drop redundant manual-option hint from offline bullet * harden isOnline: unref timer, clear on resolve, guard URL parse * test(mcp/cli): mock network probes; harden offline skills install Replace the `isOnline` test that hit real DNS with deterministic mocks for `node:dns.lookup` and `node:child_process.execSync`, and add coverage for `isSkillsCliAvailable` and `getLatestVersion` (trim, stderr pipe regression guard, error propagation). Fixes found during verification: - Offline skills install now passes `--no-install` to npx so it uses the cache that `isSkillsCliAvailable()` already confirmed, instead of hitting the registry for a fresh copy and failing offline. - Strip `--no-install` from the manual-recovery hint so users can retry with network access. - `isSkillsCliAvailable` and `getLatestVersion` now pass execSync timeouts (2s and 10s) so a wedged `npx` or captive portal cannot hang `argent init`. * refactor: replace top-level offline banner with contextual skills warning Instead of showing a large offline banner at the start of init that lists everything that will be skipped, only check online status at the skills step — the one place where offline truly matters. The update check already handles registry failures gracefully via try/catch. When offline and skills CLI is not cached, a single-line warning appears right before the skills prompt, and the default shifts to Manual. * revert: remove unrelated getLatestVersion stdio/timeout change The stdio piping and timeout addition to getLatestVersion belongs in a separate PR — it fixes stderr leakage from npm view, not offline handling. * refactor: build skills args then prepend --no-install when offline Build the base skillsArgs list once, then derive npxArgs by prepending --no-install when using the offline cache. The error recovery hint uses the base list directly — no stripping needed. * cleanup: remove initialValue override and fix stale test comment Drop the initialValue on the skills prompt — it changed the default selection which is unrelated to offline handling. Fix the test file comment that still referenced getLatestVersion after its tests were removed. * chore: unify probe timeout to 8s and fix prettier * add timeout to getLatestVersion and lower probe timeout to 3s getLatestVersion (npm view) hangs indefinitely offline without a timeout, blocking users from reaching the skills step. Use the shared PROBE_TIMEOUT_MS constant, lowered to 3s for snappier offline detection. * skip skills prompt when offline, add timeout to getLatestVersion When offline without cached skills CLI, go straight to manual instead of showing unselectable options. Add PROBE_TIMEOUT_MS to getLatestVersion so the update check doesn't hang indefinitely offline. |
||
|
|
6ab1e7edad |
feat: add MCP config adapter for Hermes Agent (#106)
* feat: add MCP config adapter for Hermes Add hermesAdapter to the MCP config registry so `argent init` can write the mcp_servers.argent entry into ~/.hermes/config.yaml. - Uses the yaml Document API (parseDocument/setIn/toString) to preserve user comments and formatting on round-trip. - remove() cleans up by deleting the argent key from mcp_servers, or the entire mcp_servers block when argent is the only entry. - readYamlDocument throws on malformed YAML instead of silently clobbering; writeYamlDocument uses lineWidth: 0 to avoid re-wrapping long user strings. - Adds symmetric local→global fallback in the init write loop so global-only adapters (Hermes, Windsurf) are not silently skipped when the user picks scope=local. * refactor: drop unused readYaml POJO flavor, unify yaml util names readYaml (POJO round-trip) was added to utils.ts but never called — the hermes adapter uses the Document-returning helpers exclusively. Drop it and rename readYamlDocument/writeYamlDocument to readYaml/writeYaml so the naming matches the readJson/readToml pairs; with only one flavor left, the Document suffix no longer disambiguates anything. * feat(init): flag adapters that only have one config scope For adapters like Hermes and Windsurf that only expose a global config path (or conversely, only a project path), the multiselect menu now shows an italic cyan note next to the adapter name so users understand up front which scope the entry will land in, instead of discovering it after selection. |
||
|
|
066aad2452 | fix: fix problematic CLI install, bump package versions to 0.5.2 for mcp, registry, skills, and tool-server | ||
|
|
d30dcd8220 |
fix: add license field to package.json (#143)
npm displays "NONE" on the package page when the license field is missing from package.json, even if a LICENSE file exists in the repo. |
||
|
|
8c06a60ae8 | chore: bump package versions to 0.5.1 for mcp, native-devtools-ios, registry, skills, and tool-server (#139) | ||
|
|
c9865eff4c |
chore: set npm publish access to public (#141)
Change publishConfig.access from "restricted" to "public" to allow public npm publication of @swmansion/argent. |
||
|
|
3979e2780d |
bump version (#136)
* init * chore: update package versions to 0.4.5 for registry, skills, and tool-server --------- Co-authored-by: pFornagiel <pawel.fornagiel2@gmail.com> |
||
|
|
72b79d31f6 |
docs: screenshot skill caveat and wait mechanism (#138)
* docs: update argent.md to include proper wait mechanism for screenshot calls * fix: grammar |
||
|
|
112336079e |
fix: crash recovery review issues from #135 + other findings (#137)
* fix: address crash recovery review issues from #135
- crashShutdown now exits with code 1 (was 0 via shared shutdown())
- reconnect() failure no longer aborts the retry loop
- health monitor timeout 3s → 10s (avoids false positives on busy server)
- ListToolsRequestSchema lets errors propagate (empty list could be cached)
- event emitter catches async listener rejections
- forceExit timer keeps event loop alive (removed .unref())
- stderr error handler no longer writes to stdout
- SIGINT/SIGTERM handlers fall back to process.exit(0) if shutdown is null
- fetchTools() checks res.ok
* fix: clarify stderr error handler comment
* fix: revert stderr handler — stdout fallback is safe
The launcher uses readline + regex matching on stdout, so extra
non-matching lines are harmlessly discarded. Restore the original
stdout fallback for stderr errors.
* fix: add fetch timeout, MCP error handlers, and idle timer on GET /tools
- fetchWithReconnect: wrap each attempt in an AbortController with a
configurable timeout (ARGENT_FETCH_TIMEOUT_MS, default 30s) so a
hanging tool-server doesn't wedge the MCP parent indefinitely
- MCP parent (mcp-server.ts): install process.stdout/stderr error
handlers and uncaughtException/unhandledRejection listeners so an
EPIPE from a closed Codex transport doesn't crash the process
- GET /tools: touch the idle timer so list_tools polling keeps the
tool-server alive between tool calls
- Add tests for fetch timeout and idle timer on GET /tools
* chore: update showcase GIF asset
* Revert "chore: update showcase GIF asset"
This reverts commit
|
||
|
|
c291fcac28 |
fix: tool server crash recovery for MCP server (#135)
* fix: add tool server crash recovery to MCP server - MCP CLI: log uncaught exceptions/rejections but don't exit when running as MCP server (long-lived stdio process must survive) - Tool server: attempt graceful registry disposal before exiting on fatal errors so child processes (simulator servers) are not orphaned - fetchWithReconnect: retry up to 5 times with exponential backoff (500ms→8s) giving a restarted tool server time to boot - ListToolsRequestSchema: add try/catch, return empty list on failure instead of propagating raw errors - Health monitor: periodic 30s ping detects tool server crashes between requests and preemptively triggers reconnect - Defense-in-depth: catch errors in EventEmitter listeners, handle rejected promises in simulator watcher and update checker, log stdout/stderr errors instead of rethrowing * fix: address PR review feedback - Halve retry backoff (4 retries, 250ms base → ~3.75s total wait) - Log initSimulator failures instead of silently swallowing them |
||
|
|
5bb865ba61 | chore: update argent-private submodule to latest commit 2ff57a8 | ||
|
|
c990e98839 |
fix: use launchctl getenv to avoid simctl 127-byte truncation of DYLD_INSERT_LIBRARIES (#127)
`xcrun simctl getenv` silently truncates return values at 127 bytes. The code was reading with `simctl getenv` but writing with `simctl spawn ... launchctl setenv`, causing each ensureEnv() cycle to read a truncated value, fail to recognize the truncated argent path fragments, preserve them, and append a fresh bootstrap — corrupting the variable and clobbering third-party dylibs like SimCam. Fix: - Read via `simctl spawn <udid> launchctl getenv` (same store, no limit) - Restore fs.existsSync guard to strip truncated/stale entries while keeping third-party dylibs that exist on disk (e.g. SimCam) - Bump mcp version to 0.4.4 |
||
|
|
bef319134e |
fix: add project_root arg to flow-start-recording (#114)
* fix: detect project root via .argent dir instead of git Flow tools previously shelled out to 'git rev-parse --show-toplevel' to locate the project root, which failed outside git repos and pulled flows from the wrong place in nested worktrees. Walk up from cwd looking for an existing .argent/ directory; fall back to cwd so the first flow-start-recording still creates it in place. * fix: skip $HOME when walking up for .argent/ The MCP launcher stores its own state in ~/.argent/ (logs, tool-server pid). Without this guard, running any flow tool from anywhere under $HOME — but outside a real project with its own .argent/ — would stop at the home directory and treat it as the project root, mixing personal MCP state with flow files. * revert: restore getGitRoot in flow-utils The walk-up .argent approach is fundamentally broken because the tool-server is a singleton daemon — its process.cwd() is not the calling project's cwd. Reverting both |