@swmansion/argent 0.14.1-next.6 → 0.14.1-next.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -20
- package/dist/cli-cmds.mjs +16 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -2
- package/dist/tool-server.cjs +143 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,18 +9,32 @@
|
|
|
9
9
|
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-argent-2&n=1)
|
|
10
10
|
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-argent-3&n=1)
|
|
11
11
|
|
|
12
|
-
**[Argent](https://argent.swmansion.com)** is an **agentic toolkit** that gives your AI assistant direct access to iOS Simulators and Android
|
|
12
|
+
**[Argent](https://argent.swmansion.com)** is an **agentic toolkit** that gives your AI assistant direct access to iOS Simulators, Android emulators and physical devices, TVs (Apple TV, Android TV, Fire TV) and Electron/Chromium desktop and web apps. Ask it to tap a button, run a profiler or reproduce an issue manually - all from within your CLI, without switching context.
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
npx @swmansion/argent init
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Supported platforms
|
|
19
|
+
|
|
20
|
+
Argent drives a growing set of targets through a single toolkit, each with the right interaction model - touch, remote or mouse:
|
|
21
|
+
|
|
22
|
+
| Platform | Targets | Interaction |
|
|
23
|
+
| ----------------- | ----------------------------------------------------------------------- | ---------------- |
|
|
24
|
+
| **iOS** | Simulators | Touch / gesture |
|
|
25
|
+
| **Android** | Emulators (AVDs) and physical devices over adb | Touch / gesture |
|
|
26
|
+
| **TV** | Apple TV (tvOS), Android TV / Google TV, Amazon Fire TV (Vega) | D-pad / remote |
|
|
27
|
+
| **Desktop & web** | Electron and Chromium apps (incl. React Native Web / Expo web) over CDP | Mouse / keyboard |
|
|
28
|
+
|
|
18
29
|
## Capabilities
|
|
19
30
|
|
|
20
|
-
- **Autonomous
|
|
21
|
-
- **UI interaction** - Give your agent full control toolkit - tapping, swiping, pinching, typing, gestures
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
31
|
+
- **Autonomous mobile, TV and desktop development** - Allow your agent to work with iOS, Android, TV and Electron/web apps on its own - let it build, open, interact with the app and debug it. Ask for reproducing issues, testing features manually, profiling your app and much more, without ever interrupting your work.
|
|
32
|
+
- **UI interaction** - Give your agent the full control toolkit - tapping, swiping, pinching, typing, gestures and hardware buttons on mobile; the directional remote on TV; mouse, scroll and drag on desktop/web. Let it navigate your app exactly as a user would, without lifting a finger.
|
|
33
|
+
- **Record & replay flows** - Capture a sequence of interactions once and let your agent replay it deterministically, so manual repros and smoke tests become repeatable.
|
|
34
|
+
- **Visual regression** - Diff two screenshots (or a saved baseline against a live capture) with OCR- and font-aware comparison to catch unintended UI changes.
|
|
35
|
+
- **Profiling with batteries included** - Argent can perform and analyze React Native (Hermes), React DevTools and native (Xcode Instruments / Android Perfetto) profiling sessions - down to fiber renders, CPU hotspots and cross-correlated commit-vs-hang reports. Get comprehensive summaries and ask to optimise your app where you find fit.
|
|
36
|
+
- **Debugging and diagnostics** - Let your agent inspect logs, capture network traffic (JS `fetch` and native), evaluate JS in the running app, walk the native UIKit and React component trees, and reproduce failing states - so you can jump straight to the fix.
|
|
37
|
+
- **Desktop & web control** - For Electron and Chromium apps your agent can drive tabs, read and write cookies and storage, walk the DOM and inspect network over the Chrome DevTools Protocol.
|
|
24
38
|
- **React Native out of the box** - Argent works with React Native apps natively, so your agent can build, launch, and iterate on your RN project the same way it would any native app - no extra setup required.
|
|
25
39
|
|
|
26
40
|
> **Tip:** Once installed, ask your assistant _"What can Argent do?"_ - it will walk you through all capabilities available.
|
|
@@ -37,8 +51,10 @@ npx @swmansion/argent init
|
|
|
37
51
|
#### Prerequisites
|
|
38
52
|
|
|
39
53
|
- **Node.js 20.11** or later
|
|
40
|
-
- For iOS: macOS with **Xcode** installed
|
|
41
|
-
- For Android: **Android SDK Platform Tools** (`adb`) on `PATH`, and the **Android Emulator** package if you want to boot AVDs from Argent. Create AVDs via Android Studio or `avdmanager`.
|
|
54
|
+
- For iOS / tvOS: macOS with **Xcode** installed (Apple TV uses tvOS simulators — Xcode downloads the tvOS runtime on demand)
|
|
55
|
+
- For Android / Android TV: **Android SDK Platform Tools** (`adb`) on `PATH`, and the **Android Emulator** package if you want to boot AVDs from Argent. Create AVDs via Android Studio or `avdmanager`.
|
|
56
|
+
- For Fire TV (Vega): the **Vega SDK** (`vega` CLI) on `PATH`
|
|
57
|
+
- For Electron / Chromium: nothing extra to control an already-running app - just launch it with `--remote-debugging-port`, or let Argent spawn your Electron app for you
|
|
42
58
|
|
|
43
59
|
##### Linux host: extra prerequisites for Android emulators
|
|
44
60
|
|
|
@@ -100,18 +116,24 @@ argent init
|
|
|
100
116
|
|
|
101
117
|
## CLI Reference
|
|
102
118
|
|
|
103
|
-
| Command | Description
|
|
104
|
-
| ------------------ |
|
|
105
|
-
| `argent init` | Install globally and configure MCP in the current workspace
|
|
106
|
-
| `argent install` | Alias for `init` command
|
|
107
|
-
| `argent update` | Pull the latest version and refresh workspace configuration
|
|
108
|
-
| `argent remove` | Unregister the MCP server and uninstall the package
|
|
109
|
-
| `argent uninstall` | Alias for `remove` command
|
|
110
|
-
| `argent mcp` | Start MCP server instance, used internally by agent
|
|
111
|
-
| `argent
|
|
112
|
-
| `argent
|
|
113
|
-
| `argent
|
|
114
|
-
| `argent
|
|
119
|
+
| Command | Description |
|
|
120
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| `argent init` | Install globally and configure MCP in the current workspace |
|
|
122
|
+
| `argent install` | Alias for `init` command |
|
|
123
|
+
| `argent update` | Pull the latest version and refresh workspace configuration |
|
|
124
|
+
| `argent remove` | Unregister the MCP server and uninstall the package |
|
|
125
|
+
| `argent uninstall` | Alias for `remove` command |
|
|
126
|
+
| `argent mcp` | Start MCP server instance, used internally by agent |
|
|
127
|
+
| `argent tools` | List tools exposed by the tool-server (`describe <name>` for details) |
|
|
128
|
+
| `argent run` | Invoke a tool by name |
|
|
129
|
+
| `argent server` | Manage the shared tool-server: `start` / `status` / `stop` / `logs` |
|
|
130
|
+
| `argent lens` | Open Argent Lens bound to a fresh coding-agent session — Claude by default, `--agent` selects codex/gemini/opencode/cursor (macOS; behind the `argent-lens` flag — run `argent enable argent-lens` first) |
|
|
131
|
+
| `argent link` | Route client requests to a remote tool-server |
|
|
132
|
+
| `argent unlink` | Remove the persisted remote tool-server link |
|
|
133
|
+
| `argent enable` | Enable a predefined feature flag (`--scope project` for project-local) |
|
|
134
|
+
| `argent disable` | Disable a feature flag (`--scope project` for project-local) |
|
|
135
|
+
| `argent flags` | List available feature flags and their state |
|
|
136
|
+
| `argent telemetry` | Manage anonymous telemetry: `status` / `enable` / `disable` |
|
|
115
137
|
|
|
116
138
|
## Supported Editors
|
|
117
139
|
|
|
@@ -123,7 +145,7 @@ argent init
|
|
|
123
145
|
| Cursor | `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) |
|
|
124
146
|
| VS Code | `.vscode/mcp.json` |
|
|
125
147
|
| Windsurf | `~/.codeium/windsurf/mcp_config.json` (global) |
|
|
126
|
-
| Zed | `.zed/settings.json`
|
|
148
|
+
| Zed | `.zed/settings.json` (project) or `~/.config/zed/settings.json` (global) |
|
|
127
149
|
| Gemini CLI | `.gemini/settings.json` |
|
|
128
150
|
| Codex CLI | `.codex/config.toml` (project) or `~/.codex/config.toml` (global) |
|
|
129
151
|
| Hermes | `~/.hermes/config.yaml` (global) |
|
package/dist/cli-cmds.mjs
CHANGED
|
@@ -7289,11 +7289,15 @@ function isScalarType(type) {
|
|
|
7289
7289
|
}
|
|
7290
7290
|
function coerceScalar(raw, type, field) {
|
|
7291
7291
|
if (type === "number") {
|
|
7292
|
+
if (raw.trim() === "")
|
|
7293
|
+
throw new FlagParseException(`--${field} expected a number, got "${raw}"`);
|
|
7292
7294
|
const n2 = Number(raw);
|
|
7293
7295
|
if (Number.isNaN(n2)) throw new FlagParseException(`--${field} expected a number, got "${raw}"`);
|
|
7294
7296
|
return n2;
|
|
7295
7297
|
}
|
|
7296
7298
|
if (type === "integer") {
|
|
7299
|
+
if (raw.trim() === "")
|
|
7300
|
+
throw new FlagParseException(`--${field} expected an integer, got "${raw}"`);
|
|
7297
7301
|
const n2 = Number(raw);
|
|
7298
7302
|
if (!Number.isInteger(n2))
|
|
7299
7303
|
throw new FlagParseException(`--${field} expected an integer, got "${raw}"`);
|
|
@@ -7322,6 +7326,7 @@ function parseFlags(argv, schema) {
|
|
|
7322
7326
|
let helpRequested = false;
|
|
7323
7327
|
let rawArgs = null;
|
|
7324
7328
|
const seenArrayFields = /* @__PURE__ */ new Set();
|
|
7329
|
+
const jsonFields = /* @__PURE__ */ new Set();
|
|
7325
7330
|
function takeNext(i2, flag) {
|
|
7326
7331
|
if (i2 + 1 >= argv.length) {
|
|
7327
7332
|
throw new FlagParseException(`--${flag} requires a value`);
|
|
@@ -7360,7 +7365,13 @@ function parseFlags(argv, schema) {
|
|
|
7360
7365
|
if (flag.endsWith("-json")) {
|
|
7361
7366
|
const fieldName = flag.slice(0, -"-json".length);
|
|
7362
7367
|
const { value: value2, nextIndex: nextIndex2 } = inlineValue !== void 0 ? { value: inlineValue, nextIndex: i2 } : takeNext(i2, flag);
|
|
7368
|
+
if (seenArrayFields.has(fieldName)) {
|
|
7369
|
+
throw new FlagParseException(
|
|
7370
|
+
`--${fieldName} and --${flag} cannot be mixed for the same field; pass it entirely as --${flag} '<json>' or --args '<json>'`
|
|
7371
|
+
);
|
|
7372
|
+
}
|
|
7363
7373
|
args[fieldName] = parseJsonOrThrow(value2, `--${flag}`);
|
|
7374
|
+
jsonFields.add(fieldName);
|
|
7364
7375
|
i2 = nextIndex2;
|
|
7365
7376
|
continue;
|
|
7366
7377
|
}
|
|
@@ -7392,6 +7403,11 @@ function parseFlags(argv, schema) {
|
|
|
7392
7403
|
);
|
|
7393
7404
|
}
|
|
7394
7405
|
const { value: value2, nextIndex: nextIndex2 } = inlineValue !== void 0 ? { value: inlineValue, nextIndex: i2 } : takeNext(i2, flag);
|
|
7406
|
+
if (jsonFields.has(flag)) {
|
|
7407
|
+
throw new FlagParseException(
|
|
7408
|
+
`--${flag} and --${flag}-json cannot be mixed for the same field; pass it entirely as --${flag}-json '<json>' or --args '<json>'`
|
|
7409
|
+
);
|
|
7410
|
+
}
|
|
7395
7411
|
const coerced = coerceScalar(value2, itemType, flag);
|
|
7396
7412
|
if (!seenArrayFields.has(flag)) {
|
|
7397
7413
|
args[flag] = [coerced];
|
package/dist/cli.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* argent run <tool> [flags] Invoke a tool by name
|
|
20
20
|
* argent server start [flags] Spawn a long-lived tool-server (foreground by default)
|
|
21
21
|
* argent server status|stop|logs Manage the shared tool-server
|
|
22
|
-
* argent lens Open Argent Lens bound to a fresh
|
|
22
|
+
* argent lens Open Argent Lens bound to a fresh coding-agent session (macOS)
|
|
23
23
|
* argent link [flags] Route client requests to a remote tool-server
|
|
24
24
|
* argent unlink Remove the persisted remote link
|
|
25
25
|
* argent enable <flag> Enable a feature flag (global by default)
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* argent run <tool> [flags] Invoke a tool by name
|
|
20
20
|
* argent server start [flags] Spawn a long-lived tool-server (foreground by default)
|
|
21
21
|
* argent server status|stop|logs Manage the shared tool-server
|
|
22
|
-
* argent lens Open Argent Lens bound to a fresh
|
|
22
|
+
* argent lens Open Argent Lens bound to a fresh coding-agent session (macOS)
|
|
23
23
|
* argent link [flags] Route client requests to a remote tool-server
|
|
24
24
|
* argent unlink Remove the persisted remote link
|
|
25
25
|
* argent enable <flag> Enable a feature flag (global by default)
|
|
@@ -63,7 +63,7 @@ Commands:
|
|
|
63
63
|
tools List tools exposed by the tool-server
|
|
64
64
|
run Invoke a tool by name (use \`argent run <tool> --help\` for flags)
|
|
65
65
|
server Manage the shared tool-server (start / status / stop / logs)
|
|
66
|
-
lens Open Argent Lens bound to a fresh
|
|
66
|
+
lens Open Argent Lens bound to a fresh coding-agent session (macOS)
|
|
67
67
|
link Route client requests to a remote tool-server
|
|
68
68
|
unlink Remove the persisted remote tool-server link
|
|
69
69
|
enable Enable a feature flag (global by default, --scope project for project)
|
package/dist/tool-server.cjs
CHANGED
|
@@ -111642,6 +111642,9 @@ Booted/ready devices are listed first. Platforms whose CLI is unavailable are si
|
|
|
111642
111642
|
// ../tool-server/src/utils/variant-proposals.ts
|
|
111643
111643
|
init_src();
|
|
111644
111644
|
var MAX_COMMENT_LENGTH = 2e3;
|
|
111645
|
+
var MAX_PENDING_OUTCOMES = 32;
|
|
111646
|
+
var MAX_MATCH_VALUE_LENGTH = 200;
|
|
111647
|
+
var MAX_ANNOTATIONS = 200;
|
|
111645
111648
|
function slug(s) {
|
|
111646
111649
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
111647
111650
|
}
|
|
@@ -111695,6 +111698,18 @@ var VariantProposalStore = class {
|
|
|
111695
111698
|
waitersList = [];
|
|
111696
111699
|
/** Frozen result of the current round once the user submits. */
|
|
111697
111700
|
lastOutcome = null;
|
|
111701
|
+
/**
|
|
111702
|
+
* Completed outcomes that finished with no `await_user_selection` parked to
|
|
111703
|
+
* receive them directly (submitSelection's "no waiter" branch), queued in
|
|
111704
|
+
* completion order. `autoRollIfCompleted` rolls into a fresh round on the
|
|
111705
|
+
* very next `propose_variant` regardless of whether the outcome was ever
|
|
111706
|
+
* retrieved — so this queue, unlike `lastOutcome`/`completed`/`consumed`
|
|
111707
|
+
* (which describe only the CURRENT round), survives `reset()` and is
|
|
111708
|
+
* drained first by `awaitSelection`, oldest first. Without it, a human's
|
|
111709
|
+
* already-submitted selection is destroyed the moment the next
|
|
111710
|
+
* `propose_variant` call rolls the round out from under it.
|
|
111711
|
+
*/
|
|
111712
|
+
pendingOutcomes = [];
|
|
111698
111713
|
/** Begin a fresh round, discarding the previous one's proposals/selections. */
|
|
111699
111714
|
reset() {
|
|
111700
111715
|
const superseded = this.waitersList.filter((w) => !w.settled);
|
|
@@ -111750,11 +111765,11 @@ var VariantProposalStore = class {
|
|
|
111750
111765
|
});
|
|
111751
111766
|
}
|
|
111752
111767
|
}
|
|
111753
|
-
|
|
111754
|
-
if (this.completed
|
|
111768
|
+
autoRollIfCompleted() {
|
|
111769
|
+
if (this.completed) this.reset();
|
|
111755
111770
|
}
|
|
111756
111771
|
proposeVariant(input) {
|
|
111757
|
-
this.
|
|
111772
|
+
this.autoRollIfCompleted();
|
|
111758
111773
|
if (input.udid && input.udid.trim()) this.device = input.udid.trim();
|
|
111759
111774
|
const match = input.match ?? { by: "text", value: input.element };
|
|
111760
111775
|
const key = `${match.by}:${match.value.trim().toLowerCase()}`;
|
|
@@ -111817,7 +111832,10 @@ var VariantProposalStore = class {
|
|
|
111817
111832
|
*/
|
|
111818
111833
|
setCliSession(active, agents = []) {
|
|
111819
111834
|
const transitioned = this.cliSession !== active;
|
|
111820
|
-
if (transitioned
|
|
111835
|
+
if (transitioned) {
|
|
111836
|
+
if (this.completed || this.proposals.length > 0) this.reset();
|
|
111837
|
+
this.pendingOutcomes = [];
|
|
111838
|
+
}
|
|
111821
111839
|
this.cliSession = active;
|
|
111822
111840
|
this.lensAgents = active ? agents.map((a) => ({ ...a })) : [];
|
|
111823
111841
|
this.lensAgentChoice = null;
|
|
@@ -111882,30 +111900,51 @@ var VariantProposalStore = class {
|
|
|
111882
111900
|
}
|
|
111883
111901
|
/** Called by the preview UI when the human presses "Complete selection". */
|
|
111884
111902
|
submitSelection(input) {
|
|
111885
|
-
const cleanAnnotations = (input.annotations ?? []).filter((a) => a && typeof a.comment === "string" && a.comment.trim()).map((a) => ({
|
|
111903
|
+
const cleanAnnotations = (input.annotations ?? []).filter((a) => a && typeof a.comment === "string" && a.comment.trim()).slice(0, MAX_ANNOTATIONS).map((a) => ({
|
|
111886
111904
|
target: String(a.target ?? "").slice(0, 200) || "(element)",
|
|
111887
|
-
|
|
111905
|
+
// Cap the matcher value too — it is caller-supplied and, unlike the
|
|
111906
|
+
// comment, was previously ingested uncapped.
|
|
111907
|
+
match: {
|
|
111908
|
+
by: a.match?.by ?? "text",
|
|
111909
|
+
value: String(a.match?.value ?? "").slice(0, MAX_MATCH_VALUE_LENGTH)
|
|
111910
|
+
},
|
|
111888
111911
|
comment: a.comment.trim().slice(0, MAX_COMMENT_LENGTH)
|
|
111889
111912
|
}));
|
|
111890
111913
|
if (this.proposals.length === 0 && cleanAnnotations.length === 0) {
|
|
111891
111914
|
throw new Error("Nothing to submit \u2014 no proposals and no comments.");
|
|
111892
111915
|
}
|
|
111893
|
-
|
|
111894
|
-
|
|
111895
|
-
|
|
111916
|
+
const seenElementIds = /* @__PURE__ */ new Set();
|
|
111917
|
+
this.submitted = input.selections.filter((s) => this.proposals.some((p) => p.id === s.elementId)).filter((s) => {
|
|
111918
|
+
if (seenElementIds.has(s.elementId)) return false;
|
|
111919
|
+
seenElementIds.add(s.elementId);
|
|
111920
|
+
return true;
|
|
111921
|
+
}).map((s) => {
|
|
111922
|
+
const capped = s.comment === void 0 ? s : { ...s, comment: s.comment.slice(0, MAX_COMMENT_LENGTH) };
|
|
111923
|
+
return capped.variantId == null ? capped : { ...capped, variantId: capped.variantId.slice(0, MAX_MATCH_VALUE_LENGTH) };
|
|
111924
|
+
});
|
|
111896
111925
|
this.submittedAnnotations = cleanAnnotations;
|
|
111897
111926
|
this.globalComment = (input.globalComment ?? "").trim().slice(0, MAX_COMMENT_LENGTH);
|
|
111927
|
+
const wasResubmit = this.completed;
|
|
111898
111928
|
this.completed = true;
|
|
111899
|
-
this.consumed = false;
|
|
111929
|
+
if (!wasResubmit) this.consumed = false;
|
|
111900
111930
|
this.lastOutcome = this.buildOutcome();
|
|
111901
111931
|
const round2 = this.round;
|
|
111902
111932
|
const toSettle = this.waitersList.filter((w) => !w.settled && w.round === round2);
|
|
111903
111933
|
this.waitersList = this.waitersList.filter((w) => w.round !== round2 || w.settled);
|
|
111904
|
-
if (toSettle.length > 0)
|
|
111905
|
-
|
|
111906
|
-
|
|
111907
|
-
|
|
111908
|
-
|
|
111934
|
+
if (toSettle.length > 0) {
|
|
111935
|
+
this.consumed = true;
|
|
111936
|
+
for (const w of toSettle) {
|
|
111937
|
+
w.settled = true;
|
|
111938
|
+
w.settle(this.lastOutcome);
|
|
111939
|
+
}
|
|
111940
|
+
} else if (this.cliSession) {
|
|
111941
|
+
this.consumed = true;
|
|
111942
|
+
} else if (!this.consumed) {
|
|
111943
|
+
this.pendingOutcomes = this.pendingOutcomes.filter((o) => o.round !== round2);
|
|
111944
|
+
this.pendingOutcomes.push(this.lastOutcome);
|
|
111945
|
+
if (this.pendingOutcomes.length > MAX_PENDING_OUTCOMES) {
|
|
111946
|
+
this.pendingOutcomes.shift();
|
|
111947
|
+
}
|
|
111909
111948
|
}
|
|
111910
111949
|
this.events.emit("changed");
|
|
111911
111950
|
this.events.emit("selectionSubmitted");
|
|
@@ -111969,16 +112008,23 @@ var VariantProposalStore = class {
|
|
|
111969
112008
|
* round is superseded), so concurrent / re-entrant awaits never strand.
|
|
111970
112009
|
*/
|
|
111971
112010
|
awaitSelection(opts) {
|
|
112011
|
+
if (opts.signal?.aborted) {
|
|
112012
|
+
const err = new Error("await_user_selection aborted (client disconnected)");
|
|
112013
|
+
err.name = "AbortError";
|
|
112014
|
+
return Promise.reject(err);
|
|
112015
|
+
}
|
|
112016
|
+
if (this.pendingOutcomes.length > 0) {
|
|
112017
|
+
const outcome = this.pendingOutcomes.shift();
|
|
112018
|
+
if (outcome.round === this.round) this.consumed = true;
|
|
112019
|
+
const morePending = this.pendingOutcomes.length > 0 || outcome.round !== this.round && this.proposals.length > 0;
|
|
112020
|
+
return Promise.resolve(morePending ? { ...outcome, morePending: true } : outcome);
|
|
112021
|
+
}
|
|
111972
112022
|
if (this.completed && this.consumed) {
|
|
111973
112023
|
return Promise.resolve({
|
|
111974
112024
|
status: "no_proposals",
|
|
111975
112025
|
message: "The previous selection round was already returned. Call propose_variant to stage new variants before awaiting again."
|
|
111976
112026
|
});
|
|
111977
112027
|
}
|
|
111978
|
-
if (this.completed && !this.consumed) {
|
|
111979
|
-
this.consumed = true;
|
|
111980
|
-
return Promise.resolve(this.lastOutcome ?? this.buildOutcome());
|
|
111981
|
-
}
|
|
111982
112028
|
if (this.proposals.length === 0) {
|
|
111983
112029
|
return Promise.resolve({
|
|
111984
112030
|
status: "no_proposals",
|
|
@@ -112027,10 +112073,7 @@ var VariantProposalStore = class {
|
|
|
112027
112073
|
this.waitersList.push(waiter);
|
|
112028
112074
|
this.events.emit("changed");
|
|
112029
112075
|
this.events.emit("awaitParked");
|
|
112030
|
-
if (opts.signal) {
|
|
112031
|
-
if (opts.signal.aborted) return onAbort();
|
|
112032
|
-
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
112033
|
-
}
|
|
112076
|
+
if (opts.signal) opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
112034
112077
|
});
|
|
112035
112078
|
}
|
|
112036
112079
|
};
|
|
@@ -112910,6 +112953,13 @@ function descendantText(parsed, maxChars = 120) {
|
|
|
112910
112953
|
const stack = [parsed];
|
|
112911
112954
|
while (stack.length > 0) {
|
|
112912
112955
|
const x = stack.pop();
|
|
112956
|
+
if (attrIsTrue(x.attrs, "password")) {
|
|
112957
|
+
if (!seen.has("[password]")) {
|
|
112958
|
+
seen.add("[password]");
|
|
112959
|
+
parts.push("[password]");
|
|
112960
|
+
}
|
|
112961
|
+
continue;
|
|
112962
|
+
}
|
|
112913
112963
|
for (const k of ["text", "content-desc"]) {
|
|
112914
112964
|
const v = (x.attrs[k] ?? "").trim();
|
|
112915
112965
|
if (v && !seen.has(v)) {
|
|
@@ -113001,6 +113051,9 @@ function computeNodeOutput(parsed, scrollClip, outputs, opts) {
|
|
|
113001
113051
|
}
|
|
113002
113052
|
const interactive = isInteractive(attrs);
|
|
113003
113053
|
let label = labelOf(attrs);
|
|
113054
|
+
if (attrIsTrue(attrs, "password")) {
|
|
113055
|
+
label = "[password]";
|
|
113056
|
+
}
|
|
113004
113057
|
if (cls.endsWith(".ImageView") && !interactive && !label) {
|
|
113005
113058
|
return keptChildren;
|
|
113006
113059
|
}
|
|
@@ -113015,6 +113068,9 @@ function computeNodeOutput(parsed, scrollClip, outputs, opts) {
|
|
|
113015
113068
|
if (interactive && bounds && keptChildren.length === 1) {
|
|
113016
113069
|
const c = keptChildren[0];
|
|
113017
113070
|
if (c.clickable && c.pixelBounds && rectsEqual(c.pixelBounds, bounds)) {
|
|
113071
|
+
if (!c.label && label) c.label = label;
|
|
113072
|
+
const rid = attrs["resource-id"];
|
|
113073
|
+
if (!c.identifier && rid) c.identifier = rid;
|
|
113018
113074
|
return [c];
|
|
113019
113075
|
}
|
|
113020
113076
|
}
|
|
@@ -113024,9 +113080,6 @@ function computeNodeOutput(parsed, scrollClip, outputs, opts) {
|
|
|
113024
113080
|
(c) => !(c.role === "StaticText" && c.label && lower.includes(c.label.toLowerCase()) && !c.clickable)
|
|
113025
113081
|
);
|
|
113026
113082
|
}
|
|
113027
|
-
if (attrIsTrue(attrs, "password")) {
|
|
113028
|
-
label = "[password]";
|
|
113029
|
-
}
|
|
113030
113083
|
const node = makeUiNode(attrs, deriveUiAutomatorRole(cls), bounds, label, keptChildren);
|
|
113031
113084
|
if (hiddenInScroll > 0) node.scrollHidden = hiddenInScroll;
|
|
113032
113085
|
return [node];
|
|
@@ -113065,7 +113118,7 @@ function finalizeUiNode(n, children, sw, sh) {
|
|
|
113065
113118
|
};
|
|
113066
113119
|
} else {
|
|
113067
113120
|
if (children.length === 0) return null;
|
|
113068
|
-
if (children.length === 1) return children[0];
|
|
113121
|
+
if (children.length === 1 && !n.label && !n.identifier) return children[0];
|
|
113069
113122
|
const x1 = Math.min(...children.map((c) => c.frame.x));
|
|
113070
113123
|
const y1 = Math.min(...children.map((c) => c.frame.y));
|
|
113071
113124
|
const x2 = Math.max(...children.map((c) => c.frame.x + c.frame.width));
|
|
@@ -115430,7 +115483,7 @@ var LogFileWriter = class {
|
|
|
115430
115483
|
const sourceFile = sourceUrl ? cleanSourceUrl(sourceUrl) ?? void 0 : void 0;
|
|
115431
115484
|
const source = sourceFile !== void 0 && sourceLine !== void 0 ? `${sourceFile}:${sourceLine}` : "-";
|
|
115432
115485
|
const flatMessage = entry.message.replace(/\n/g, " ");
|
|
115433
|
-
const levelDisplay = LEVEL_DISPLAY[entry.level] ?? entry.level.toUpperCase().padEnd(5)
|
|
115486
|
+
const levelDisplay = LEVEL_DISPLAY[entry.level] ?? entry.level.toUpperCase().padEnd(5);
|
|
115434
115487
|
const line = `[L:${entry.id}] ${entry.timestamp} ${levelDisplay} ${source} | ${flatMessage}
|
|
115435
115488
|
`;
|
|
115436
115489
|
if (this.ready && this.fd !== null) {
|
|
@@ -115543,6 +115596,13 @@ function parseFlatLine(line) {
|
|
|
115543
115596
|
};
|
|
115544
115597
|
}
|
|
115545
115598
|
|
|
115599
|
+
// ../tool-server/src/utils/debugger/console-timestamp.ts
|
|
115600
|
+
var MAX_TIMESTAMP_MS = 864e13;
|
|
115601
|
+
function consoleTimestampToIso(rawTimestampMs) {
|
|
115602
|
+
const usable = Number.isFinite(rawTimestampMs) && Math.abs(rawTimestampMs) <= MAX_TIMESTAMP_MS;
|
|
115603
|
+
return new Date(usable ? rawTimestampMs : Date.now()).toISOString();
|
|
115604
|
+
}
|
|
115605
|
+
|
|
115546
115606
|
// ../tool-server/src/blueprints/chromium-js-runtime-debugger.ts
|
|
115547
115607
|
var CHROMIUM_JS_RUNTIME_DEBUGGER_NAMESPACE = "ChromiumJsRuntimeDebugger";
|
|
115548
115608
|
function chromiumJsRuntimeDebuggerRef(device) {
|
|
@@ -115669,7 +115729,7 @@ var chromiumJsRuntimeDebuggerBlueprint = {
|
|
|
115669
115729
|
};
|
|
115670
115730
|
logWriter.write({
|
|
115671
115731
|
id: entry.id,
|
|
115672
|
-
timestamp:
|
|
115732
|
+
timestamp: consoleTimestampToIso(ts),
|
|
115673
115733
|
level: entry.level,
|
|
115674
115734
|
message: entry.message,
|
|
115675
115735
|
stackTrace: entry.stackTrace
|
|
@@ -117132,7 +117192,7 @@ var jsRuntimeDebuggerBlueprint = {
|
|
|
117132
117192
|
};
|
|
117133
117193
|
logWriter.write({
|
|
117134
117194
|
id: entry.id,
|
|
117135
|
-
timestamp:
|
|
117195
|
+
timestamp: consoleTimestampToIso(entry.timestamp),
|
|
117136
117196
|
level: entry.level,
|
|
117137
117197
|
message: entry.message,
|
|
117138
117198
|
stackTrace: entry.stackTrace
|
|
@@ -122513,8 +122573,21 @@ function makeComponentTreeScript(opts) {
|
|
|
122513
122573
|
return `(async function() {
|
|
122514
122574
|
var REQUEST_ID = ${requestId};
|
|
122515
122575
|
var TRACK_SKIPPED = ${trackSkipped};
|
|
122576
|
+
// Deliver errors through the SAME binding channel as the success path. The
|
|
122577
|
+
// consumer (evaluateWithBinding) runs Runtime.evaluate WITHOUT returnByValue/
|
|
122578
|
+
// awaitPromise, so the IIFE's return value is discarded \u2014 a 'return' here never
|
|
122579
|
+
// settles the promise and the tool hangs until the 15s binding timeout. Route
|
|
122580
|
+
// every error through __argent_callback with result = JSON.stringify({ error }),
|
|
122581
|
+
// matching the shape the tool parses (response.result \u2192 JSON.parse \u2192 .error).
|
|
122582
|
+
function __argent_fail(msg) {
|
|
122583
|
+
__argent_callback(JSON.stringify({ requestId: REQUEST_ID, result: JSON.stringify({ error: msg }) }));
|
|
122584
|
+
}
|
|
122585
|
+
// Route any UNEXPECTED throw (outside the named guards below) through the same
|
|
122586
|
+
// binding, so a crash still settles immediately instead of hanging to the 15s
|
|
122587
|
+
// binding timeout. Mirrors inspect-at-point.ts's top-level try/catch.
|
|
122588
|
+
try {
|
|
122516
122589
|
var hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
122517
|
-
if (!hook)
|
|
122590
|
+
if (!hook) { __argent_fail('No DevTools hook'); return; }
|
|
122518
122591
|
// Collect fiber roots across ALL renderers. A single React renderer id is not
|
|
122519
122592
|
// safe to assume: secondary reconcilers (e.g. react-native-skia) frequently
|
|
122520
122593
|
// register first and own renderer id 1, whose roots contain only that library's
|
|
@@ -122532,7 +122605,7 @@ function makeComponentTreeScript(opts) {
|
|
|
122532
122605
|
var _legacy = hook.getFiberRoots(1);
|
|
122533
122606
|
if (_legacy) _legacy.forEach(function(_rt) { _allRoots.push(_rt); });
|
|
122534
122607
|
}
|
|
122535
|
-
if (_allRoots.length === 0)
|
|
122608
|
+
if (_allRoots.length === 0) { __argent_fail('No fiber roots'); return; }
|
|
122536
122609
|
// Pick the mounted root with the largest fiber subtree \u2014 i.e. the real app UI.
|
|
122537
122610
|
var root = _allRoots[0];
|
|
122538
122611
|
var _bestSize = -1;
|
|
@@ -122562,7 +122635,7 @@ function makeComponentTreeScript(opts) {
|
|
|
122562
122635
|
try { var m = __r(i); if (m && m.UIManager) { UIManagerMod = m.UIManager; break; } } catch(e) {}
|
|
122563
122636
|
}
|
|
122564
122637
|
}
|
|
122565
|
-
if (!UIManagerMod)
|
|
122638
|
+
if (!UIManagerMod) { __argent_fail('Could not find UIManager'); return; }
|
|
122566
122639
|
}
|
|
122567
122640
|
|
|
122568
122641
|
var SKIP = new Set([
|
|
@@ -122910,6 +122983,9 @@ function makeComponentTreeScript(opts) {
|
|
|
122910
122983
|
result.skippedCounts = skippedCounts;
|
|
122911
122984
|
}
|
|
122912
122985
|
__argent_callback(JSON.stringify({ requestId: REQUEST_ID, result: JSON.stringify(result) }));
|
|
122986
|
+
} catch (e) {
|
|
122987
|
+
__argent_fail('Component-tree script crashed: ' + (e && e.message ? e.message : String(e)));
|
|
122988
|
+
}
|
|
122913
122989
|
})()`;
|
|
122914
122990
|
}
|
|
122915
122991
|
|
|
@@ -131110,7 +131186,7 @@ function enrich(input) {
|
|
|
131110
131186
|
}
|
|
131111
131187
|
|
|
131112
131188
|
// ../tool-server/src/utils/react-profiler/pipeline/03-tag.ts
|
|
131113
|
-
var ANIMATED_PATTERN = /(Animated|Animation|Transition|Motion)
|
|
131189
|
+
var ANIMATED_PATTERN = /(?<![A-Z0-9])(Animated|Animation|Transition|Motion)(?=[A-Z0-9_(.]|$)/;
|
|
131114
131190
|
var RECYCLER_CHILD_PATTERN = /(ListItem|CellItem|Cell|Row|Item)$/i;
|
|
131115
131191
|
var RECYCLER_PARENT_PATTERN = /^(FlatList|SectionList|VirtualizedList|FlashList|RecyclerListView)/i;
|
|
131116
131192
|
function tag(input) {
|
|
@@ -135202,8 +135278,9 @@ async function renderHangStacksAndroid(opts, target) {
|
|
|
135202
135278
|
return `_Invalid hang_index ${opts.hangIndex}. There are ${hangRows.length} hangs (0-indexed)._`;
|
|
135203
135279
|
}
|
|
135204
135280
|
const hang = hangRows[opts.hangIndex];
|
|
135205
|
-
const startNs = hang.ts_ns;
|
|
135206
|
-
const
|
|
135281
|
+
const startNs = Number(hang.ts_ns);
|
|
135282
|
+
const durNs = Number(hang.dur_ns);
|
|
135283
|
+
const endNs = startNs + durNs;
|
|
135207
135284
|
const [stateRows, sampleRows] = await Promise.all([
|
|
135208
135285
|
runTpQuery({
|
|
135209
135286
|
tracePath: opts.tracePath,
|
|
@@ -135224,18 +135301,22 @@ async function renderHangStacksAndroid(opts, target) {
|
|
|
135224
135301
|
}
|
|
135225
135302
|
}).catch(() => [])
|
|
135226
135303
|
]);
|
|
135227
|
-
const durationMs = Math.round(
|
|
135304
|
+
const durationMs = Math.round(durNs / 1e6);
|
|
135305
|
+
const stateBreakdown = stateRows.map((r) => ({
|
|
135306
|
+
state: r.state,
|
|
135307
|
+
blockedFunction: r.blocked_function,
|
|
135308
|
+
durationMs: Math.round(Number(r.total_dur_ns) / 1e6)
|
|
135309
|
+
}));
|
|
135228
135310
|
const lines = [
|
|
135229
135311
|
`## Hang #${opts.hangIndex} \u2014 ${hang.kind} (${durationMs}ms)` + (hang.reason ? ` \u2014 reason: \`${hang.reason}\`` : ""),
|
|
135230
135312
|
""
|
|
135231
135313
|
];
|
|
135232
|
-
if (
|
|
135314
|
+
if (stateBreakdown.length > 0) {
|
|
135233
135315
|
lines.push("### Main-thread State Breakdown", "");
|
|
135234
135316
|
lines.push("| State | Blocked on | Duration |", "|---|---|---|");
|
|
135235
|
-
for (const
|
|
135236
|
-
const ms = Math.round(row.total_dur_ns / 1e6);
|
|
135317
|
+
for (const entry of stateBreakdown) {
|
|
135237
135318
|
lines.push(
|
|
135238
|
-
`| ${
|
|
135319
|
+
`| ${entry.state} | ${entry.blockedFunction ? `\`${entry.blockedFunction}\`` : "\u2014"} | ${entry.durationMs}ms |`
|
|
135239
135320
|
);
|
|
135240
135321
|
}
|
|
135241
135322
|
lines.push("");
|
|
@@ -135258,13 +135339,7 @@ async function renderHangStacksAndroid(opts, target) {
|
|
|
135258
135339
|
lines.push("```");
|
|
135259
135340
|
}
|
|
135260
135341
|
} else {
|
|
135261
|
-
const blocking = summarizeHangBlocking(
|
|
135262
|
-
stateRows.map((r) => ({
|
|
135263
|
-
state: r.state,
|
|
135264
|
-
blockedFunction: r.blocked_function,
|
|
135265
|
-
durationMs: Math.round(r.total_dur_ns / 1e6)
|
|
135266
|
-
}))
|
|
135267
|
-
);
|
|
135342
|
+
const blocking = summarizeHangBlocking(stateBreakdown);
|
|
135268
135343
|
lines.push("### Main-thread Samples During Hang", "");
|
|
135269
135344
|
if (blocking && blocking.kind === "blocked") {
|
|
135270
135345
|
lines.push(
|
|
@@ -135274,7 +135349,7 @@ async function renderHangStacksAndroid(opts, target) {
|
|
|
135274
135349
|
lines.push(
|
|
135275
135350
|
`_No usable on-CPU stack samples were captured during this hang, even though the main thread was on-CPU (state \`${blocking.dominantState}\`, executing) for most of the window \u2014 the sampler could not unwind a call stack (commonly stripped or missing frame symbols). This is genuine main-thread CPU work, not a wait; see the state breakdown above._`
|
|
135276
135351
|
);
|
|
135277
|
-
} else if (
|
|
135352
|
+
} else if (stateBreakdown.length > 0) {
|
|
135278
135353
|
lines.push(
|
|
135279
135354
|
`_No on-CPU stack samples were captured during this hang. The main thread spent the window off-CPU or runnable-but-not-scheduled, so there is no CPU call stack to show; see the state breakdown above._`
|
|
135280
135355
|
);
|
|
@@ -135405,8 +135480,12 @@ function cpuRowsToAggregatorRows(rows, traceStartNs) {
|
|
|
135405
135480
|
timestampsNs: [],
|
|
135406
135481
|
callChains: [{ chain: [dominant], count: row.sample_count }],
|
|
135407
135482
|
precomputedBursts: parseBurstWindows(row.burst_windows, traceStartMs),
|
|
135408
|
-
|
|
135409
|
-
|
|
135483
|
+
// first/last_ts_ns are absolute CLOCK_MONOTONIC ns: they exceed 2^53 after
|
|
135484
|
+
// ~104 days of device uptime, so the WASM decoder hands them back as bigint
|
|
135485
|
+
// (see readCell). traceStartNs is already a plain Number, so coerce here to
|
|
135486
|
+
// avoid "Cannot mix BigInt and other types" — values stay integral.
|
|
135487
|
+
firstMs: Math.round((Number(row.first_ts_ns) - traceStartNs) / 1e6),
|
|
135488
|
+
lastMs: Math.round((Number(row.last_ts_ns) - traceStartNs) / 1e6),
|
|
135410
135489
|
sampleCount: row.sample_count
|
|
135411
135490
|
});
|
|
135412
135491
|
}
|
|
@@ -135414,16 +135493,16 @@ function cpuRowsToAggregatorRows(rows, traceStartNs) {
|
|
|
135414
135493
|
}
|
|
135415
135494
|
function hangRowsToBottlenecks(rows, traceStartNs) {
|
|
135416
135495
|
return rows.map((row) => {
|
|
135417
|
-
const
|
|
135418
|
-
const startNs = row.ts_ns - traceStartNs;
|
|
135496
|
+
const durNs = Number(row.dur_ns);
|
|
135497
|
+
const startNs = Number(row.ts_ns) - traceStartNs;
|
|
135419
135498
|
return {
|
|
135420
135499
|
type: "ui_hang",
|
|
135421
135500
|
platform: "android",
|
|
135422
135501
|
hangType: row.kind,
|
|
135423
|
-
durationMs,
|
|
135502
|
+
durationMs: Math.round(durNs / 1e6),
|
|
135424
135503
|
startTimeFormatted: formatTraceTime(startNs),
|
|
135425
135504
|
startNs,
|
|
135426
|
-
endNs: startNs +
|
|
135505
|
+
endNs: startNs + durNs,
|
|
135427
135506
|
suspectedFunctions: [],
|
|
135428
135507
|
appCallChains: [],
|
|
135429
135508
|
severity: classifyAndroidHangSeverity(row),
|
|
@@ -135473,7 +135552,7 @@ function formatTraceTime(ns) {
|
|
|
135473
135552
|
function classifyAndroidHangSeverity(row) {
|
|
135474
135553
|
if (row.kind === "anr") return "RED";
|
|
135475
135554
|
if (row.reason === "App Deadline Missed") return "RED";
|
|
135476
|
-
const durationMs = row.dur_ns / 1e6;
|
|
135555
|
+
const durationMs = Number(row.dur_ns) / 1e6;
|
|
135477
135556
|
if (durationMs > 500) return "RED";
|
|
135478
135557
|
return "YELLOW";
|
|
135479
135558
|
}
|
|
@@ -140825,11 +140904,14 @@ It parks until the user presses "Complete selection" in the Argent Lens window (
|
|
|
140825
140904
|
that opens automatically on the user's screen), then returns their choices and any comments.
|
|
140826
140905
|
|
|
140827
140906
|
Returns one of:
|
|
140828
|
-
\u2022 { status: "completed", selections: [{ element, chosenVariant, comment? }], unselected,
|
|
140829
|
-
annotations: [{ target, match, comment }], globalComment }
|
|
140830
|
-
\u2014 the user
|
|
140831
|
-
\`annotations\` are free-form comments the user pinned to specific on-screen
|
|
140832
|
-
the inspector \u2014 treat each as a change request for that element.
|
|
140907
|
+
\u2022 { status: "completed", round, selections: [{ element, chosenVariant, comment? }], unselected,
|
|
140908
|
+
annotations: [{ target, match, comment }], globalComment, morePending? }
|
|
140909
|
+
\u2014 the user finished this round; apply chosenVariant for each element (skip ones in
|
|
140910
|
+
\`unselected\`). \`annotations\` are free-form comments the user pinned to specific on-screen
|
|
140911
|
+
elements via the inspector \u2014 treat each as a change request for that element.
|
|
140912
|
+
If \`morePending\` is true, another already-decided round or a freshly-staged round is
|
|
140913
|
+
still waiting \u2014 call await_user_selection AGAIN (repeat until you get a completed result
|
|
140914
|
+
without \`morePending\`, a \`pending\`, or \`no_proposals\`); do not stop on this result alone.
|
|
140833
140915
|
\u2022 { status: "pending", message, proposedElements } \u2014 timeoutSeconds elapsed with no submission.
|
|
140834
140916
|
Expected, not an error: the proposals are still live; call await_user_selection AGAIN.
|
|
140835
140917
|
\u2022 { status: "no_proposals" } \u2014 you called this before propose_variant.
|