agent-device 0.8.6 → 0.10.0
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 +106 -22
- package/dist/src/224.js +2 -2
- package/dist/src/bin.js +65 -58
- package/dist/src/client-normalizers.d.ts +1 -2
- package/dist/src/client-shared.d.ts +1 -2
- package/dist/src/client-types.d.ts +0 -19
- package/dist/src/client.d.ts +1 -1
- package/dist/src/core/capabilities.d.ts +1 -1
- package/dist/src/core/click-button.d.ts +20 -0
- package/dist/src/core/dispatch-resolve.d.ts +7 -6
- package/dist/src/core/dispatch.d.ts +1 -0
- package/dist/src/daemon/context.d.ts +1 -0
- package/dist/src/daemon/handlers/interaction-common.d.ts +12 -0
- package/dist/src/daemon/handlers/interaction-fill.d.ts +3 -0
- package/dist/src/daemon/handlers/interaction-flags.d.ts +4 -0
- package/dist/src/daemon/handlers/interaction-get.d.ts +3 -0
- package/dist/src/daemon/handlers/interaction-is.d.ts +3 -0
- package/dist/src/daemon/handlers/interaction-press.d.ts +3 -0
- package/dist/src/daemon/handlers/interaction-scroll.d.ts +3 -0
- package/dist/src/daemon/handlers/interaction-selector.d.ts +27 -0
- package/dist/src/daemon/handlers/interaction-snapshot.d.ts +8 -0
- package/dist/src/daemon/handlers/interaction-targeting.d.ts +28 -0
- package/dist/src/daemon/handlers/interaction.d.ts +5 -12
- package/dist/src/daemon/handlers/session-device-utils.d.ts +1 -0
- package/dist/src/daemon/handlers/session-runtime.d.ts +3 -8
- package/dist/src/daemon/handlers/session.d.ts +8 -0
- package/dist/src/daemon/handlers/snapshot-alert.d.ts +13 -0
- package/dist/src/daemon/handlers/snapshot-capture.d.ts +27 -0
- package/dist/src/daemon/handlers/snapshot-session.d.ts +15 -0
- package/dist/src/daemon/handlers/snapshot-settings.d.ts +24 -0
- package/dist/src/daemon/handlers/snapshot-wait.d.ts +37 -0
- package/dist/src/daemon/handlers/snapshot.d.ts +4 -20
- package/dist/src/daemon/is-predicates.d.ts +2 -1
- package/dist/src/daemon/script-utils.d.ts +14 -2
- package/dist/src/daemon/selectors-build.d.ts +2 -1
- package/dist/src/daemon/selectors-match.d.ts +3 -2
- package/dist/src/daemon/selectors-resolve.d.ts +3 -2
- package/dist/src/daemon/session-open-script.d.ts +7 -0
- package/dist/src/daemon/session-store.d.ts +1 -0
- package/dist/src/daemon/snapshot-processing.d.ts +2 -1
- package/dist/src/daemon/types.d.ts +6 -5
- package/dist/src/daemon.js +35 -34
- package/dist/src/index.d.ts +1 -1
- package/dist/src/platforms/android/devices.d.ts +4 -0
- package/dist/src/platforms/android/sdk.d.ts +2 -0
- package/dist/src/platforms/ios/app-filter.d.ts +2 -0
- package/dist/src/platforms/ios/devices.d.ts +2 -1
- package/dist/src/platforms/ios/macos-apps.d.ts +12 -0
- package/dist/src/platforms/ios/runner-client.d.ts +3 -1
- package/dist/src/platforms/ios/runner-macos-products.d.ts +3 -0
- package/dist/src/platforms/ios/runner-xctestrun-products.d.ts +2 -0
- package/dist/src/platforms/ios/runner-xctestrun.d.ts +20 -2
- package/dist/src/utils/args.d.ts +1 -1
- package/dist/src/utils/cli-config.d.ts +2 -1
- package/dist/src/utils/command-schema.d.ts +7 -3
- package/dist/src/utils/device.d.ts +13 -5
- package/dist/src/utils/remote-config.d.ts +15 -0
- package/dist/src/utils/remote-open.d.ts +9 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +58 -50
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +10 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +35 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +83 -9
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +39 -7
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +2 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +5 -6
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +132 -112
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +4 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +22 -5
- package/package.json +3 -2
- package/skills/agent-device/SKILL.md +28 -9
- package/skills/agent-device/references/macos-desktop.md +89 -0
- package/skills/agent-device/references/snapshot-refs.md +11 -2
package/README.md
CHANGED
|
@@ -8,20 +8,57 @@
|
|
|
8
8
|
|
|
9
9
|
# agent-device
|
|
10
10
|
|
|
11
|
-
CLI
|
|
11
|
+
`agent-device` is a CLI for UI automation on iOS, tvOS, macOS, Android, and AndroidTV. It is designed for agent-driven workflows: inspect the UI, act on it deterministically, and keep that work session-aware and replayable.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
If you know Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser), this project applies the same broad idea to mobile apps and devices.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
<video src="https://github.com/user-attachments/assets/db81d164-c179-4e68-97fa-53f06e467211" controls muted playsinline></video>
|
|
16
|
+
|
|
17
|
+
## Project Goals
|
|
18
|
+
|
|
19
|
+
- Give agents a practical way to understand mobile UI state through structured snapshots.
|
|
20
|
+
- Keep automation flows token-efficient enough for real agent loops.
|
|
21
|
+
- Make common interactions reliable enough for repeated automation runs.
|
|
22
|
+
- Keep automation grounded in sessions, selectors, and replayable flows instead of one-off scripts.
|
|
23
|
+
|
|
24
|
+
## Core Ideas
|
|
25
|
+
|
|
26
|
+
- Sessions: open a target once, interact within that session, then close it cleanly.
|
|
27
|
+
- Snapshots: inspect the current accessibility tree in a compact form and get stable refs for exploration.
|
|
28
|
+
- Refs vs selectors: use refs for discovery, use selectors for durable replay and assertions.
|
|
29
|
+
- Human docs vs agent skills: docs explain the system for people; skills provide compact operating guidance for agents.
|
|
30
|
+
|
|
31
|
+
## Command Flow
|
|
32
|
+
|
|
33
|
+
The canonical loop is:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
agent-device open SampleApp --platform ios
|
|
37
|
+
agent-device snapshot -i
|
|
38
|
+
agent-device press @e3
|
|
39
|
+
agent-device diff snapshot -i
|
|
40
|
+
agent-device fill @e5 "test"
|
|
41
|
+
agent-device close
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
In practice, most work follows the same pattern:
|
|
45
|
+
|
|
46
|
+
1. `open` a target app or URL.
|
|
47
|
+
2. `snapshot -i` to inspect the current screen.
|
|
48
|
+
3. `press`, `fill`, `scroll`, `get`, or `wait` using refs or selectors.
|
|
49
|
+
4. `diff snapshot` or re-snapshot after UI changes.
|
|
50
|
+
5. `close` when the session is finished.
|
|
51
|
+
|
|
52
|
+
## Where To Go Next
|
|
53
|
+
|
|
54
|
+
For people:
|
|
55
|
+
- [Website](https://agent-device.dev/)
|
|
56
|
+
- [Docs](https://incubator.callstack.com/agent-device/docs/introduction)
|
|
57
|
+
|
|
58
|
+
For agents:
|
|
59
|
+
|
|
60
|
+
- [agent-device skill](skills/agent-device/SKILL.md)
|
|
61
|
+
- [dogfood skill](skills/dogfood/SKILL.md)
|
|
25
62
|
|
|
26
63
|
## Install
|
|
27
64
|
|
|
@@ -113,6 +150,7 @@ agent-device <command> [args] [--json]
|
|
|
113
150
|
Create an `agent-device.json` file to set persistent CLI defaults instead of repeating flags.
|
|
114
151
|
|
|
115
152
|
Config file lookup order:
|
|
153
|
+
|
|
116
154
|
- `~/.agent-device/config.json`
|
|
117
155
|
- `./agent-device.json`
|
|
118
156
|
- `AGENT_DEVICE_*` environment variables
|
|
@@ -133,6 +171,7 @@ Example:
|
|
|
133
171
|
```
|
|
134
172
|
|
|
135
173
|
Notes:
|
|
174
|
+
|
|
136
175
|
- Config keys use the existing camelCase flag names, for example `stateDir`, `daemonAuthToken`, `iosSimulatorDeviceSet`, and `androidDeviceAllowlist`.
|
|
137
176
|
- Environment overrides use `AGENT_DEVICE_*` uppercase snake case names, for example `AGENT_DEVICE_SESSION`, `AGENT_DEVICE_DAEMON_BASE_URL`, and `AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET`.
|
|
138
177
|
- Bound-session routing defaults also work through config or env, for example `sessionLock` / `AGENT_DEVICE_SESSION_LOCK`.
|
|
@@ -159,10 +198,12 @@ agent-device trace stop ./trace.log
|
|
|
159
198
|
```
|
|
160
199
|
|
|
161
200
|
Coordinates:
|
|
201
|
+
|
|
162
202
|
- All coordinate-based commands (`press`, `longpress`, `swipe`, `focus`, `fill`) use device coordinates with origin at top-left.
|
|
163
203
|
- X increases to the right, Y increases downward.
|
|
164
204
|
- `press` is the canonical tap command.
|
|
165
205
|
- `click` is an equivalent alias and accepts the same targets (`x y`, `@ref`, selector) and flags.
|
|
206
|
+
- `click --secondary` performs a secondary click on macOS, which is useful for opening context menus before a follow-up `snapshot -i`.
|
|
166
207
|
|
|
167
208
|
Gesture series examples:
|
|
168
209
|
|
|
@@ -176,6 +217,7 @@ agent-device scrollintoview @e42
|
|
|
176
217
|
```
|
|
177
218
|
|
|
178
219
|
## Command Index
|
|
220
|
+
|
|
179
221
|
- `boot`, `open`, `close`, `install`, `reinstall`, `home`, `back`, `app-switcher`
|
|
180
222
|
- `push`
|
|
181
223
|
- `batch`
|
|
@@ -185,7 +227,7 @@ agent-device scrollintoview @e42
|
|
|
185
227
|
- `trigger-app-event <event> [payloadJson]`
|
|
186
228
|
- `trace start`, `trace stop`
|
|
187
229
|
- `logs path`, `logs start`, `logs stop`, `logs clear`, `logs clear --restart`, `logs doctor`, `logs mark` (session app log file for grep; iOS simulator + iOS device + Android)
|
|
188
|
-
- `clipboard read`, `clipboard write <text>` (iOS simulator + Android)
|
|
230
|
+
- `clipboard read`, `clipboard write <text>` (macOS + iOS simulator + Android)
|
|
189
231
|
- `keyboard [status|get|dismiss]` (Android emulator/device)
|
|
190
232
|
- `network dump [limit] [summary|headers|body|all]`, `network log ...` (best-effort HTTP(s) parsing from session app log)
|
|
191
233
|
- `settings wifi|airplane|location on|off`
|
|
@@ -211,6 +253,7 @@ agent-device push com.example.app '{"action":"com.example.app.PUSH","extras":{"t
|
|
|
211
253
|
```
|
|
212
254
|
|
|
213
255
|
Payload notes:
|
|
256
|
+
|
|
214
257
|
- iOS uses `xcrun simctl push <device> <bundle> <payload>` and requires APNs-style JSON object (for example `{"aps":{"alert":"..."}}`).
|
|
215
258
|
- Android uses `adb shell am broadcast` with payload JSON shape:
|
|
216
259
|
`{"action":"<intent-action>","receiver":"<optional component>","extras":{"key":"value","flag":true,"count":3}}`.
|
|
@@ -225,10 +268,12 @@ agent-device trigger-app-event screenshot_taken '{"source":"qa"}'
|
|
|
225
268
|
|
|
226
269
|
- `trigger-app-event` dispatches an app event via deep link and requires an app-side test/debug hook.
|
|
227
270
|
- `trigger-app-event` requires either an active session or explicit device selectors (`--platform`, `--device`, `--udid`, `--serial`).
|
|
271
|
+
- On macOS, use `AGENT_DEVICE_MACOS_APP_EVENT_URL_TEMPLATE` to override the desktop deep-link template.
|
|
228
272
|
- On iOS physical devices, custom-scheme deep links require active app context (open the app in-session first).
|
|
229
273
|
- Configure one of:
|
|
230
274
|
- `AGENT_DEVICE_APP_EVENT_URL_TEMPLATE`
|
|
231
275
|
- `AGENT_DEVICE_IOS_APP_EVENT_URL_TEMPLATE`
|
|
276
|
+
- `AGENT_DEVICE_MACOS_APP_EVENT_URL_TEMPLATE`
|
|
232
277
|
- `AGENT_DEVICE_ANDROID_APP_EVENT_URL_TEMPLATE`
|
|
233
278
|
- Template placeholders: `{event}`, `{payload}`, `{platform}`.
|
|
234
279
|
- Example template: `myapp://agent-device/event?name={event}&payload={payload}`.
|
|
@@ -239,17 +284,20 @@ agent-device trigger-app-event screenshot_taken '{"source":"qa"}'
|
|
|
239
284
|
## iOS Snapshots
|
|
240
285
|
|
|
241
286
|
Notes:
|
|
287
|
+
|
|
242
288
|
- iOS snapshots use XCTest on simulators and physical devices.
|
|
243
289
|
- Scope snapshots with `-s "<label>"` or `-s @ref`.
|
|
244
290
|
- If XCTest returns 0 nodes (e.g., foreground app changed), agent-device fails explicitly.
|
|
245
291
|
- `diff snapshot` uses the same snapshot flags and compares the current capture with the previous session baseline, then updates baseline.
|
|
246
292
|
|
|
247
293
|
Diff snapshots:
|
|
294
|
+
|
|
248
295
|
- Run `diff snapshot` once to initialize baseline for the current session.
|
|
249
296
|
- Run `diff snapshot` again after UI changes to get unified-style output (`-` removed, `+` added, unchanged context).
|
|
250
297
|
- Use `--json` to get `{ mode, baselineInitialized, summary, lines }`.
|
|
251
298
|
|
|
252
299
|
Efficient snapshot usage:
|
|
300
|
+
|
|
253
301
|
- Default to `snapshot -i` for iterative agent loops.
|
|
254
302
|
- Add `-s "<label>"` (or `-s @ref`) for screen-local work to reduce payload size.
|
|
255
303
|
- Add `-d <depth>` when lower tree levels are not needed.
|
|
@@ -258,9 +306,10 @@ Efficient snapshot usage:
|
|
|
258
306
|
- Reserve `--raw` for troubleshooting and parser/debug investigations.
|
|
259
307
|
|
|
260
308
|
Flags:
|
|
309
|
+
|
|
261
310
|
- `--version, -V` print version and exit
|
|
262
|
-
- `--platform ios|android|apple` (`apple` aliases the
|
|
263
|
-
- `--target mobile|tv` select device class within platform (requires `--platform`; for example AndroidTV/tvOS)
|
|
311
|
+
- `--platform ios|macos|android|apple` (`apple` aliases the Apple automation backend)
|
|
312
|
+
- `--target mobile|tv|desktop` select device class within platform (requires `--platform`; for example AndroidTV/tvOS/macOS)
|
|
264
313
|
- `--device <name>`
|
|
265
314
|
- `--udid <udid>` (iOS)
|
|
266
315
|
- `--serial <serial>` (Android)
|
|
@@ -292,11 +341,13 @@ Flags:
|
|
|
292
341
|
- `--max-steps <n>` batch: max allowed steps per request
|
|
293
342
|
|
|
294
343
|
Isolation precedence:
|
|
344
|
+
|
|
295
345
|
- Discovery scope (`--ios-simulator-device-set`, `--android-device-allowlist`) is applied before selector matching (`--device`, `--udid`, `--serial`).
|
|
296
346
|
- If a selector points outside the scoped set/allowlist, command resolution fails with `DEVICE_NOT_FOUND` (no host-global fallback).
|
|
297
347
|
- When `--ios-simulator-device-set` is set (or its env equivalent), iOS discovery is simulator-set only (physical iOS devices are not enumerated).
|
|
298
348
|
|
|
299
349
|
TV targets:
|
|
350
|
+
|
|
300
351
|
- Use `--target tv` together with `--platform ios|android|apple`.
|
|
301
352
|
- TV target selection supports both simulator/emulator and connected physical devices (AppleTV + AndroidTV).
|
|
302
353
|
- AndroidTV app launch/app listing use TV launcher discovery (`LEANBACK_LAUNCHER`) and fallback component resolution when needed.
|
|
@@ -304,23 +355,37 @@ TV targets:
|
|
|
304
355
|
- tvOS back/home/app-switcher use Siri Remote semantics in the runner (`menu`, `home`, double-home).
|
|
305
356
|
- tvOS follows iOS simulator-only command semantics for helpers like `pinch`, `settings`, and `push`.
|
|
306
357
|
|
|
358
|
+
Desktop targets:
|
|
359
|
+
|
|
360
|
+
- Use `--platform macos` for the host Mac, or `--platform apple --target desktop` when selecting through the Apple family alias.
|
|
361
|
+
- macOS uses the same runner-driven interaction/snapshot flow as iOS/tvOS for `open`, `appstate`, `snapshot`, `press`, `fill`, `scroll`, `back`, `screenshot`, `record`, and selector-based commands.
|
|
362
|
+
- macOS also supports `clipboard read|write`, `trigger-app-event`, and only `settings appearance light|dark|toggle`.
|
|
363
|
+
- Prefer selector or `@ref`-driven interactions on macOS. Window position is not stable, so raw x/y commands are more fragile than snapshot-derived refs.
|
|
364
|
+
- Mobile-only helpers remain unsupported on macOS: `boot`, `home`, `app-switcher`, `install`, `reinstall`, `install-from-source`, `push`, `logs`, and `network`.
|
|
365
|
+
|
|
307
366
|
Examples:
|
|
367
|
+
|
|
308
368
|
- `agent-device open YouTube --platform android --target tv`
|
|
309
369
|
- `agent-device apps --platform android --target tv`
|
|
310
370
|
- `agent-device open Settings --platform ios --target tv`
|
|
311
371
|
- `agent-device screenshot ./apple-tv.png --platform ios --target tv`
|
|
372
|
+
- `agent-device open TextEdit --platform macos`
|
|
373
|
+
- `agent-device snapshot -i --platform apple --target desktop`
|
|
312
374
|
|
|
313
375
|
Pinch:
|
|
376
|
+
|
|
314
377
|
- `pinch` is supported on iOS simulators (including tvOS simulator targets).
|
|
315
378
|
- On Android, `pinch` currently returns `UNSUPPORTED_OPERATION` in the adb backend.
|
|
316
379
|
|
|
317
380
|
Swipe timing:
|
|
381
|
+
|
|
318
382
|
- `swipe` accepts optional `durationMs` (default `250`, range `16..10000`).
|
|
319
383
|
- Android uses requested swipe duration directly.
|
|
320
384
|
- iOS clamps swipe duration to a safe range (`16..60ms`) to avoid longpress side effects.
|
|
321
385
|
- `scrollintoview` accepts either plain text or a snapshot ref (`@eN`); ref mode uses best-effort geometry-based scrolling without post-scroll verification. Run `snapshot` again before follow-up `@ref` commands.
|
|
322
386
|
|
|
323
387
|
## Skills
|
|
388
|
+
|
|
324
389
|
Install the automation skills listed in [SKILL.md](skills/agent-device/SKILL.md).
|
|
325
390
|
|
|
326
391
|
```bash
|
|
@@ -328,6 +393,7 @@ npx skills add https://github.com/callstackincubator/agent-device --skill agent-
|
|
|
328
393
|
```
|
|
329
394
|
|
|
330
395
|
Sessions:
|
|
396
|
+
|
|
331
397
|
- `open` starts a session. Without args boots/activates the target device/simulator without launching an app.
|
|
332
398
|
- All interaction commands require an open session.
|
|
333
399
|
- If a session is already open, `open <app|url>` switches the active app or opens a deep link URL.
|
|
@@ -340,6 +406,7 @@ Sessions:
|
|
|
340
406
|
- On iOS, `appstate` is session-scoped and requires an active session on the target device.
|
|
341
407
|
|
|
342
408
|
Navigation helpers:
|
|
409
|
+
|
|
343
410
|
- `boot --platform ios|android|apple` ensures the target is ready without launching an app.
|
|
344
411
|
- Use `boot` mainly when starting a new session and `open` fails because no booted simulator/emulator is available.
|
|
345
412
|
- `open [app|url] [url]` already boots/activates the selected target when needed.
|
|
@@ -349,8 +416,8 @@ Navigation helpers:
|
|
|
349
416
|
- `install`/`reinstall` accept package/bundle id style app names and support `~` in paths.
|
|
350
417
|
- `install-from-source` supports `--retain-paths` and `--retention-ms <ms>` when callers need retained materialized artifact paths after the install.
|
|
351
418
|
- When `AGENT_DEVICE_DAEMON_BASE_URL` targets a remote daemon, local `.apk`/`.aab`/`.ipa` files and `.app` bundles are uploaded automatically before `install`/`reinstall`.
|
|
352
|
-
- `
|
|
353
|
-
-
|
|
419
|
+
- `open <app> --remote-config <path> --relaunch` is the canonical remote Metro-backed launch flow for sandbox agents. The remote profile supplies host + Metro settings, `open` prepares Metro locally when needed, derives platform runtime hints, and forwards them inline to the remote daemon before launch.
|
|
420
|
+
- `metro prepare --remote-config <path>` remains available for inspection and debugging. It prints JSON runtime hints to stdout, `--json` wraps them in the standard `{ success, data }` envelope, and `--runtime-file <path>` persists the same payload when callers need an artifact.
|
|
354
421
|
- Remote daemon screenshots and recordings are materialized back to the caller path instead of returning host-local daemon paths.
|
|
355
422
|
- To force a daemon-side path instead of uploading a local file, prefix it with `remote:`, for example `remote:/srv/builds/MyApp.app`.
|
|
356
423
|
- Supported binary formats for `install`/`reinstall`: Android `.apk` and `.aab`, iOS `.app` and `.ipa`.
|
|
@@ -359,6 +426,7 @@ Navigation helpers:
|
|
|
359
426
|
- `.ipa` install extracts `Payload/*.app`; when an IPA contains multiple app bundles, `<app>` is used as a bundle id/name hint to select the target bundle.
|
|
360
427
|
|
|
361
428
|
Deep links:
|
|
429
|
+
|
|
362
430
|
- `open <url>` supports deep links with `scheme://...`.
|
|
363
431
|
- `open <app> <url>` opens a deep link on iOS.
|
|
364
432
|
- Android opens deep links via `VIEW` intent.
|
|
@@ -374,15 +442,18 @@ agent-device open MyApp "myapp://screen/to" --platform ios # open deep link
|
|
|
374
442
|
```
|
|
375
443
|
|
|
376
444
|
Find (semantic):
|
|
445
|
+
|
|
377
446
|
- `find <text> <action> [value]` finds by any text (label/value/identifier) using a scoped snapshot.
|
|
378
447
|
- `find text|label|value|role|id <value> <action> [value]` for specific locators.
|
|
379
448
|
- Actions: `click` (default), `fill`, `type`, `focus`, `get text`, `get attrs`, `wait [timeout]`, `exists`.
|
|
380
449
|
|
|
381
450
|
Assertions:
|
|
451
|
+
|
|
382
452
|
- `is` predicates: `visible`, `hidden`, `exists`, `editable`, `selected`, `text`.
|
|
383
453
|
- `is text` uses exact equality.
|
|
384
454
|
|
|
385
455
|
Performance metrics:
|
|
456
|
+
|
|
386
457
|
- `perf` (or `metrics`) requires an active session and returns a JSON metrics blob.
|
|
387
458
|
- Current metric: `startup` sampled from the elapsed wall-clock time around each session `open` command dispatch (`open-command-roundtrip`), unit `ms`.
|
|
388
459
|
- Startup samples are session-scoped and include sample history from recent `open` actions.
|
|
@@ -402,6 +473,7 @@ agent-device perf --json
|
|
|
402
473
|
- Caveat: startup here is command-to-launch round-trip timing, not true app TTI/first-interactive telemetry.
|
|
403
474
|
|
|
404
475
|
Replay update:
|
|
476
|
+
|
|
405
477
|
- `replay <path>` runs deterministic replay from `.ad` scripts.
|
|
406
478
|
- `replay -u <path>` attempts selector updates on failures and atomically rewrites the same file.
|
|
407
479
|
- Refs are the default/core mechanism for interactive agent flows.
|
|
@@ -429,6 +501,7 @@ click "id=\"auth_continue\" || label=\"Continue\""
|
|
|
429
501
|
```
|
|
430
502
|
|
|
431
503
|
Android fill reliability:
|
|
504
|
+
|
|
432
505
|
- `fill` clears the current value, then enters text.
|
|
433
506
|
- `type` enters text into the focused field without clearing.
|
|
434
507
|
- `fill` now verifies the entered value on Android.
|
|
@@ -443,31 +516,35 @@ Android fill reliability:
|
|
|
443
516
|
- `adb -s <serial> shell ime list -s` (verify current/default IME)
|
|
444
517
|
|
|
445
518
|
Settings helpers:
|
|
519
|
+
|
|
446
520
|
- `settings wifi on|off`
|
|
447
521
|
- `settings airplane on|off`
|
|
448
522
|
- `settings location on|off` (iOS uses per-app permission for the current session app)
|
|
449
|
-
- `settings appearance light|dark|toggle` (iOS simulator appearance + Android night mode)
|
|
523
|
+
- `settings appearance light|dark|toggle` (macOS appearance + iOS simulator appearance + Android night mode)
|
|
450
524
|
- `settings faceid|touchid match|nonmatch|enroll|unenroll` (iOS simulator only)
|
|
451
525
|
- `settings fingerprint match|nonmatch` (Android emulator/device where supported)
|
|
452
526
|
On physical Android devices, fingerprint simulation depends on `cmd fingerprint` support.
|
|
453
527
|
- `settings permission grant|deny|reset <camera|microphone|photos|contacts|notifications> [full|limited]` (session app required)
|
|
454
|
-
Note: iOS supports these only on simulators. iOS wifi/airplane toggles status bar indicators, not actual network state. Airplane off clears status bar overrides.
|
|
528
|
+
Note: iOS supports these only on simulators. On macOS, only `settings appearance` is supported. iOS wifi/airplane toggles status bar indicators, not actual network state. Airplane off clears status bar overrides.
|
|
455
529
|
- iOS permission targets map to `simctl privacy`: `camera`, `microphone`, `photos` (`full` => `photos`, `limited` => `photos-add`), `contacts`, `notifications`.
|
|
456
530
|
- Android permission targets: `camera`, `microphone`, `photos`, `contacts` use `pm grant|revoke` (`reset` maps to `pm revoke`); `notifications` uses `appops set POST_NOTIFICATION allow|deny|default`.
|
|
457
531
|
- `full|limited` mode is valid only for iOS `photos`; other targets reject mode.
|
|
458
532
|
|
|
459
533
|
App state:
|
|
534
|
+
|
|
460
535
|
- `appstate` shows the foreground app/activity (Android).
|
|
461
536
|
- On iOS, `appstate` returns the currently tracked session app (`source: session`) and requires an active session on the selected device.
|
|
462
537
|
- `apps` includes default/system apps by default (use `--user-installed` to filter).
|
|
463
538
|
|
|
464
539
|
Clipboard:
|
|
540
|
+
|
|
465
541
|
- `clipboard read` returns current clipboard text.
|
|
466
542
|
- `clipboard write <text>` sets clipboard text (`clipboard write ""` clears it).
|
|
467
|
-
- Supported on Android emulator/device and iOS simulator.
|
|
543
|
+
- Supported on macOS, Android emulator/device, and iOS simulator.
|
|
468
544
|
- iOS physical devices currently return `UNSUPPORTED_OPERATION` for clipboard commands.
|
|
469
545
|
|
|
470
546
|
Keyboard:
|
|
547
|
+
|
|
471
548
|
- `keyboard status` (or `keyboard get`) reports Android keyboard visibility and best-effort input type classification (`text`, `number`, `email`, `phone`, `password`, `datetime`).
|
|
472
549
|
- `keyboard dismiss` issues Android back keyevent only when keyboard is visible, then verifies hidden state.
|
|
473
550
|
- Works with an active session device or explicit selectors (`--platform`, `--device`, `--udid`, `--serial`).
|
|
@@ -492,6 +569,7 @@ Keyboard:
|
|
|
492
569
|
- If startup fails with stale metadata hints, remove stale `<state-dir>/daemon.json` / `<state-dir>/daemon.lock` and retry (state dir defaults to `~/.agent-device` unless overridden).
|
|
493
570
|
|
|
494
571
|
Boot diagnostics:
|
|
572
|
+
|
|
495
573
|
- Boot failures include normalized reason codes in `error.details.reason` (JSON mode) and verbose logs.
|
|
496
574
|
- Reason codes: `IOS_BOOT_TIMEOUT`, `IOS_RUNNER_CONNECT_TIMEOUT`, `ANDROID_BOOT_TIMEOUT`, `ADB_TRANSPORT_UNAVAILABLE`, `CI_RESOURCE_STARVATION_SUSPECTED`, `BOOT_COMMAND_FAILED`, `UNKNOWN`.
|
|
497
575
|
- Android boot waits fail fast for permission/tooling issues and do not always collapse into timeout errors.
|
|
@@ -502,16 +580,19 @@ Boot diagnostics:
|
|
|
502
580
|
- Set `AGENT_DEVICE_RETRY_LOGS=1` to also print retry telemetry directly to stderr (ad-hoc troubleshooting).
|
|
503
581
|
|
|
504
582
|
Diagnostics files:
|
|
583
|
+
|
|
505
584
|
- Failed commands persist diagnostics in `~/.agent-device/logs/<session>/<date>/<timestamp>-<diagnosticId>.ndjson`.
|
|
506
585
|
- `--debug` persists diagnostics for successful commands too and streams live diagnostic events.
|
|
507
586
|
- JSON failures include `error.hint`, `error.diagnosticId`, and `error.logPath`.
|
|
508
587
|
|
|
509
588
|
## App resolution
|
|
589
|
+
|
|
510
590
|
- Bundle/package identifiers are accepted directly (e.g., `com.apple.Preferences`).
|
|
511
591
|
- Human-readable names are resolved when possible (e.g., `Settings`).
|
|
512
592
|
- Built-in aliases include `Settings` for both platforms.
|
|
513
593
|
|
|
514
594
|
## iOS notes
|
|
595
|
+
|
|
515
596
|
- Core runner commands: `snapshot`, `wait`, `click`, `fill`, `get`, `is`, `find`, `press`, `longpress`, `focus`, `type`, `scroll`, `scrollintoview`, `back`, `home`, `app-switcher`.
|
|
516
597
|
- Simulator-only commands: `alert`, `pinch`, `settings`.
|
|
517
598
|
- tvOS targets are selectable (`--platform ios --target tv` or `--platform apple --target tv`) and support runner-driven interaction/snapshot commands.
|
|
@@ -546,6 +627,7 @@ pnpm build
|
|
|
546
627
|
```
|
|
547
628
|
|
|
548
629
|
Environment selectors:
|
|
630
|
+
|
|
549
631
|
- `ANDROID_DEVICE=Pixel_9_Pro_XL` or `ANDROID_SERIAL=emulator-5554`
|
|
550
632
|
- `IOS_DEVICE="iPhone 17 Pro"` or `IOS_UDID=<udid>`
|
|
551
633
|
- `AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET=<path>` (or `IOS_SIMULATOR_DEVICE_SET=<path>`) to scope all iOS simulator discovery/commands to one simulator set.
|
|
@@ -568,7 +650,7 @@ Environment selectors:
|
|
|
568
650
|
- `AGENT_DEVICE_DAEMON_BASE_URL=http(s)://host:port[/base-path]` connect directly to a remote HTTP daemon and skip local daemon metadata/startup.
|
|
569
651
|
- Remote daemon installs upload local artifacts through `POST /upload`; use a `remote:` path prefix when you need the daemon to read an existing server-side artifact path as-is.
|
|
570
652
|
- `AGENT_DEVICE_DAEMON_AUTH_TOKEN=<token>` auth token for remote HTTP daemon mode; sent in both the JSON-RPC request token and HTTP auth headers (`Authorization: Bearer` and `x-agent-device-token`).
|
|
571
|
-
- `AGENT_DEVICE_PROXY_TOKEN=<token>` preferred bearer token for `metro prepare --proxy-base-url <url>` so the
|
|
653
|
+
- `AGENT_DEVICE_PROXY_TOKEN=<token>` preferred bearer token for `metro prepare --proxy-base-url <url>` so the host-bridge secret does not need to be passed on the command line. `AGENT_DEVICE_METRO_BEARER_TOKEN` is also supported.
|
|
572
654
|
- `AGENT_DEVICE_DAEMON_SERVER_MODE=socket|http|dual` daemon server mode. `http` and `dual` expose JSON-RPC 2.0 at `POST /rpc` (`GET /health` available for liveness).
|
|
573
655
|
- `AGENT_DEVICE_DAEMON_TRANSPORT=auto|socket|http` client preference when connecting to daemon metadata.
|
|
574
656
|
- `AGENT_DEVICE_HTTP_AUTH_HOOK=<module-path>` optional HTTP auth hook module path for JSON-RPC server mode.
|
|
@@ -587,11 +669,13 @@ Environment selectors:
|
|
|
587
669
|
- `AGENT_DEVICE_IOS_CLEAN_DERIVED=1` rebuild iOS runner artifacts from scratch for runtime daemon-triggered builds (`pnpm ad ...`) on the selected path. `pnpm build:xcuitest` (alias of `pnpm build:xcuitest:ios`), `pnpm build:xcuitest:tvos`, and `pnpm build:all` already clear their default derived paths and do not require this variable. When `AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH` is set, cleanup is blocked by default; set `AGENT_DEVICE_IOS_ALLOW_OVERRIDE_DERIVED_CLEAN=1` only for trusted custom paths.
|
|
588
670
|
|
|
589
671
|
Test screenshots are written to:
|
|
672
|
+
|
|
590
673
|
- `test/screenshots/android-settings.png`
|
|
591
674
|
- `test/screenshots/ios-settings.png`
|
|
592
675
|
|
|
593
676
|
## Contributing
|
|
594
|
-
|
|
677
|
+
|
|
678
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
595
679
|
|
|
596
680
|
## Made at Callstack
|
|
597
681
|
|
package/dist/src/224.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createRequestId as e,node_path as t,isAgentDeviceDaemonProcess as r,runCmdDetached as a,readVersion as n,findProjectRoot as o,node_https as i,runCmdSync as s,withDiagnosticTimer as l,resolveDaemonTransportPreference as d,resolveUserPath as u,emitDiagnostic as c,spawn as p,AppError as m,node_fs as h,resolveDaemonPaths as f,node_net as I,resolveDaemonServerMode as g,stopProcessForTakeover as w,node_http as b}from"./331.js";async function y(e){let{localPath:r,baseUrl:a,token:n}=e,o=h.statSync(r).isDirectory(),s=t.basename(r),l=new URL("upload",a.endsWith("/")?a:`${a}/`),d="https:"===l.protocol?i:b,u={"x-artifact-type":o?"app-bundle":"file","x-artifact-filename":s,"transfer-encoding":"chunked"};return n&&(u.authorization=`Bearer ${n}`,u["x-agent-device-token"]=n),new Promise((e,a)=>{let n=d.request({protocol:l.protocol,host:l.hostname,port:l.port,method:"POST",path:l.pathname+l.search,headers:u},t=>{let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{clearTimeout(i);try{let t=JSON.parse(r);if(!t.ok||!t.uploadId)return void a(new m("COMMAND_FAILED",`Upload failed: ${r}`));e(t.uploadId)}catch{a(new m("COMMAND_FAILED",`Invalid upload response: ${r}`))}})}),i=setTimeout(()=>{n.destroy(),a(new m("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(n.on("error",e=>{clearTimeout(i),a(new m("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),o){let e=p("tar",["cf","-","-C",t.dirname(r),t.basename(r)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(n.destroy(),a(new m("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=h.createReadStream(r);e.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}let v=function(e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if(!e)return 9e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):9e4}(),A=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_TIMEOUT_MS){if(!e)return 15e3;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):15e3}(),D=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_ATTEMPTS){if(!e)return 2;let t=Number(e);return Number.isFinite(t)?Math.min(5,Math.max(1,Math.floor(t))):2}(),_=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"];async function P(t){let r=t.meta?.requestId??e(),a=!!(t.meta?.debug||t.flags?.verbose),n=function(e){let t=e.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,r=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new m("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new m("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(e.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),a=e.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN,n=e.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,o=d(n);if(r&&"socket"===o)throw new m("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:r});let i=g(e.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===n?"dual":void 0));return{paths:f(t),transportPreference:o,serverMode:i,remoteBaseUrl:r,remoteAuthToken:a}}(t),o=await l("daemon_startup",async()=>await N(n),{requestId:r,session:t.session}),i=await E(t,o),s={...t,positionals:i.positionals,flags:i.flags,token:o.token,meta:{...t.meta??{},requestId:r,debug:a,cwd:t.meta?.cwd,tenantId:t.meta?.tenantId??t.flags?.tenant,runId:t.meta?.runId??t.flags?.runId,leaseId:t.meta?.leaseId??t.flags?.leaseId,sessionIsolation:t.meta?.sessionIsolation??t.flags?.sessionIsolation,lockPolicy:t.meta?.lockPolicy,lockPlatform:t.meta?.lockPlatform,...i.uploadedArtifactId?{uploadedArtifactId:i.uploadedArtifactId}:{},...i.clientArtifactPaths?{clientArtifactPaths:i.clientArtifactPaths}:{},...i.installSource?{installSource:i.installSource}:{}}};return c({level:"info",phase:"daemon_request_prepare",data:{requestId:r,command:t.command,session:t.session}}),await l("daemon_request",async()=>await j(o,s,n.transportPreference),{requestId:r,command:t.command})}async function E(e,r){let a,n=[...e.positionals??[]],o=e.flags?{...e.flags}:void 0,i=e.meta?.installSource,s={};if(X(r)){let l=function(e,r){if("screenshot"===e.command){let t=M(e,"path",".png");return r[0]?{field:"path",localPath:t,positionalIndex:0,positionalPath:k("screenshot",".png")}:{field:"path",localPath:t,positionalIndex:0,flagPath:k("screenshot",".png")}}if("record"===e.command&&"start"===(r[0]??"").toLowerCase()){let r=M(e,"outPath",".mp4",1);return{field:"outPath",localPath:r,positionalIndex:1,positionalPath:k("recording",t.extname(r)||".mp4")}}return null}(e,n);l&&(void 0!==l.positionalPath&&(n[l.positionalIndex]=l.positionalPath),void 0!==l.flagPath&&((o??={}).out=l.flagPath),s[l.field]=l.localPath);let d=await S(e,r);d&&(i=d.installSource,a=d.uploadedArtifactId??a)}if(!X(r)||"install"!==e.command&&"reinstall"!==e.command||n.length<2)return{positionals:n,flags:o,installSource:i,uploadedArtifactId:a,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}};let l=n[1];if(l.startsWith("remote:"))return n[1]=l.slice(7),{positionals:n,flags:o,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}};let d=t.isAbsolute(l)?l:t.resolve(e.meta?.cwd??process.cwd(),l);return h.existsSync(d)?{positionals:n,flags:o,installSource:i,uploadedArtifactId:a=await y({localPath:d,baseUrl:r.baseUrl,token:r.token}),...Object.keys(s).length>0?{clientArtifactPaths:s}:{}}:{positionals:n,flags:o,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}}}async function S(e,r){let a=e.meta?.installSource;if("install_source"!==e.command||!a||"path"!==a.kind)return null;let n=a.path.trim();if(!n)return{installSource:a};if(n.startsWith("remote:"))return{installSource:{...a,path:n.slice(7)}};let o=t.isAbsolute(n)?n:t.resolve(e.meta?.cwd??process.cwd(),n);if(!h.existsSync(o))return{installSource:{...a,path:o}};let i=await y({localPath:o,baseUrl:r.baseUrl,token:r.token});return{installSource:{...a,path:o},uploadedArtifactId:i}}function M(e,r,a,n=0){let o=e.positionals?.[n]??e.flags?.out,i=`${"path"===r?"screenshot":"recording"}-${Date.now()}${a}`,s=o&&o.trim().length>0?o:i;return t.isAbsolute(s)?s:t.resolve(e.meta?.cwd??process.cwd(),s)}function k(e,r){let a=r.startsWith(".")?r:`.${r}`;return t.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${a}`)}async function N(e){let a;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await q(t,"http"))return t;throw new m("COMMAND_FAILED","Remote daemon is unavailable",{daemonBaseUrl:e.remoteBaseUrl,hint:"Verify AGENT_DEVICE_DAEMON_BASE_URL points to a reachable daemon with GET /health and POST /rpc."})}let i=x(e.paths.infoPath),s=n(),l=function(e,r=o()){try{let a=h.statSync(e),n=t.relative(r,e)||e;return`${n}:${a.size}:${Math.trunc(a.mtimeMs)}`}catch{return"unknown"}}((a=B()).useSrc?a.srcPath:a.distPath,a.root),d=!!i&&await q(i,e.transportPreference);if(i&&i.version===s&&i.codeSignature===l&&d)return i;i&&(i.version!==s||i.codeSignature!==l||!d)&&(await O(i),F(e.paths.infoPath)),function(e){let t=L(e);if(!t.hasLock||t.hasInfo)return;let a=C(e.lockPath);if(!a)return F(e.lockPath);r(a.pid,a.processStartTime)||F(e.lockPath)}(e.paths);let u=0;for(let t=1;t<=D;t+=1){await z(e);let r=await T(A,e);if(r)return r;if(await U(e.paths)){u+=1;continue}let a=L(e.paths);if(!(t<D))break;if(!a.hasInfo&&!a.hasLock){await R(150);continue}}let c=L(e.paths);throw new m("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:A,startupAttempts:D,lockRecoveryCount:u,metadataState:c,hint:function(e,t=f(process.env.AGENT_DEVICE_STATE_DIR)){return e.hasLock&&!e.hasInfo?`Detected ${t.lockPath} without ${t.infoPath}. If no agent-device daemon process is running, delete ${t.lockPath} and retry.`:e.hasLock&&e.hasInfo?`Daemon metadata may be stale. If no agent-device daemon process is running, delete ${t.infoPath} and ${t.lockPath}, then retry.`:`Daemon metadata is missing or stale. Delete ${t.infoPath} if present and retry.`}(c,e.paths)})}async function T(e,t){let r=Date.now();for(;Date.now()-r<e;){let e=x(t.paths.infoPath);if(e&&await q(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function R(e){await new Promise(t=>setTimeout(t,e))}async function U(e){let t=L(e);if(!t.hasLock||t.hasInfo)return!1;let a=C(e.lockPath);return a&&r(a.pid,a.processStartTime)&&await w(a.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:a.processStartTime}),F(e.lockPath),!0}async function O(e){await w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function x(e){let t=$(e);if(!t||"object"!=typeof t)return null;let r="string"==typeof t.token&&t.token.length>0?t.token:null;if(!r)return null;let a=Number.isInteger(t.port)&&Number(t.port)>0,n=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;if(!a&&!n)return null;let o=t.transport,i="string"==typeof t.version?t.version:void 0,s="string"==typeof t.codeSignature?t.codeSignature:void 0,l="string"==typeof t.processStartTime?t.processStartTime:void 0,d=Number.isInteger(t.pid)&&Number(t.pid)>0;return{token:r,port:a?Number(t.port):void 0,httpPort:n?Number(t.httpPort):void 0,transport:"socket"===o||"http"===o||"dual"===o?o:void 0,pid:d?Number(t.pid):0,version:i,codeSignature:s,processStartTime:l}}function C(e){let t=$(e);return t&&"object"==typeof t&&Number.isInteger(t.pid)&&Number(t.pid)>0?{pid:Number(t.pid),processStartTime:"string"==typeof t.processStartTime?t.processStartTime:void 0,startedAt:"number"==typeof t.startedAt?t.startedAt:void 0}:null}function L(e){return{hasInfo:h.existsSync(e.infoPath),hasLock:h.existsSync(e.lockPath)}}function $(e){if(!h.existsSync(e))return null;try{return JSON.parse(h.readFileSync(e,"utf8"))}catch{return null}}function F(e){try{h.existsSync(e)&&h.unlinkSync(e)}catch{}}async function q(e,t){var r;return"http"===G(e,t)?await function(e){let t=e.baseUrl?Y(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!t)return Promise.resolve(!1);let r=new URL(t),a="https:"===r.protocol?i:b,n=e.baseUrl?3e3:500;return new Promise(e=>{let t=a.request({protocol:r.protocol,host:r.hostname,port:r.port,path:r.pathname+r.search,method:"GET",timeout:n},t=>{t.resume(),e((t.statusCode??500)<500)});t.on("timeout",()=>{t.destroy(),e(!1)}),t.on("error",()=>{e(!1)}),t.end()})}(e):await ((r=e.port)?new Promise(e=>{let t=I.createConnection({host:"127.0.0.1",port:r},()=>{t.destroy(),e(!0)});t.on("error",()=>{e(!1)})}):Promise.resolve(!1))}async function z(e){let t=B(),r=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],n={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};a(process.execPath,r,{env:n})}function B(){let e=o(),r=t.join(e,"dist","src","daemon.js"),a=t.join(e,"src","daemon.ts"),n=h.existsSync(r),i=h.existsSync(a);if(!n&&!i)throw new m("COMMAND_FAILED","Daemon entry not found",{distPath:r,srcPath:a});return{root:e,distPath:r,srcPath:a,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!n&&i}}async function j(e,t,r){return"http"===G(e,r)?await J(e,t):await H(e,t)}function G(e,t){if(e.baseUrl){if("socket"===t)throw new m("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t||"socket"===t){var r=e,a=t;if(V(r,a))return a;throw new m("COMMAND_FAILED","http"===a?"Daemon HTTP endpoint is unavailable":"Daemon socket endpoint is unavailable")}let n=("socket"===e.transport||"dual"===e.transport?["socket","http"]:["http","socket"]).find(t=>V(e,t));if(n)return n;throw new m("COMMAND_FAILED","Daemon metadata has no reachable transport")}function V(e,t){return"http"===t?!!e.httpPort:!!e.port}async function H(e,t){let r=e.port;if(!r)throw new m("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((a,n)=>{let o=I.createConnection({host:"127.0.0.1",port:r},()=>{o.write(`${JSON.stringify(t)}
|
|
2
|
-
`)}),i=setTimeout(()=>{o.destroy();let r=K(),a=W(e,f(t.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR));c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:v,requestId:t.meta?.requestId,command:t.command,timedOutRunnerPidsTerminated:r.terminated,timedOutRunnerCleanupError:r.error,daemonPidReset:e.pid,daemonPidForceKilled:a.forcedKill}}),n(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:v,requestId:t.meta?.requestId,hint:"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},v),s="";o.setEncoding("utf8"),o.on("data",e=>{let r=(s+=e).indexOf("\n");if(-1===r)return;let l=s.slice(0,r).trim();if(l)try{let e=JSON.parse(l);o.end(),clearTimeout(i),a(e)}catch(e){clearTimeout(i),n(new m("COMMAND_FAILED","Invalid daemon response",{requestId:t.meta?.requestId,line:l},e instanceof Error?e:void 0))}}),o.on("error",e=>{clearTimeout(i),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:t.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),n(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:t.meta?.requestId,hint:"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e))})})}async function J(t,r){let a=t.baseUrl?new URL(Y(t.baseUrl,"rpc")):t.httpPort?new URL(`http://127.0.0.1:${t.httpPort}/rpc`):null;if(!a)throw new m("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let n=JSON.stringify({jsonrpc:"2.0",id:r.meta?.requestId??e(),method:"agent_device.command",params:r}),o={"content-type":"application/json","content-length":Buffer.byteLength(n)};return t.baseUrl&&t.token&&(o.authorization=`Bearer ${t.token}`,o["x-agent-device-token"]=t.token),await new Promise((e,s)=>{let l=f(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),d=("https:"===a.protocol?i:b).request({protocol:a.protocol,host:a.hostname,port:a.port,method:"POST",path:a.pathname+a.search,headers:o},a=>{let n="";a.setEncoding("utf8"),a.on("data",e=>{n+=e}),a.on("end",()=>{clearTimeout(u);try{let a=JSON.parse(n);if(a.error){let e=a.error.data??{};s(new m(String(e.code??"COMMAND_FAILED"),String(e.message??a.error.message??"Daemon RPC request failed"),{..."object"==typeof e.details&&e.details?e.details:{},hint:"string"==typeof e.hint?e.hint:void 0,diagnosticId:"string"==typeof e.diagnosticId?e.diagnosticId:void 0,logPath:"string"==typeof e.logPath?e.logPath:void 0,requestId:r.meta?.requestId}));return}if(!a.result||"object"!=typeof a.result)return void s(new m("COMMAND_FAILED","Invalid daemon RPC response",{requestId:r.meta?.requestId}));if(t.baseUrl&&a.result.ok)return void Q(t,r,a.result).then(e).catch(s);e(a.result)}catch(e){clearTimeout(u),s(new m("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:n},e instanceof Error?e:void 0))}})}),u=setTimeout(()=>{d.destroy();let e=X(t)?{terminated:0}:K(),a=X(t)?{forcedKill:!1}:W(t,l);c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:v,requestId:r.meta?.requestId,command:r.command,timedOutRunnerPidsTerminated:e.terminated,timedOutRunnerCleanupError:e.error,daemonPidReset:X(t)?void 0:t.pid,daemonPidForceKilled:X(t)?void 0:a.forcedKill,daemonBaseUrl:t.baseUrl}}),s(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:v,requestId:r.meta?.requestId,hint:X(t)?"Retry with --debug and verify the remote daemon URL, auth token, and remote host logs.":"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},v);d.on("error",e=>{clearTimeout(u),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:r.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),s(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:r.meta?.requestId,hint:X(t)?"Retry command. If this persists, verify the remote daemon URL, auth token, and remote host reachability.":"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e))}),d.write(n),d.end()})}function K(){let e=0;try{for(let t of _){let r=s("pkill",["-f",t],{allowFailure:!0});0===r.exitCode&&(e+=1)}return{terminated:e}}catch(t){return{terminated:e,error:t instanceof Error?t.message:String(t)}}}function W(e,t){let a=!1;try{r(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),a=!0)}catch{w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{F(t.infoPath),F(t.lockPath)}return{forcedKill:a}}function X(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function Y(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function Q(e,r,a){let n=Array.isArray(a.data?.artifacts)?a.data.artifacts:[];if(0===n.length||!e.baseUrl)return a;let o=a.data?{...a.data}:{},i=[];for(let a of n){if(!a||"object"!=typeof a||"string"!=typeof a.artifactId){i.push(a);continue}let n=function(e,r){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let a=e.fileName?.trim()||`${e.field}-${Date.now()}`;return t.resolve(r.meta?.cwd??process.cwd(),a)}(a,r);await Z({baseUrl:e.baseUrl,token:e.token,artifactId:a.artifactId,destinationPath:n,requestId:r.meta?.requestId}),o[a.field]=n,i.push({...a,localPath:n})}return o.artifacts=i,{ok:!0,data:o}}async function Z(e){var r,a;let n,o=new URL((r=e.baseUrl,a=e.artifactId,n=r.endsWith("/")?r:`${r}/`,new URL(`upload/${encodeURIComponent(a)}`,n).toString())),s="https:"===o.protocol?i:b;await h.promises.mkdir(t.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,r)=>{let a=!1,n=e.timeoutMs??v,i=n=>{if(!a){if(a=!0,clearTimeout(d),n)return void h.promises.rm(e.destinationPath,{force:!0}).finally(()=>r(n));t()}},l=s.request({protocol:o.protocol,host:o.hostname,port:o.port,method:"GET",path:o.pathname+o.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:r}))});return}let r=h.createWriteStream(e.destinationPath);r.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("aborted",()=>{i(new m("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),r.on("finish",()=>{r.close(()=>i())}),t.pipe(r)}),d=setTimeout(()=>{l.destroy(new m("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n}))},n);l.on("error",t=>{t instanceof m?i(t):i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n},t instanceof Error?t:void 0))}),l.end()})}function ee(e){return e.replace(/\/+$/,"")}function et(e){return"string"==typeof e&&e.trim()?ee(e.trim()):""}function er(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function ea(e,t,r){return u(e,{env:t,cwd:r})}function en(e){try{return h.accessSync(e,h.constants.F_OK),!0}catch{return!1}}function eo(e,t,r){if(null==e||""===e)return t;let a=Number.parseInt(String(e),10);return Number.isInteger(a)?Math.max(a,r):t}function ei(e,t){let r;return{platform:t,bundleUrl:((r=new URL(`${ee(e)}/index.bundle`)).searchParams.set("platform",t),r.searchParams.set("dev","true"),r.searchParams.set("minify","false"),r.toString())}}function es(e,t){return{platform:t,metroHost:er(e?.metro_host),metroPort:e?.metro_port,bundleUrl:er(e?.metro_bundle_url),launchUrl:er(e?.launch_url)}}function el(e){return`'${e.replace(/'/g,"'\"'\"'")}'`}async function ed(e){await new Promise(t=>setTimeout(t,e))}async function eu(e,t,r={}){try{let a=await fetch(e,{headers:r,signal:AbortSignal.timeout(t)});return{ok:a.ok,status:a.status,body:await a.text()}}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw Error(`Timed out fetching ${e} after ${t}ms`);throw r}}async function ec(e,t){try{let r=await eu(e,t);return r.ok&&r.body.includes("packager-status:running")}catch{return!1}}async function ep(e){var t,r,a;let n;try{n=await fetch(`${e.baseUrl}/api/metro/bridge`,{method:"POST",headers:(t=e.baseUrl,r=e.bearerToken,{Authorization:`Bearer ${r}`,"Content-Type":"application/json",...t.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({ios_runtime:e.runtime,timeout_ms:e.timeoutMs}),signal:AbortSignal.timeout(e.timeoutMs)})}catch(t){if(t instanceof Error&&"TimeoutError"===t.name)throw Error(`/api/metro/bridge timed out after ${e.timeoutMs}ms calling ${e.baseUrl}/api/metro/bridge`);throw t}let o=await n.text(),i=o?JSON.parse(o):{};if(!n.ok)throw Error(`/api/metro/bridge failed (${n.status}): ${JSON.stringify(i)}`);return{enabled:(a=i.data??i).enabled,baseUrl:a.base_url,statusUrl:a.status_url,bundleUrl:a.bundle_url,iosRuntime:es(a.ios_runtime,"ios"),androidRuntime:es(a.android_runtime,"android"),upstream:{bundleUrl:a.upstream.bundle_url,host:a.upstream.host,port:a.upstream.port,statusUrl:a.upstream.status_url},probe:{reachable:a.probe.reachable,statusCode:a.probe.status_code,latencyMs:a.probe.latency_ms,detail:a.probe.detail}}}async function em(e,t,r){let a=Date.now()+t;for(;Date.now()<a;){let t=Math.min(r,Math.max(a-Date.now(),1));if(await ec(e,t))return!0;let n=Math.min(500,Math.max(a-Date.now(),0));n>0&&await ed(n)}return!1}async function eh(e={}){let r=e.env??process.env,a=process.cwd(),n=ea(e.projectRoot??a,r,a),o=function(e,r){if("auto"!==r)return r;let a=function(e){let r=t.join(e,"package.json");if(!en(r))throw new m("INVALID_ARGS",`package.json not found at ${r}`);return JSON.parse(h.readFileSync(r,"utf8"))}(e);return"string"==typeof({...a.dependencies??{},...a.devDependencies??{}}).expo?"expo":"react-native"}(n,e.kind??"auto"),i=function(e,t){if(null==e||""===e)return 8081;let r=Number.parseInt(String(e),10);if(!Number.isInteger(r)||r<1||r>65535)throw new m("INVALID_ARGS",`Invalid Metro port: ${String(e)}. Use 1-65535.`);return r}(e.metroPort??8081,0),l=er(e.listenHost)??"0.0.0.0",d=er(e.statusHost)??"127.0.0.1",u=et(e.publicBaseUrl),c=eo(e.startupTimeoutMs,18e4,3e4),p=eo(e.probeTimeoutMs,1e4,1e3),f=e.reuseExisting??!0,I=e.installDependenciesIfNeeded??!0,g=e.runtimeFilePath?ea(e.runtimeFilePath,r,a):null,w=ea(e.logPath??t.join(n,".agent-device","metro.log"),r,a);if(!u)throw new m("INVALID_ARGS","metro prepare requires --public-base-url <url>.");let{proxyEnabled:b,proxyBaseUrl:y,proxyBearerToken:v}=function(e,t){if(e&&!t)throw new m("INVALID_ARGS","metro prepare requires proxy auth when --proxy-base-url is provided. Pass --bearer-token or set AGENT_DEVICE_PROXY_TOKEN.");if(!e&&t)throw new m("INVALID_ARGS","metro prepare requires --proxy-base-url when proxy auth is provided.");return{proxyEnabled:!!(e&&t),proxyBaseUrl:e,proxyBearerToken:t}}(et(e.proxyBaseUrl),er(e.proxyBearerToken)??""),A=I?function(e,r){if(function(e){try{return h.statSync(e).isDirectory()}catch{return!1}}(t.join(e,"node_modules")))return{installed:!1};let a=en(t.join(e,"pnpm-lock.yaml"))?{command:"pnpm",installArgs:["install"]}:en(t.join(e,"yarn.lock"))?{command:"yarn",installArgs:["install"]}:{command:"npm",installArgs:["install"]};return s(a.command,a.installArgs,{cwd:e,env:r}),{installed:!0,packageManager:a.command}}(n,r):{installed:!1},D=`http://${d}:${i}/status`,_=!1,P=!1,E=0;if(f&&await ec(D,p))P=!0;else if(_=!0,E=function(e,r,a,n,o,i){let l="expo"===r?{command:"npx",installArgs:["expo","start","--host","lan","--port",String(a)]}:{command:"npx",installArgs:["react-native","start","--host",n,"--port",String(a)]};h.mkdirSync(t.dirname(o),{recursive:!0});let d=[el(l.command),...l.installArgs.map(el)].join(" "),u=s("/bin/sh",["-c",`nohup ${d} >> ${el(o)} 2>&1 < /dev/null & echo $!`],{cwd:e,env:i}),c=Number.parseInt(u.stdout.trim(),10);if(!Number.isInteger(c)||c<=0)throw Error(`Failed to start Metro. Expected a child PID in stdout, got "${u.stdout.trim()}".`);return{pid:c}}(n,o,i,l,w,r).pid,!await em(D,c,p))throw Error(`Metro did not become ready at ${D} within ${c}ms. Check ${w}.`);let S=ei(u,"ios"),M=ei(u,"android"),k=null,N=null;if(b)try{k=await ep({baseUrl:y,bearerToken:v,runtime:{metro_bundle_url:S.bundleUrl},timeoutMs:p})}catch(e){N=e instanceof Error?e.message:String(e)}if(b&&(!k||!1===k.probe.reachable)){var T,R;let e;throw Error((T=N,R=k,e=[`Metro bridge is required for this run but could not be configured via ${y}/api/metro/bridge.`],T&&e.push(`bridgeError=${T}`),R?.probe.reachable===!1&&e.push(`bridgeProbe=${R.probe.detail||`unreachable (status ${R.probe.statusCode||0})`}`),e.join(" ")))}let U=k?.iosRuntime??S,O=k?.androidRuntime??M,x={projectRoot:n,kind:o,dependenciesInstalled:A.installed,packageManager:A.packageManager??null,started:_,reused:P,pid:E,logPath:w,statusUrl:D,runtimeFilePath:g,iosRuntime:U,androidRuntime:O,bridge:k};return g&&(h.mkdirSync(t.dirname(g),{recursive:!0}),h.writeFileSync(g,JSON.stringify(x,null,2))),x}function ef(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function eI(e,t,r){return{deviceId:t,deviceName:r,..."ios"===e?{udid:t}:{serial:t}}}function eg(e,t={}){let r=t.includeAndroidSerial??!0;return{platform:e.platform,target:e.target,device:e.name,id:e.id,..."ios"===e.platform?{device_udid:e.ios?.udid??e.id,ios_simulator_device_set:e.ios?.simulatorSetPath??null}:{},..."android"===e.platform&&r?{serial:e.android?.serial??e.id}:{}}}function ew(e){return{name:e.name,...eg(e.device,{includeAndroidSerial:!1}),createdAt:e.createdAt}}function eb(e){return{platform:e.platform,id:e.id,name:e.name,kind:e.kind,target:e.target,..."boolean"==typeof e.booted?{booted:e.booted}:{}}}function ey(e){return{udid:e.udid,device:e.device,runtime:e.runtime,ios_simulator_device_set:e.iosSimulatorDeviceSet??null,created:e.created,booted:e.booted}}function ev(e){return{session:e.session,configured:e.configured,...e.cleared?{cleared:!0}:{},...e.runtime?{runtime:e.runtime}:{}}}function eA(e){return{app:e.app,appPath:e.appPath,platform:e.platform,...e.appId?{appId:e.appId}:{},...e.bundleId?{bundleId:e.bundleId}:{},...e.package?{package:e.package}:{}}}function eD(e){return{launchTarget:e.launchTarget,...e.appName?{appName:e.appName}:{},...e.appId?{appId:e.appId}:{},...e.bundleId?{bundleId:e.bundleId}:{},...e.packageName?{package:e.packageName}:{},...e.installablePath?{installablePath:e.installablePath}:{},...e.archivePath?{archivePath:e.archivePath}:{},...e.materializationId?{materializationId:e.materializationId}:{},...e.materializationExpiresAt?{materializationExpiresAt:e.materializationExpiresAt}:{}}}function e_(e){return{session:e.session,...e.appName?{appName:e.appName}:{},...e.appBundleId?{appBundleId:e.appBundleId}:{},...e.startup?{startup:e.startup}:{},...e.runtime?{runtime:e.runtime}:{},...e.device?eg(e.device):{}}}function eP(e){return{session:e.session,...e.shutdown?{shutdown:e.shutdown}:{}}}function eE(e){return{nodes:e.nodes,truncated:e.truncated,...e.appName?{appName:e.appName}:{},...e.appBundleId?{appBundleId:e.appBundleId}:{}}}function eS(e,t){let r=eL(e,"bundleId"),a=eL(e,"package");return{app:eC(e,"app"),appPath:eC(e,"appPath"),platform:e$(e,"platform"),appId:r??a,bundleId:r,package:a,identifiers:ef({session:t,bundleId:r,packageName:a})}}function eM(e,t){return{session:t,configured:!0===e.configured,cleared:!0===e.cleared||void 0,runtime:eT(e.runtime),identifiers:{session:t}}}function ek(e){var t;let r=eO(e),a=e$(r,"platform"),n=eC(r,"id"),o=eC(r,"name");return{platform:a,target:eF(r,"target"),kind:eq(r,t="kind",eV,`Daemon response has invalid "${t}".`),id:n,name:o,booted:"boolean"==typeof r.booted?r.booted:void 0,identifiers:eI(a,n,o),ios:"ios"===a?{udid:n}:void 0,android:"android"===a?{serial:n}:void 0}}function eN(e){var t;let r=eO(e),a=e$(r,"platform"),n=eC(r,"id"),o=eC(r,"name"),i=eF(r,"target"),s=eC(r,"device"),l={session:o,...eI(a,n,s)};return{name:o,createdAt:eq(r,t="createdAt",ej,`Daemon response is missing numeric "${t}".`),device:{platform:a,target:i,id:n,name:s,identifiers:l,ios:"ios"===a?{udid:n,simulatorSetPath:ez(r,"ios_simulator_device_set",eB)}:void 0,android:"android"===a?{serial:n}:void 0},identifiers:l}}function eT(e){if(!ex(e))return;let t=e.platform,r=eL(e,"metroHost"),a="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:r,metroPort:a,bundleUrl:eL(e,"bundleUrl"),launchUrl:eL(e,"launchUrl")}}function eR(e,t){return t??e??"default"}function eU(e){let t={};for(let[r,a]of Object.entries(e))void 0!==a&&(t[r]=a);return t}function eO(e){if(!ex(e))throw new m("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function ex(e){return"object"==typeof e&&null!==e}function eC(e,t){return eq(e,t,eB,`Daemon response is missing "${t}".`)}function eL(e,t){return eB(e[t])}function e$(e,t){return eq(e,t,eG,`Daemon response has invalid "${t}".`)}function eF(e,t){return eH(e[t])??"mobile"}function eq(e,t,r,a){let n=r(e[t]);if(void 0===n)throw new m("COMMAND_FAILED",a,{response:e});return n}function ez(e,t,r){let a=e[t];return null===a?null:r(a)}function eB(e){return"string"==typeof e&&e.length>0?e:void 0}function ej(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function eG(e){return"ios"===e||"android"===e?e:void 0}function eV(e){return"simulator"===e||"emulator"===e||"device"===e?e:void 0}function eH(e){return"tv"===e?"tv":"mobile"===e?"mobile":void 0}function eJ(e={},t={}){let r=t.transport??P,a=async(t,a=[],n={})=>{let o={...e,...n},i=await r({session:eR(e.session,n.session),command:t,positionals:a,flags:eU({stateDir:o.stateDir,daemonBaseUrl:o.daemonBaseUrl,daemonAuthToken:o.daemonAuthToken,daemonTransport:o.daemonTransport,daemonServerMode:o.daemonServerMode,tenant:o.tenant,sessionIsolation:o.sessionIsolation,runId:o.runId,leaseId:o.leaseId,platform:o.platform,target:o.target,device:o.device,udid:o.udid,serial:o.serial,iosSimulatorDeviceSet:o.iosSimulatorDeviceSet,androidDeviceAllowlist:o.androidDeviceAllowlist,runtime:o.simulatorRuntimeId,boot:o.boot,reuseExisting:o.reuseExisting,activity:o.activity,relaunch:o.relaunch,shutdown:o.shutdown,saveScript:o.saveScript,noRecord:o.noRecord,metroHost:o.metroHost,metroPort:o.metroPort,bundleUrl:o.bundleUrl,launchUrl:o.launchUrl,snapshotInteractiveOnly:o.interactiveOnly,snapshotCompact:o.compact,snapshotDepth:o.depth,snapshotScope:o.scope,snapshotRaw:o.raw,verbose:o.debug}),runtime:o.runtime,meta:eU({requestId:o.requestId,cwd:o.cwd,debug:o.debug,lockPolicy:o.lockPolicy,lockPlatform:o.lockPlatform,tenantId:o.tenant,runId:o.runId,leaseId:o.leaseId,sessionIsolation:o.sessionIsolation,installSource:o.installSource,retainMaterializedPaths:o.retainMaterializedPaths,materializedPathRetentionMs:o.materializedPathRetentionMs,materializationId:o.materializationId})});if(!i.ok)throw new m(i.error.code,i.error.message,{...i.error.details??{},hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath});return i.data??{}},n=async(e={})=>{let t=await a("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(eN)};return{devices:{list:async(e={})=>{let t=await a("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(ek)}},sessions:{list:async(e={})=>await n(e),close:async(t={})=>{let r=eR(e.session,t.session),n=(await a("close",[],t)).shutdown;return{session:r,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},simulators:{ensure:async e=>{let{runtime:t,...r}=e,n=await a("ensure-simulator",[],{...r,simulatorRuntimeId:t}),o=eC(n,"udid"),i=eC(n,"device");return{udid:o,device:i,runtime:eC(n,"runtime"),created:!0===n.created,booted:!0===n.booted,iosSimulatorDeviceSet:ez(n,"ios_simulator_device_set",eB),identifiers:{deviceId:o,deviceName:i,udid:o}}}},apps:{install:async t=>eS(await a("install",[t.app,t.appPath],t),eR(e.session,t.session)),reinstall:async t=>eS(await a("reinstall",[t.app,t.appPath],t),eR(e.session,t.session)),installFromSource:async t=>(function(e,t){let r=eL(e,"bundleId"),a=eL(e,"packageName"),n=r??a??eL(e,"appId"),o=eL(e,"launchTarget")??a??r??n;if(!o)throw new m("COMMAND_FAILED",'Daemon response is missing "launchTarget".',{response:e});return{appName:eL(e,"appName"),appId:n,bundleId:r,packageName:a,launchTarget:o,installablePath:eL(e,"installablePath"),archivePath:eL(e,"archivePath"),materializationId:eL(e,"materializationId"),materializationExpiresAt:eL(e,"materializationExpiresAt"),identifiers:ef({session:t,bundleId:r,packageName:a,appId:n})}})(await a("install_source",[],{...t,installSource:t.source,retainMaterializedPaths:t.retainPaths,materializedPathRetentionMs:t.retentionMs}),eR(e.session,t.session)),open:async t=>{let r=eR(e.session,t.session),n=t.url?[t.app,t.url]:[t.app],o=await a("open",n,t),i=function(e){let t=e.platform,r=eL(e,"id"),a=eL(e,"device");if("ios"!==t&&"android"!==t||!r||!a)return;let n=eF(e,"target"),o=eI(t,r,a);return{platform:t,target:n,id:r,name:a,identifiers:o,ios:"ios"===t?{udid:eL(e,"device_udid")??r,simulatorSetPath:ez(e,"ios_simulator_device_set",eB)}:void 0,android:"android"===t?{serial:eL(e,"serial")??r}:void 0}}(o),s=eL(o,"appBundleId");return{session:r,appName:eL(o,"appName"),appBundleId:s,appId:s,startup:function(e){if(ex(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:eL(e,"appTarget"),appBundleId:eL(e,"appBundleId")}}(o.startup),runtime:eT(o.runtime),device:i,identifiers:{session:r,deviceId:i?.id,deviceName:i?.name,udid:i?.ios?.udid,serial:i?.android?.serial,appId:s,appBundleId:s}}},close:async(t={})=>{let r=eR(e.session,t.session),n=(await a("close",t.app?[t.app]:[],t)).shutdown;return{session:r,closedApp:t.app,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},materializations:{release:async e=>{var t;return{released:!0===(t=await a("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:eC(t,"materializationId"),identifiers:{}}}},runtime:{set:async t=>eM(await a("runtime",["set"],t),eR(e.session,t.session)),show:async(t={})=>eM(await a("runtime",["show"],t),eR(e.session,t.session))},metro:{prepare:async t=>await eh({projectRoot:t.projectRoot??e.cwd,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.bearerToken,metroPort:t.port,listenHost:t.listenHost,statusHost:t.statusHost,startupTimeoutMs:t.startupTimeoutMs,probeTimeoutMs:t.probeTimeoutMs,reuseExisting:t.reuseExisting,installDependenciesIfNeeded:t.installDependenciesIfNeeded,runtimeFilePath:t.runtimeFilePath,logPath:t.logPath})},capture:{snapshot:async(t={})=>{var r;let n=eR(e.session,t.session),o=await a("snapshot",[],t),i=eL(o,"appBundleId");return{nodes:Array.isArray(r=o.nodes)?r:[],truncated:!0===o.truncated,appName:eL(o,"appName"),appBundleId:i,identifiers:{session:n,appId:i,appBundleId:i}}},screenshot:async(t={})=>{let r=eR(e.session,t.session);return{path:eC(await a("screenshot",t.path?[t.path]:[],t),"path"),identifiers:{session:r}}}}}}export{eJ as createAgentDeviceClient,P as sendToDaemon,eP as serializeCloseResult,eA as serializeDeployResult,eb as serializeDevice,ey as serializeEnsureSimulatorResult,eD as serializeInstallFromSourceResult,e_ as serializeOpenResult,ev as serializeRuntimeResult,ew as serializeSessionListEntry,eE as serializeSnapshotResult};
|
|
1
|
+
import{createRequestId as e,node_path as t,isAgentDeviceDaemonProcess as r,runCmdDetached as a,readVersion as n,findProjectRoot as o,node_https as i,runCmdSync as s,withDiagnosticTimer as l,resolveDaemonTransportPreference as d,resolveUserPath as u,emitDiagnostic as c,spawn as p,AppError as m,node_fs as h,resolveDaemonPaths as f,node_net as I,resolveDaemonServerMode as g,stopProcessForTakeover as w,node_http as b}from"./331.js";async function y(e){let{localPath:r,baseUrl:a,token:n}=e,o=h.statSync(r).isDirectory(),s=t.basename(r),l=new URL("upload",a.endsWith("/")?a:`${a}/`),d="https:"===l.protocol?i:b,u={"x-artifact-type":o?"app-bundle":"file","x-artifact-filename":s,"transfer-encoding":"chunked"};return n&&(u.authorization=`Bearer ${n}`,u["x-agent-device-token"]=n),new Promise((e,a)=>{let n=d.request({protocol:l.protocol,host:l.hostname,port:l.port,method:"POST",path:l.pathname+l.search,headers:u},t=>{let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{clearTimeout(i);try{let t=JSON.parse(r);if(!t.ok||!t.uploadId)return void a(new m("COMMAND_FAILED",`Upload failed: ${r}`));e(t.uploadId)}catch{a(new m("COMMAND_FAILED",`Invalid upload response: ${r}`))}})}),i=setTimeout(()=>{n.destroy(),a(new m("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(n.on("error",e=>{clearTimeout(i),a(new m("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),o){let e=p("tar",["cf","-","-C",t.dirname(r),t.basename(r)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(n.destroy(),a(new m("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=h.createReadStream(r);e.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}let v=function(e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if(!e)return 9e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):9e4}(),A=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_TIMEOUT_MS){if(!e)return 15e3;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):15e3}(),D=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_ATTEMPTS){if(!e)return 2;let t=Number(e);return Number.isFinite(t)?Math.min(5,Math.max(1,Math.floor(t))):2}(),_=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"];async function P(t){let r=t.meta?.requestId??e(),a=!!(t.meta?.debug||t.flags?.verbose),n=function(e){let t=e.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,r=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new m("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new m("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(e.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),a=e.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN,n=e.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,o=d(n);if(r&&"socket"===o)throw new m("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:r});let i=g(e.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===n?"dual":void 0));return{paths:f(t),transportPreference:o,serverMode:i,remoteBaseUrl:r,remoteAuthToken:a}}(t),o=await l("daemon_startup",async()=>await N(n),{requestId:r,session:t.session}),i=await E(t,o),s={...t,positionals:i.positionals,flags:i.flags,token:o.token,meta:{...t.meta??{},requestId:r,debug:a,cwd:t.meta?.cwd,tenantId:t.meta?.tenantId??t.flags?.tenant,runId:t.meta?.runId??t.flags?.runId,leaseId:t.meta?.leaseId??t.flags?.leaseId,sessionIsolation:t.meta?.sessionIsolation??t.flags?.sessionIsolation,lockPolicy:t.meta?.lockPolicy,lockPlatform:t.meta?.lockPlatform,...i.uploadedArtifactId?{uploadedArtifactId:i.uploadedArtifactId}:{},...i.clientArtifactPaths?{clientArtifactPaths:i.clientArtifactPaths}:{},...i.installSource?{installSource:i.installSource}:{}}};return c({level:"info",phase:"daemon_request_prepare",data:{requestId:r,command:t.command,session:t.session}}),await l("daemon_request",async()=>await j(o,s,n.transportPreference),{requestId:r,command:t.command})}async function E(e,r){let a,n=[...e.positionals??[]],o=e.flags?{...e.flags}:void 0,i=e.meta?.installSource,s={};if(X(r)){let l=function(e,r){if("screenshot"===e.command){let t=M(e,"path",".png");return r[0]?{field:"path",localPath:t,positionalIndex:0,positionalPath:k("screenshot",".png")}:{field:"path",localPath:t,positionalIndex:0,flagPath:k("screenshot",".png")}}if("record"===e.command&&"start"===(r[0]??"").toLowerCase()){let r=M(e,"outPath",".mp4",1);return{field:"outPath",localPath:r,positionalIndex:1,positionalPath:k("recording",t.extname(r)||".mp4")}}return null}(e,n);l&&(void 0!==l.positionalPath&&(n[l.positionalIndex]=l.positionalPath),void 0!==l.flagPath&&((o??={}).out=l.flagPath),s[l.field]=l.localPath);let d=await S(e,r);d&&(i=d.installSource,a=d.uploadedArtifactId??a)}if(!X(r)||"install"!==e.command&&"reinstall"!==e.command||n.length<2)return{positionals:n,flags:o,installSource:i,uploadedArtifactId:a,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}};let l=n[1];if(l.startsWith("remote:"))return n[1]=l.slice(7),{positionals:n,flags:o,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}};let d=t.isAbsolute(l)?l:t.resolve(e.meta?.cwd??process.cwd(),l);return h.existsSync(d)?{positionals:n,flags:o,installSource:i,uploadedArtifactId:a=await y({localPath:d,baseUrl:r.baseUrl,token:r.token}),...Object.keys(s).length>0?{clientArtifactPaths:s}:{}}:{positionals:n,flags:o,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}}}async function S(e,r){let a=e.meta?.installSource;if("install_source"!==e.command||!a||"path"!==a.kind)return null;let n=a.path.trim();if(!n)return{installSource:a};if(n.startsWith("remote:"))return{installSource:{...a,path:n.slice(7)}};let o=t.isAbsolute(n)?n:t.resolve(e.meta?.cwd??process.cwd(),n);if(!h.existsSync(o))return{installSource:{...a,path:o}};let i=await y({localPath:o,baseUrl:r.baseUrl,token:r.token});return{installSource:{...a,path:o},uploadedArtifactId:i}}function M(e,r,a,n=0){let o=e.positionals?.[n]??e.flags?.out,i=`${"path"===r?"screenshot":"recording"}-${Date.now()}${a}`,s=o&&o.trim().length>0?o:i;return t.isAbsolute(s)?s:t.resolve(e.meta?.cwd??process.cwd(),s)}function k(e,r){let a=r.startsWith(".")?r:`.${r}`;return t.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${a}`)}async function N(e){let a;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await q(t,"http"))return t;throw new m("COMMAND_FAILED","Remote daemon is unavailable",{daemonBaseUrl:e.remoteBaseUrl,hint:"Verify AGENT_DEVICE_DAEMON_BASE_URL points to a reachable daemon with GET /health and POST /rpc."})}let i=x(e.paths.infoPath),s=n(),l=function(e,r=o()){try{let a=h.statSync(e),n=t.relative(r,e)||e;return`${n}:${a.size}:${Math.trunc(a.mtimeMs)}`}catch{return"unknown"}}((a=z()).useSrc?a.srcPath:a.distPath,a.root),d=!!i&&await q(i,e.transportPreference);if(i&&i.version===s&&i.codeSignature===l&&d)return i;i&&(i.version!==s||i.codeSignature!==l||!d)&&(await O(i),F(e.paths.infoPath)),function(e){let t=L(e);if(!t.hasLock||t.hasInfo)return;let a=C(e.lockPath);if(!a)return F(e.lockPath);r(a.pid,a.processStartTime)||F(e.lockPath)}(e.paths);let u=0;for(let t=1;t<=D;t+=1){await B(e);let r=await T(A,e);if(r)return r;if(await U(e.paths)){u+=1;continue}let a=L(e.paths);if(!(t<D))break;if(!a.hasInfo&&!a.hasLock){await R(150);continue}}let c=L(e.paths);throw new m("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:A,startupAttempts:D,lockRecoveryCount:u,metadataState:c,hint:function(e,t=f(process.env.AGENT_DEVICE_STATE_DIR)){return e.hasLock&&!e.hasInfo?`Detected ${t.lockPath} without ${t.infoPath}. If no agent-device daemon process is running, delete ${t.lockPath} and retry.`:e.hasLock&&e.hasInfo?`Daemon metadata may be stale. If no agent-device daemon process is running, delete ${t.infoPath} and ${t.lockPath}, then retry.`:`Daemon metadata is missing or stale. Delete ${t.infoPath} if present and retry.`}(c,e.paths)})}async function T(e,t){let r=Date.now();for(;Date.now()-r<e;){let e=x(t.paths.infoPath);if(e&&await q(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function R(e){await new Promise(t=>setTimeout(t,e))}async function U(e){let t=L(e);if(!t.hasLock||t.hasInfo)return!1;let a=C(e.lockPath);return a&&r(a.pid,a.processStartTime)&&await w(a.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:a.processStartTime}),F(e.lockPath),!0}async function O(e){await w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function x(e){let t=$(e);if(!t||"object"!=typeof t)return null;let r="string"==typeof t.token&&t.token.length>0?t.token:null;if(!r)return null;let a=Number.isInteger(t.port)&&Number(t.port)>0,n=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;if(!a&&!n)return null;let o=t.transport,i="string"==typeof t.version?t.version:void 0,s="string"==typeof t.codeSignature?t.codeSignature:void 0,l="string"==typeof t.processStartTime?t.processStartTime:void 0,d=Number.isInteger(t.pid)&&Number(t.pid)>0;return{token:r,port:a?Number(t.port):void 0,httpPort:n?Number(t.httpPort):void 0,transport:"socket"===o||"http"===o||"dual"===o?o:void 0,pid:d?Number(t.pid):0,version:i,codeSignature:s,processStartTime:l}}function C(e){let t=$(e);return t&&"object"==typeof t&&Number.isInteger(t.pid)&&Number(t.pid)>0?{pid:Number(t.pid),processStartTime:"string"==typeof t.processStartTime?t.processStartTime:void 0,startedAt:"number"==typeof t.startedAt?t.startedAt:void 0}:null}function L(e){return{hasInfo:h.existsSync(e.infoPath),hasLock:h.existsSync(e.lockPath)}}function $(e){if(!h.existsSync(e))return null;try{return JSON.parse(h.readFileSync(e,"utf8"))}catch{return null}}function F(e){try{h.existsSync(e)&&h.unlinkSync(e)}catch{}}async function q(e,t){var r;return"http"===G(e,t)?await function(e){let t=e.baseUrl?Y(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!t)return Promise.resolve(!1);let r=new URL(t),a="https:"===r.protocol?i:b,n=e.baseUrl?3e3:500;return new Promise(e=>{let t=a.request({protocol:r.protocol,host:r.hostname,port:r.port,path:r.pathname+r.search,method:"GET",timeout:n},t=>{t.resume(),e((t.statusCode??500)<500)});t.on("timeout",()=>{t.destroy(),e(!1)}),t.on("error",()=>{e(!1)}),t.end()})}(e):await ((r=e.port)?new Promise(e=>{let t=I.createConnection({host:"127.0.0.1",port:r},()=>{t.destroy(),e(!0)});t.on("error",()=>{e(!1)})}):Promise.resolve(!1))}async function B(e){let t=z(),r=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],n={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};a(process.execPath,r,{env:n})}function z(){let e=o(),r=t.join(e,"dist","src","daemon.js"),a=t.join(e,"src","daemon.ts"),n=h.existsSync(r),i=h.existsSync(a);if(!n&&!i)throw new m("COMMAND_FAILED","Daemon entry not found",{distPath:r,srcPath:a});return{root:e,distPath:r,srcPath:a,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!n&&i}}async function j(e,t,r){return"http"===G(e,r)?await J(e,t):await H(e,t)}function G(e,t){if(e.baseUrl){if("socket"===t)throw new m("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t||"socket"===t){var r=e,a=t;if(V(r,a))return a;throw new m("COMMAND_FAILED","http"===a?"Daemon HTTP endpoint is unavailable":"Daemon socket endpoint is unavailable")}let n=("socket"===e.transport||"dual"===e.transport?["socket","http"]:["http","socket"]).find(t=>V(e,t));if(n)return n;throw new m("COMMAND_FAILED","Daemon metadata has no reachable transport")}function V(e,t){return"http"===t?!!e.httpPort:!!e.port}async function H(e,t){let r=e.port;if(!r)throw new m("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((a,n)=>{let o=I.createConnection({host:"127.0.0.1",port:r},()=>{o.write(`${JSON.stringify(t)}
|
|
2
|
+
`)}),i=setTimeout(()=>{o.destroy();let r=K(),a=W(e,f(t.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR));c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:v,requestId:t.meta?.requestId,command:t.command,timedOutRunnerPidsTerminated:r.terminated,timedOutRunnerCleanupError:r.error,daemonPidReset:e.pid,daemonPidForceKilled:a.forcedKill}}),n(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:v,requestId:t.meta?.requestId,hint:"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},v),s="";o.setEncoding("utf8"),o.on("data",e=>{let r=(s+=e).indexOf("\n");if(-1===r)return;let l=s.slice(0,r).trim();if(l)try{let e=JSON.parse(l);o.end(),clearTimeout(i),a(e)}catch(e){clearTimeout(i),n(new m("COMMAND_FAILED","Invalid daemon response",{requestId:t.meta?.requestId,line:l},e instanceof Error?e:void 0))}}),o.on("error",e=>{clearTimeout(i),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:t.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),n(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:t.meta?.requestId,hint:"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e))})})}async function J(t,r){let a=t.baseUrl?new URL(Y(t.baseUrl,"rpc")):t.httpPort?new URL(`http://127.0.0.1:${t.httpPort}/rpc`):null;if(!a)throw new m("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let n=JSON.stringify({jsonrpc:"2.0",id:r.meta?.requestId??e(),method:"agent_device.command",params:r}),o={"content-type":"application/json","content-length":Buffer.byteLength(n)};return t.baseUrl&&t.token&&(o.authorization=`Bearer ${t.token}`,o["x-agent-device-token"]=t.token),await new Promise((e,s)=>{let l=f(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),d=("https:"===a.protocol?i:b).request({protocol:a.protocol,host:a.hostname,port:a.port,method:"POST",path:a.pathname+a.search,headers:o},a=>{let n="";a.setEncoding("utf8"),a.on("data",e=>{n+=e}),a.on("end",()=>{clearTimeout(u);try{let a=JSON.parse(n);if(a.error){let e=a.error.data??{};s(new m(String(e.code??"COMMAND_FAILED"),String(e.message??a.error.message??"Daemon RPC request failed"),{..."object"==typeof e.details&&e.details?e.details:{},hint:"string"==typeof e.hint?e.hint:void 0,diagnosticId:"string"==typeof e.diagnosticId?e.diagnosticId:void 0,logPath:"string"==typeof e.logPath?e.logPath:void 0,requestId:r.meta?.requestId}));return}if(!a.result||"object"!=typeof a.result)return void s(new m("COMMAND_FAILED","Invalid daemon RPC response",{requestId:r.meta?.requestId}));if(t.baseUrl&&a.result.ok)return void Q(t,r,a.result).then(e).catch(s);e(a.result)}catch(e){clearTimeout(u),s(new m("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:n},e instanceof Error?e:void 0))}})}),u=setTimeout(()=>{d.destroy();let e=X(t)?{terminated:0}:K(),a=X(t)?{forcedKill:!1}:W(t,l);c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:v,requestId:r.meta?.requestId,command:r.command,timedOutRunnerPidsTerminated:e.terminated,timedOutRunnerCleanupError:e.error,daemonPidReset:X(t)?void 0:t.pid,daemonPidForceKilled:X(t)?void 0:a.forcedKill,daemonBaseUrl:t.baseUrl}}),s(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:v,requestId:r.meta?.requestId,hint:X(t)?"Retry with --debug and verify the remote daemon URL, auth token, and remote host logs.":"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},v);d.on("error",e=>{clearTimeout(u),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:r.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),s(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:r.meta?.requestId,hint:X(t)?"Retry command. If this persists, verify the remote daemon URL, auth token, and remote host reachability.":"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e))}),d.write(n),d.end()})}function K(){let e=0;try{for(let t of _){let r=s("pkill",["-f",t],{allowFailure:!0});0===r.exitCode&&(e+=1)}return{terminated:e}}catch(t){return{terminated:e,error:t instanceof Error?t.message:String(t)}}}function W(e,t){let a=!1;try{r(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),a=!0)}catch{w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{F(t.infoPath),F(t.lockPath)}return{forcedKill:a}}function X(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function Y(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function Q(e,r,a){let n=Array.isArray(a.data?.artifacts)?a.data.artifacts:[];if(0===n.length||!e.baseUrl)return a;let o=a.data?{...a.data}:{},i=[];for(let a of n){if(!a||"object"!=typeof a||"string"!=typeof a.artifactId){i.push(a);continue}let n=function(e,r){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let a=e.fileName?.trim()||`${e.field}-${Date.now()}`;return t.resolve(r.meta?.cwd??process.cwd(),a)}(a,r);await Z({baseUrl:e.baseUrl,token:e.token,artifactId:a.artifactId,destinationPath:n,requestId:r.meta?.requestId}),o[a.field]=n,i.push({...a,localPath:n})}return o.artifacts=i,{ok:!0,data:o}}async function Z(e){var r,a;let n,o=new URL((r=e.baseUrl,a=e.artifactId,n=r.endsWith("/")?r:`${r}/`,new URL(`upload/${encodeURIComponent(a)}`,n).toString())),s="https:"===o.protocol?i:b;await h.promises.mkdir(t.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,r)=>{let a=!1,n=e.timeoutMs??v,i=n=>{if(!a){if(a=!0,clearTimeout(d),n)return void h.promises.rm(e.destinationPath,{force:!0}).finally(()=>r(n));t()}},l=s.request({protocol:o.protocol,host:o.hostname,port:o.port,method:"GET",path:o.pathname+o.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:r}))});return}let r=h.createWriteStream(e.destinationPath);r.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("aborted",()=>{i(new m("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),r.on("finish",()=>{r.close(()=>i())}),t.pipe(r)}),d=setTimeout(()=>{l.destroy(new m("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n}))},n);l.on("error",t=>{t instanceof m?i(t):i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n},t instanceof Error?t:void 0))}),l.end()})}function ee(e){return e.replace(/\/+$/,"")}function et(e){return"string"==typeof e&&e.trim()?ee(e.trim()):""}function er(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function ea(e,t,r){return u(e,{env:t,cwd:r})}function en(e){try{return h.accessSync(e,h.constants.F_OK),!0}catch{return!1}}function eo(e,t,r){if(null==e||""===e)return t;let a=Number.parseInt(String(e),10);return Number.isInteger(a)?Math.max(a,r):t}function ei(e,t){let r;return{platform:t,bundleUrl:((r=new URL(`${ee(e)}/index.bundle`)).searchParams.set("platform",t),r.searchParams.set("dev","true"),r.searchParams.set("minify","false"),r.toString())}}function es(e,t){return{platform:t,metroHost:er(e?.metro_host),metroPort:e?.metro_port,bundleUrl:er(e?.metro_bundle_url),launchUrl:er(e?.launch_url)}}function el(e){return`'${e.replace(/'/g,"'\"'\"'")}'`}async function ed(e){await new Promise(t=>setTimeout(t,e))}async function eu(e,t,r={}){try{let a=await fetch(e,{headers:r,signal:AbortSignal.timeout(t)});return{ok:a.ok,status:a.status,body:await a.text()}}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw Error(`Timed out fetching ${e} after ${t}ms`);throw r}}async function ec(e,t){try{let r=await eu(e,t);return r.ok&&r.body.includes("packager-status:running")}catch{return!1}}async function ep(e){var t,r,a;let n;try{n=await fetch(`${e.baseUrl}/api/metro/bridge`,{method:"POST",headers:(t=e.baseUrl,r=e.bearerToken,{Authorization:`Bearer ${r}`,"Content-Type":"application/json",...t.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({ios_runtime:e.runtime,timeout_ms:e.timeoutMs}),signal:AbortSignal.timeout(e.timeoutMs)})}catch(t){if(t instanceof Error&&"TimeoutError"===t.name)throw Error(`/api/metro/bridge timed out after ${e.timeoutMs}ms calling ${e.baseUrl}/api/metro/bridge`);throw t}let o=await n.text(),i=o?JSON.parse(o):{};if(!n.ok)throw Error(`/api/metro/bridge failed (${n.status}): ${JSON.stringify(i)}`);return{enabled:(a=i.data??i).enabled,baseUrl:a.base_url,statusUrl:a.status_url,bundleUrl:a.bundle_url,iosRuntime:es(a.ios_runtime,"ios"),androidRuntime:es(a.android_runtime,"android"),upstream:{bundleUrl:a.upstream.bundle_url,host:a.upstream.host,port:a.upstream.port,statusUrl:a.upstream.status_url},probe:{reachable:a.probe.reachable,statusCode:a.probe.status_code,latencyMs:a.probe.latency_ms,detail:a.probe.detail}}}async function em(e,t,r){let a=Date.now()+t;for(;Date.now()<a;){let t=Math.min(r,Math.max(a-Date.now(),1));if(await ec(e,t))return!0;let n=Math.min(500,Math.max(a-Date.now(),0));n>0&&await ed(n)}return!1}async function eh(e={}){let r=e.env??process.env,a=process.cwd(),n=ea(e.projectRoot??a,r,a),o=function(e,r){if("auto"!==r)return r;let a=function(e){let r=t.join(e,"package.json");if(!en(r))throw new m("INVALID_ARGS",`package.json not found at ${r}`);return JSON.parse(h.readFileSync(r,"utf8"))}(e);return"string"==typeof({...a.dependencies??{},...a.devDependencies??{}}).expo?"expo":"react-native"}(n,e.kind??"auto"),i=function(e,t){if(null==e||""===e)return 8081;let r=Number.parseInt(String(e),10);if(!Number.isInteger(r)||r<1||r>65535)throw new m("INVALID_ARGS",`Invalid Metro port: ${String(e)}. Use 1-65535.`);return r}(e.metroPort??8081,0),l=er(e.listenHost)??"0.0.0.0",d=er(e.statusHost)??"127.0.0.1",u=et(e.publicBaseUrl),c=eo(e.startupTimeoutMs,18e4,3e4),p=eo(e.probeTimeoutMs,1e4,1e3),f=e.reuseExisting??!0,I=e.installDependenciesIfNeeded??!0,g=e.runtimeFilePath?ea(e.runtimeFilePath,r,a):null,w=ea(e.logPath??t.join(n,".agent-device","metro.log"),r,a);if(!u)throw new m("INVALID_ARGS","metro prepare requires --public-base-url <url>.");let{proxyEnabled:b,proxyBaseUrl:y,proxyBearerToken:v}=function(e,t){if(e&&!t)throw new m("INVALID_ARGS","metro prepare requires proxy auth when --proxy-base-url is provided. Pass --bearer-token or set AGENT_DEVICE_PROXY_TOKEN.");if(!e&&t)throw new m("INVALID_ARGS","metro prepare requires --proxy-base-url when proxy auth is provided.");return{proxyEnabled:!!(e&&t),proxyBaseUrl:e,proxyBearerToken:t}}(et(e.proxyBaseUrl),er(e.proxyBearerToken)??""),A=I?function(e,r){if(function(e){try{return h.statSync(e).isDirectory()}catch{return!1}}(t.join(e,"node_modules")))return{installed:!1};let a=en(t.join(e,"pnpm-lock.yaml"))?{command:"pnpm",installArgs:["install"]}:en(t.join(e,"yarn.lock"))?{command:"yarn",installArgs:["install"]}:{command:"npm",installArgs:["install"]};return s(a.command,a.installArgs,{cwd:e,env:r}),{installed:!0,packageManager:a.command}}(n,r):{installed:!1},D=`http://${d}:${i}/status`,_=!1,P=!1,E=0;if(f&&await ec(D,p))P=!0;else if(_=!0,E=function(e,r,a,n,o,i){let l="expo"===r?{command:"npx",installArgs:["expo","start","--host","lan","--port",String(a)]}:{command:"npx",installArgs:["react-native","start","--host",n,"--port",String(a)]};h.mkdirSync(t.dirname(o),{recursive:!0});let d=[el(l.command),...l.installArgs.map(el)].join(" "),u=s("/bin/sh",["-c",`nohup ${d} >> ${el(o)} 2>&1 < /dev/null & echo $!`],{cwd:e,env:i}),c=Number.parseInt(u.stdout.trim(),10);if(!Number.isInteger(c)||c<=0)throw Error(`Failed to start Metro. Expected a child PID in stdout, got "${u.stdout.trim()}".`);return{pid:c}}(n,o,i,l,w,r).pid,!await em(D,c,p))throw Error(`Metro did not become ready at ${D} within ${c}ms. Check ${w}.`);let S=ei(u,"ios"),M=ei(u,"android"),k=null,N=null;if(b)try{k=await ep({baseUrl:y,bearerToken:v,runtime:{metro_bundle_url:S.bundleUrl},timeoutMs:p})}catch(e){N=e instanceof Error?e.message:String(e)}if(b&&(!k||!1===k.probe.reachable)){var T,R;let e;throw Error((T=N,R=k,e=[`Metro bridge is required for this run but could not be configured via ${y}/api/metro/bridge.`],T&&e.push(`bridgeError=${T}`),R?.probe.reachable===!1&&e.push(`bridgeProbe=${R.probe.detail||`unreachable (status ${R.probe.statusCode||0})`}`),e.join(" ")))}let U=k?.iosRuntime??S,O=k?.androidRuntime??M,x={projectRoot:n,kind:o,dependenciesInstalled:A.installed,packageManager:A.packageManager??null,started:_,reused:P,pid:E,logPath:w,statusUrl:D,runtimeFilePath:g,iosRuntime:U,androidRuntime:O,bridge:k};return g&&(h.mkdirSync(t.dirname(g),{recursive:!0}),h.writeFileSync(g,JSON.stringify(x,null,2))),x}function ef(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function eI(e,t,r){return{deviceId:t,deviceName:r,..."android"===e?{serial:t}:"ios"===e?{udid:t}:{}}}function eg(e,t={}){let r=t.includeAndroidSerial??!0;return{platform:e.platform,target:e.target,device:e.name,id:e.id,..."ios"===e.platform?{device_udid:e.ios?.udid??e.id,ios_simulator_device_set:e.ios?.simulatorSetPath??null}:{},..."android"===e.platform&&r?{serial:e.android?.serial??e.id}:{}}}function ew(e){return{name:e.name,...eg(e.device,{includeAndroidSerial:!1}),createdAt:e.createdAt}}function eb(e){return{platform:e.platform,id:e.id,name:e.name,kind:e.kind,target:e.target,..."boolean"==typeof e.booted?{booted:e.booted}:{}}}function ey(e){return{udid:e.udid,device:e.device,runtime:e.runtime,ios_simulator_device_set:e.iosSimulatorDeviceSet??null,created:e.created,booted:e.booted}}function ev(e){return{app:e.app,appPath:e.appPath,platform:e.platform,...e.appId?{appId:e.appId}:{},...e.bundleId?{bundleId:e.bundleId}:{},...e.package?{package:e.package}:{}}}function eA(e){return{launchTarget:e.launchTarget,...e.appName?{appName:e.appName}:{},...e.appId?{appId:e.appId}:{},...e.bundleId?{bundleId:e.bundleId}:{},...e.packageName?{package:e.packageName}:{},...e.installablePath?{installablePath:e.installablePath}:{},...e.archivePath?{archivePath:e.archivePath}:{},...e.materializationId?{materializationId:e.materializationId}:{},...e.materializationExpiresAt?{materializationExpiresAt:e.materializationExpiresAt}:{}}}function eD(e){return{session:e.session,...e.appName?{appName:e.appName}:{},...e.appBundleId?{appBundleId:e.appBundleId}:{},...e.startup?{startup:e.startup}:{},...e.runtime?{runtime:e.runtime}:{},...e.device?eg(e.device):{}}}function e_(e){return{session:e.session,...e.shutdown?{shutdown:e.shutdown}:{}}}function eP(e){return{nodes:e.nodes,truncated:e.truncated,...e.appName?{appName:e.appName}:{},...e.appBundleId?{appBundleId:e.appBundleId}:{}}}function eE(e,t){let r=eO(e,"bundleId"),a=eO(e,"package");return{app:eU(e,"app"),appPath:eU(e,"appPath"),platform:ex(e,"platform"),appId:r??a,bundleId:r,package:a,identifiers:ef({session:t,bundleId:r,packageName:a})}}function eS(e){var t;let r=eT(e),a=ex(r,"platform"),n=eU(r,"id"),o=eU(r,"name");return{platform:a,target:eC(r,"target"),kind:eL(r,t="kind",ez,`Daemon response has invalid "${t}".`),id:n,name:o,booted:"boolean"==typeof r.booted?r.booted:void 0,identifiers:eI(a,n,o),ios:"ios"===a?{udid:n}:void 0,android:"android"===a?{serial:n}:void 0}}function eM(e){var t;let r=eT(e),a=ex(r,"platform"),n=eU(r,"id"),o=eU(r,"name"),i=eC(r,"target"),s=eU(r,"device"),l={session:o,...eI(a,n,s)};return{name:o,createdAt:eL(r,t="createdAt",eq,`Daemon response is missing numeric "${t}".`),device:{platform:a,target:i,id:n,name:s,identifiers:l,ios:"ios"===a?{udid:n,simulatorSetPath:e$(r,"ios_simulator_device_set",eF)}:void 0,android:"android"===a?{serial:n}:void 0},identifiers:l}}function ek(e,t){return t??e??"default"}function eN(e){let t={};for(let[r,a]of Object.entries(e))void 0!==a&&(t[r]=a);return t}function eT(e){if(!eR(e))throw new m("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function eR(e){return"object"==typeof e&&null!==e}function eU(e,t){return eL(e,t,eF,`Daemon response is missing "${t}".`)}function eO(e,t){return eF(e[t])}function ex(e,t){return eL(e,t,eB,`Daemon response has invalid "${t}".`)}function eC(e,t){return ej(e[t])??"mobile"}function eL(e,t,r,a){let n=r(e[t]);if(void 0===n)throw new m("COMMAND_FAILED",a,{response:e});return n}function e$(e,t,r){let a=e[t];return null===a?null:r(a)}function eF(e){return"string"==typeof e&&e.length>0?e:void 0}function eq(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function eB(e){return"ios"===e||"macos"===e||"android"===e?e:void 0}function ez(e){return"simulator"===e||"emulator"===e||"device"===e?e:void 0}function ej(e){return"tv"===e||"mobile"===e||"desktop"===e?e:void 0}function eG(e={},t={}){let r=t.transport??P,a=async(t,a=[],n={})=>{let o={...e,...n},i=await r({session:ek(e.session,n.session),command:t,positionals:a,flags:eN({stateDir:o.stateDir,daemonBaseUrl:o.daemonBaseUrl,daemonAuthToken:o.daemonAuthToken,daemonTransport:o.daemonTransport,daemonServerMode:o.daemonServerMode,tenant:o.tenant,sessionIsolation:o.sessionIsolation,runId:o.runId,leaseId:o.leaseId,platform:o.platform,target:o.target,device:o.device,udid:o.udid,serial:o.serial,iosSimulatorDeviceSet:o.iosSimulatorDeviceSet,androidDeviceAllowlist:o.androidDeviceAllowlist,runtime:o.simulatorRuntimeId,boot:o.boot,reuseExisting:o.reuseExisting,activity:o.activity,relaunch:o.relaunch,shutdown:o.shutdown,saveScript:o.saveScript,noRecord:o.noRecord,metroHost:o.metroHost,metroPort:o.metroPort,bundleUrl:o.bundleUrl,launchUrl:o.launchUrl,snapshotInteractiveOnly:o.interactiveOnly,snapshotCompact:o.compact,snapshotDepth:o.depth,snapshotScope:o.scope,snapshotRaw:o.raw,verbose:o.debug}),runtime:o.runtime,meta:eN({requestId:o.requestId,cwd:o.cwd,debug:o.debug,lockPolicy:o.lockPolicy,lockPlatform:o.lockPlatform,tenantId:o.tenant,runId:o.runId,leaseId:o.leaseId,sessionIsolation:o.sessionIsolation,installSource:o.installSource,retainMaterializedPaths:o.retainMaterializedPaths,materializedPathRetentionMs:o.materializedPathRetentionMs,materializationId:o.materializationId})});if(!i.ok)throw new m(i.error.code,i.error.message,{...i.error.details??{},hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath});return i.data??{}},n=async(e={})=>{let t=await a("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(eM)};return{devices:{list:async(e={})=>{let t=await a("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(eS)}},sessions:{list:async(e={})=>await n(e),close:async(t={})=>{let r=ek(e.session,t.session),n=(await a("close",[],t)).shutdown;return{session:r,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},simulators:{ensure:async e=>{let{runtime:t,...r}=e,n=await a("ensure-simulator",[],{...r,simulatorRuntimeId:t}),o=eU(n,"udid"),i=eU(n,"device");return{udid:o,device:i,runtime:eU(n,"runtime"),created:!0===n.created,booted:!0===n.booted,iosSimulatorDeviceSet:e$(n,"ios_simulator_device_set",eF),identifiers:{deviceId:o,deviceName:i,udid:o}}}},apps:{install:async t=>eE(await a("install",[t.app,t.appPath],t),ek(e.session,t.session)),reinstall:async t=>eE(await a("reinstall",[t.app,t.appPath],t),ek(e.session,t.session)),installFromSource:async t=>(function(e,t){let r=eO(e,"bundleId"),a=eO(e,"packageName"),n=r??a??eO(e,"appId"),o=eO(e,"launchTarget")??a??r??n;if(!o)throw new m("COMMAND_FAILED",'Daemon response is missing "launchTarget".',{response:e});return{appName:eO(e,"appName"),appId:n,bundleId:r,packageName:a,launchTarget:o,installablePath:eO(e,"installablePath"),archivePath:eO(e,"archivePath"),materializationId:eO(e,"materializationId"),materializationExpiresAt:eO(e,"materializationExpiresAt"),identifiers:ef({session:t,bundleId:r,packageName:a,appId:n})}})(await a("install_source",[],{...t,installSource:t.source,retainMaterializedPaths:t.retainPaths,materializedPathRetentionMs:t.retentionMs}),ek(e.session,t.session)),open:async t=>{let r=ek(e.session,t.session),n=t.url?[t.app,t.url]:[t.app],o=await a("open",n,t),i=function(e){let t=e.platform,r=eO(e,"id"),a=eO(e,"device");if("ios"!==t&&"macos"!==t&&"android"!==t||!r||!a)return;let n=eC(e,"target"),o=eI(t,r,a);return{platform:t,target:n,id:r,name:a,identifiers:o,ios:"ios"===t?{udid:eO(e,"device_udid")??r,simulatorSetPath:e$(e,"ios_simulator_device_set",eF)}:void 0,android:"android"===t?{serial:eO(e,"serial")??r}:void 0}}(o),s=eO(o,"appBundleId");return{session:r,appName:eO(o,"appName"),appBundleId:s,appId:s,startup:function(e){if(eR(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:eO(e,"appTarget"),appBundleId:eO(e,"appBundleId")}}(o.startup),runtime:function(e){if(!eR(e))return;let t=e.platform,r=eO(e,"metroHost"),a="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:r,metroPort:a,bundleUrl:eO(e,"bundleUrl"),launchUrl:eO(e,"launchUrl")}}(o.runtime),device:i,identifiers:{session:r,deviceId:i?.id,deviceName:i?.name,udid:i?.ios?.udid,serial:i?.android?.serial,appId:s,appBundleId:s}}},close:async(t={})=>{let r=ek(e.session,t.session),n=(await a("close",t.app?[t.app]:[],t)).shutdown;return{session:r,closedApp:t.app,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},materializations:{release:async e=>{var t;return{released:!0===(t=await a("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:eU(t,"materializationId"),identifiers:{}}}},metro:{prepare:async t=>await eh({projectRoot:t.projectRoot??e.cwd,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.bearerToken,metroPort:t.port,listenHost:t.listenHost,statusHost:t.statusHost,startupTimeoutMs:t.startupTimeoutMs,probeTimeoutMs:t.probeTimeoutMs,reuseExisting:t.reuseExisting,installDependenciesIfNeeded:t.installDependenciesIfNeeded,runtimeFilePath:t.runtimeFilePath,logPath:t.logPath})},capture:{snapshot:async(t={})=>{var r;let n=ek(e.session,t.session),o=await a("snapshot",[],t),i=eO(o,"appBundleId");return{nodes:Array.isArray(r=o.nodes)?r:[],truncated:!0===o.truncated,appName:eO(o,"appName"),appBundleId:i,identifiers:{session:n,appId:i,appBundleId:i}}},screenshot:async(t={})=>{let r=ek(e.session,t.session);return{path:eU(await a("screenshot",t.path?[t.path]:[],t),"path"),identifiers:{session:r}}}}}}export{eG as createAgentDeviceClient,P as sendToDaemon,e_ as serializeCloseResult,ev as serializeDeployResult,eb as serializeDevice,ey as serializeEnsureSimulatorResult,eA as serializeInstallFromSourceResult,eD as serializeOpenResult,ew as serializeSessionListEntry,eP as serializeSnapshotResult};
|