agent-device 0.15.1 → 0.16.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.
Files changed (45) hide show
  1. package/README.md +51 -155
  2. package/android-multitouch-helper/README.md +41 -0
  3. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.0.apk +0 -0
  4. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.0.apk.sha256 +1 -0
  5. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.0.manifest.json +10 -0
  6. package/android-snapshot-helper/README.md +2 -0
  7. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.0.apk +0 -0
  8. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.0.apk.sha256 +1 -0
  9. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.15.1.manifest.json → agent-device-android-snapshot-helper-0.16.0.manifest.json} +6 -6
  10. package/dist/src/1231.js +1 -1
  11. package/dist/src/1769.js +7 -7
  12. package/dist/src/2099.js +1 -0
  13. package/dist/src/221.js +4 -4
  14. package/dist/src/3622.js +3 -0
  15. package/dist/src/7519.js +1 -0
  16. package/dist/src/7556.js +1 -1
  17. package/dist/src/89.js +1 -0
  18. package/dist/src/940.js +1 -1
  19. package/dist/src/9542.js +2 -2
  20. package/dist/src/9639.js +2 -2
  21. package/dist/src/989.js +1 -1
  22. package/dist/src/android-adb.d.ts +26 -0
  23. package/dist/src/android-adb.js +1 -1
  24. package/dist/src/android-snapshot-helper.d.ts +30 -0
  25. package/dist/src/batch.d.ts +9 -9
  26. package/dist/src/cli.js +494 -76
  27. package/dist/src/index.d.ts +48 -5
  28. package/dist/src/internal/daemon.js +69 -44
  29. package/dist/src/server.js +2 -2
  30. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
  31. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +19 -0
  32. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +297 -0
  33. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +144 -5
  34. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +333 -23
  35. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +3 -1
  36. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +8 -0
  37. package/package.json +9 -3
  38. package/server.json +2 -2
  39. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.15.1.apk +0 -0
  40. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.15.1.apk.sha256 +0 -1
  41. package/dist/src/1393.js +0 -1
  42. package/dist/src/2151.js +0 -434
  43. package/dist/src/3572.js +0 -1
  44. package/dist/src/7599.js +0 -3
  45. package/dist/src/9671.js +0 -1
package/README.md CHANGED
@@ -12,34 +12,40 @@
12
12
  [![CI](https://github.com/callstackincubator/agent-device/actions/workflows/ci.yml/badge.svg)](https://github.com/callstackincubator/agent-device/actions/workflows/ci.yml)
13
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE)
14
14
 
15
- Device automation CLI for AI agents. Mobile, TV, and desktop apps.
15
+ Mobile app verification for AI agents.
16
16
 
17
- `agent-device` lets coding agents run real apps, inspect UI state, interact with visible elements, and collect debugging evidence through one CLI.
17
+ A device automation CLI for real apps on iOS, Android, TV, and desktop. Agents get token-efficient snapshots, semantic refs, and evidence captured only when needed.
18
18
 
19
- It is built around token-efficient accessibility snapshots, not pixel-first screenshots. Agents read compact UI trees, locate elements through refs like `@e3`, perform touch and text actions, and capture screenshots, video, logs, network, CPU/memory/perf, crash-related logs, and React profiles only when evidence is needed.
19
+ `agent-device` lets coding agents open apps, inspect the current UI, interact with visible elements, and collect debugging evidence through one CLI. Use it when an agent needs to verify what actually happens on a device, not just reason about code.
20
20
 
21
- Agents can ingest the current docs from [llms-full.txt](https://incubator.callstack.com/agent-device/llms-full.txt). The installed CLI help remains authoritative for exact command syntax.
21
+ If you know Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser), `agent-device` is the same idea for mobile, TV, and desktop apps.
22
22
 
23
- ## Agentic QA And Development
23
+ It works with native iOS and Android apps, plus apps built with Expo, Flutter, and React Native, as long as the target can run on a supported device, simulator, emulator, or desktop environment.
24
24
 
25
- - **Quality Assurance**: dogfood flows, validate PR builds, check accessibility coverage, and turn stable explorations into `.ad` e2e tests.
26
- - **Development**: build from specs, inspect real runtime behavior, and iterate until the UI matches the work.
25
+ ![agent-device demo showing Codex using agent-device to create a new contact in the iOS Contacts app from a simple prompt](./website/docs/public/agent-device-contacts.gif)
27
26
 
28
- `agent-device` closes the agentic development loop: agents can write code, run the real app, verify the UI end-to-end, collect screenshots/videos/logs/perf evidence, and feed bugs, crashes, or performance findings back into the next fix iteration before a human reviews the PR.
27
+ ## Capabilities
29
28
 
30
- ![Sketch showing agent-device as the live app verification layer in the agentic development loop](./website/docs/public/agentic-development-loop.svg)
29
+ - **Inspect** real app UI through compact accessibility snapshots, interactive refs like `@e3`, selectors, and React Native component trees.
30
+ - **Interact** by opening apps, tapping, typing, scrolling, performing gestures, waiting, asserting state, handling alerts, and closing sessions.
31
+ - **Capture evidence** with screenshots, videos, logs, traces, network traffic, performance samples, crash context, and React profiles.
32
+ - **Replay workflows** by recording `.ad` scripts for local runs, CI, and repeatable e2e checks.
33
+ - **Run across platforms** with iOS Simulator automation, Android Emulator automation, physical devices, tvOS, Android TV, macOS, Linux, and desktop app automation, so agents can see and feel the app they work on.
31
34
 
32
- If you know Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser), this is the same idea for apps and devices.
35
+ ## Use Cases
33
36
 
34
- Use it for AI mobile testing, AI QA for React Native and Expo apps, iOS Simulator automation, Android Emulator automation, tvOS/Android TV checks, and desktop app verification from coding agents. Humans install and configure `agent-device`; agents run the workflows.
37
+ - Verify mobile changes on real devices, simulators, and emulators before review or merge.
38
+ - Give AI coding agents a real app feedback loop while they implement features.
39
+ - Debug regressions with screenshots, logs, traces, network evidence, and crash context.
40
+ - Profile performance issues with CPU/memory samples and React render profiles when needed.
41
+ - Turn exploratory app interactions into replayable e2e checks for CI.
42
+ - Use one agent workflow across native iOS, Android, Expo, Flutter, React Native, TV, and desktop apps.
35
43
 
36
- ![agent-device demo showing Codex using agent-device to create a new contact in the iOS Contacts app from a simple prompt](./website/docs/public/agent-device-contacts.gif)
37
-
38
- Demo: Codex uses `agent-device` to inspect iOS Contacts through accessibility snapshots, interact with visible UI, and create a contact from a simple prompt.
44
+ ![Sketch showing agent-device as the live app verification layer in the agentic development loop](./website/docs/public/agentic-development-loop.svg)
39
45
 
40
46
  ## Quick Start
41
47
 
42
- Install the CLI first:
48
+ Install the CLI:
43
49
 
44
50
  ```bash
45
51
  npm install -g agent-device@latest
@@ -47,74 +53,14 @@ agent-device --version
47
53
  agent-device help workflow
48
54
  ```
49
55
 
50
- The CLI help is the source of truth for agents and is shipped with the installed version. Skills are optional but recommended when your agent runtime supports them: they auto-route device, React DevTools, and dogfood tasks to the right `agent-device help <topic>` page and verify the CLI is new enough before acting.
51
-
52
- If you install skills separately, keep the CLI on `agent-device >= 0.14.0`. Older CLIs do not include the workflow help topics that the router skills expect.
53
-
54
- ### AI Agent Entry Points
55
-
56
- - **Agent + terminal**: in Cursor, Codex, Claude Code, Windsurf, and similar clients, run `agent-device` in the integrated terminal. Start planning with `agent-device help workflow`; CLI help is authoritative.
57
- - **Skills or rules**: install the skill with `npx skills add callstackincubator/agent-device`, use the bundled [agent-device skill](skills/agent-device/SKILL.md), or mirror it as a thin project rule, so the agent checks the installed version and reads `agent-device help workflow` before acting. Use `agent-device help react-native` for React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence.
58
- - **MCP router**: use `agent-device mcp` when an MCP-aware client needs to discover the CLI package, install command, version check, and first help command. MCP is discovery-only; device automation still runs through terminal CLI commands.
59
-
60
- For client-specific setup, see [AI Agent Setup](https://incubator.callstack.com/agent-device/docs/agent-setup). For agent-readable docs, use [llms-full.txt](https://incubator.callstack.com/agent-device/llms-full.txt).
61
-
62
- ### MCP Router
63
-
64
- `agent-device` ships an official stdio MCP router for discovery-oriented clients. It exposes only a `status` tool that returns structured CLI handoff guidance: npm package name, installed version, CLI command name, install command, verify command, starting help command, and an explicit note that automation happens through the CLI.
65
-
66
- MCP clients must not use this server as a device automation surface or generic shell runner. If the CLI is missing, agents should ask a human before installing or updating packages, then verify with `agent-device --version` and start with `agent-device help workflow`.
67
-
68
- Paste one of these into clients that accept `mcpServers`, such as Cursor project `.cursor/mcp.json` or user-level MCP settings.
69
-
70
- <details>
71
- <summary>Global install MCP config</summary>
72
-
73
- ```json
74
- {
75
- "mcpServers": {
76
- "agent-device": {
77
- "command": "agent-device",
78
- "args": ["mcp"]
79
- }
80
- }
81
- }
82
- ```
83
-
84
- </details>
85
-
86
- <details>
87
- <summary>No global install MCP config</summary>
88
-
89
- ```json
90
- {
91
- "mcpServers": {
92
- "agent-device": {
93
- "command": "npx",
94
- "args": ["-y", "agent-device@<reviewed-version>", "mcp"]
95
- }
96
- }
97
- }
98
- ```
99
-
100
- </details>
101
-
102
- Registry metadata uses MCP name `io.github.callstackincubator/agent-device`, npm package `agent-device`, stdio transport, `mcpName` package verification, `server.json`, and `smithery.yaml`.
103
-
104
- ```bash
105
- npm install -g agent-device@latest
106
- agent-device --version
107
- agent-device help
108
- ```
109
-
110
- `agent-device` performs a lightweight background upgrade check for interactive CLI runs and, when a newer package is available, suggests a global reinstall command. Updating the package also refreshes the bundled `skills/` shipped with the CLI.
56
+ The installed CLI help is the source of truth for agents. Start with `agent-device help workflow`, then follow the topic-specific help when a task needs dogfooding, debugging, replay, or React Native profiling.
111
57
 
112
- Prerequisites: Node.js 22+, Xcode for iOS/tvOS/macOS targets, Android SDK + ADB for Android, and macOS Accessibility permission for desktop automation. See [Installation](https://incubator.callstack.com/agent-device/docs/installation).
58
+ Prerequisites depend on the target platform: Node.js 22+, Xcode for iOS/tvOS/macOS targets, Android SDK + ADB for Android, and macOS Accessibility permission for desktop automation. See [Installation](https://incubator.callstack.com/agent-device/docs/installation) for platform setup.
113
59
 
114
- Try the loop.
60
+ Try the basic loop:
115
61
 
116
62
  ```bash
117
- # Find the app.
63
+ # Find an app.
118
64
  agent-device apps --platform ios
119
65
  agent-device apps --platform android
120
66
 
@@ -127,106 +73,56 @@ agent-device snapshot -i
127
73
  # @e2 [button] "Sign In"
128
74
  # @e3 [text-field] "Email"
129
75
 
130
- # Act, capture a screenshot, and close.
131
- agent-device fill @e3 "test"
76
+ # Act, capture evidence, and close.
77
+ agent-device fill @e3 "test@example.com"
132
78
  agent-device screenshot ./artifacts/settings.png
133
79
  agent-device close
134
80
  ```
135
81
 
136
- Snapshots assign refs like `@e1`, `@e2`, and `@e3` to current-screen elements. Refs from the default snapshot are immediately actionable; for hidden content, scroll and re-snapshot.
82
+ Snapshots assign refs like `@e1`, `@e2`, and `@e3` to elements on the current screen. Refs from the latest snapshot are immediately actionable; after scrolling or changing screens, take a fresh snapshot.
137
83
 
138
- ### First 5 Minutes: Expo Test App
84
+ ## Next Steps
139
85
 
140
- Use the bundled Expo fixture when you want a concrete first agent run with setup checks, screenshots, replay, and performance evidence. This path requires a repo checkout because `examples/test-app` and the `pnpm test-app:*` scripts are not included in the published npm package.
141
-
142
- ```bash
143
- git clone https://github.com/callstackincubator/agent-device.git
144
- cd agent-device
145
- ```
146
-
147
- First terminal:
148
-
149
- ```bash
150
- pnpm test-app:install
151
- cd examples/test-app
152
- npx expo-doctor@latest
153
- cd ../..
154
- pnpm test-app:ios
155
- # or: pnpm test-app:android
156
- ```
157
-
158
- Then give your agent this prompt:
159
-
160
- ```text
161
- Use agent-device to dogfood the bundled Expo app and produce an evidence-backed report.
162
-
163
- Setup:
164
- - Read `agent-device help workflow`, `agent-device help dogfood`, `agent-device help debugging`, and `agent-device help react-devtools` before planning commands.
165
- - Confirm the test app setup commands were run: `pnpm test-app:install`, `cd examples/test-app && npx expo-doctor@latest`, then `pnpm test-app:ios` or `pnpm test-app:android`.
166
- - If Metro prints an Expo URL, prefer opening the shell with that URL. On iOS use `agent-device open "Expo Go" <url> --platform ios`; on Android use the visible Expo/dev-client target or URL. Confirm the app UI with `snapshot -i`.
167
-
168
- Run:
169
- - Create `./dogfood-output/screenshots`, `./dogfood-output/videos`, `./dogfood-output/traces`, `./dogfood-output/perf`, and `./dogfood-output/replays`.
170
- - Open a named session `expo-qa` and save a replay script to `./dogfood-output/replays/expo-test.ad`.
171
- - Use command shapes like `agent-device --session expo-qa open "Expo Go" <url> --platform ios --save-script ./dogfood-output/replays/expo-test.ad`, `agent-device --session expo-qa screenshot ./dogfood-output/screenshots/home.png`, `agent-device --session expo-qa perf --json > ./dogfood-output/perf/baseline.json`, and `agent-device --session expo-qa record start ./dogfood-output/videos/checkout.mp4`.
172
- - Capture a baseline `snapshot -i`, screenshot, and `perf --json` sample.
173
- - Exercise Home, Catalog, product detail, Checkout, and Settings. Re-snapshot after each mutation and use refs/selectors from fresh snapshots.
174
- - Capture at least one overlay-ref screenshot, one normal screenshot, one short video recording for a meaningful flow, logs marks around any issue, and trace output if a runtime symptom needs diagnostics.
175
- - Run focused performance checks: compare `perf --json` before and after a navigation or form flow; if React DevTools connects, use one bounded first-pass profile survey (`slow --limit 5`, `rerenders --limit 5`, and `timeline --limit 20` only when timing matters), then drill into a specific `@c` ref with `profile report`. If it cannot connect, include the status and continue.
176
- - Close the session so the `.ad` replay is written.
177
-
178
- Report:
179
- - Write `./dogfood-output/report.md`.
180
- - Link every screenshot, video, trace, log path, replay file, and performance artifact you used.
181
- - Include setup results, platform/device, Expo doctor outcome, coverage, severity counts, findings with repro commands, and a short performance section summarizing startup/CPU/memory/frame-health or React profile findings.
182
- - If no issues are found, report covered flows and residual risk instead of claiming the app is bug-free.
183
- ```
86
+ - **Set up your agent**: run the CLI from Cursor, Codex, Claude Code, Windsurf, or another agent terminal. For skills, rules, direct MCP tools, and client-specific setup, see [AI Agent Setup](https://incubator.callstack.com/agent-device/docs/agent-setup).
87
+ - **Try the sample app**: clone the repo and run the bundled Expo fixture when you want a guided first dogfood run with screenshots, replay, and performance evidence. See [Quick Start](https://incubator.callstack.com/agent-device/docs/quick-start).
88
+ - **Go deeper**: use [Commands](https://incubator.callstack.com/agent-device/docs/commands), [Replay & E2E](https://incubator.callstack.com/agent-device/docs/replay-e2e), and [Debugging & Profiling](https://incubator.callstack.com/agent-device/docs/debugging-profiling) for production workflows.
184
89
 
185
90
  ## Where To Run agent-device
186
91
 
187
92
  | Path | Best for | Start with |
188
93
  | --- | --- | --- |
189
94
  | Local | Exploration, debugging, and development loops on simulators, emulators, physical devices, macOS apps, and Linux desktop targets. | Follow the Quick Start. |
190
- | CI/CD | Automated PR and merge validation with replay scripts and captured artifacts. | Start with the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml). GitHub Actions template coming soon. |
95
+ | CI/CD | Automated PR and merge validation with replay scripts and captured artifacts. | Try the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml). GitHub Actions template coming soon. |
191
96
  | Cloud / remote execution | Linux runners, managed devices, and remote execution. | Use [Agent Device Cloud](https://agent-device.dev/cloud), see [Commands](https://incubator.callstack.com/agent-device/docs/commands) for remote profiles, or [contact Callstack](mailto:hello@callstack.com) for team-scale QA. |
192
97
 
193
- ## Capabilities
194
-
195
- - **Platforms**: iOS, Android, tvOS, Android TV, macOS, and Linux. Real devices and simulators are supported.
196
- - **Agent-native UI model**: token-efficient accessibility snapshots, current-screen refs for exploration, selectors for durable replay, and skill-tested workflow guidance.
197
- - **Capture and debug**: screenshots, video, logs, network traffic, CPU/memory/performance data, crash-related logs, accessibility snapshots, and React render profiles.
198
- - **Produce**: replayable `.ad` scripts (recorded replay files that run locally or in CI), e2e test runs, snapshot and screenshot diffs, and debugging artifacts.
199
- - **React Native and Expo**: component tree inspection, props/state/hooks, and render profiling.
200
- - **MCP boundary**: discovery and help over MCP; app/device control through the CLI for explicit, auditable commands.
201
- - **License**: MIT. Free to use.
202
-
203
98
  ## How It Works
204
99
 
205
- `agent-device` runs session-aware commands through platform backends: XCTest for iOS and tvOS, ADB plus the Android snapshot helper for Android, a local helper for macOS desktop automation, and AT-SPI for Linux desktop targets. See [Introduction](https://incubator.callstack.com/agent-device/docs/introduction) and [Commands](https://incubator.callstack.com/agent-device/docs/commands) for platform details.
100
+ `agent-device` runs session-aware commands through platform backends: XCTest for iOS and tvOS, ADB plus the Android snapshot helper for Android, a local helper for macOS desktop automation, and AT-SPI for Linux desktop targets.
206
101
 
207
102
  Node consumers can use the typed client and public subpaths for bridge integrations. `agent-device/android-adb` exposes the Android ADB provider contract, logcat/clipboard/keyboard/app helpers, and port reverse management.
208
103
 
104
+ ## FAQ
105
+
106
+ ### What is agent-device?
107
+
108
+ `agent-device` is a device automation CLI for AI mobile app testing. It lets AI agents verify real apps on iOS, Android, TV, desktop, simulators, emulators, and physical devices.
109
+
110
+ ### Does it work with React Native, Expo, Flutter, and native apps?
111
+
112
+ Yes. `agent-device` works with native iOS and Android apps, Expo apps, Flutter apps, React Native apps, TV apps, and desktop apps that run on supported targets.
113
+
114
+ ### How is it different from Appium, Detox, or Maestro?
115
+
116
+ Appium, Detox, and Maestro are traditional mobile automation frameworks. `agent-device` is optimized for AI agents that need to inspect app state, interact semantically, capture evidence, debug, profile, and turn useful explorations into replayable checks.
117
+
209
118
  ## Used By
210
119
 
211
120
  Used by teams and developers at Callstack, Expensify, Shopify, Kindred, Total Wine & More, LegendList, HerLyfe, App & Flow, and more.
212
121
 
213
122
  ## Documentation
214
123
 
215
- - [Installation](https://incubator.callstack.com/agent-device/docs/installation)
216
- - [AI Agent Setup](https://incubator.callstack.com/agent-device/docs/agent-setup)
217
- - [Typed Client](https://incubator.callstack.com/agent-device/docs/client-api)
218
- - [Commands](https://incubator.callstack.com/agent-device/docs/commands)
219
- - [Replay & E2E](https://incubator.callstack.com/agent-device/docs/replay-e2e)
220
- - [Security & Trust](https://incubator.callstack.com/agent-device/docs/security-trust)
221
- - [Known limitations](https://incubator.callstack.com/agent-device/docs/known-limitations)
222
- - [llms-full.txt](https://incubator.callstack.com/agent-device/llms-full.txt)
223
-
224
- Agent integration:
225
-
226
- - [agent-device skill](skills/agent-device/SKILL.md)
227
- - [dogfood skill](skills/dogfood/SKILL.md)
228
- - MCP router: `agent-device mcp`
229
- - [agent-device skill on ClawHub](https://clawhub.ai/okwasniewski/agent-device)
124
+ - [Docs](https://incubator.callstack.com/agent-device/)
125
+ - [Agent-readable docs](https://incubator.callstack.com/agent-device/llms-full.txt)
230
126
 
231
127
  ## Contributing
232
128
 
@@ -234,4 +130,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
234
130
 
235
131
  ## Made at Callstack
236
132
 
237
- agent-device is open source and MIT licensed. Visit [agent-device.dev](https://agent-device.dev/), try the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml), read the [incubator docs](https://incubator.callstack.com/agent-device/), or contact us at hello@callstack.com.
133
+ `agent-device` is open source and MIT licensed. Visit [agent-device.dev](https://agent-device.dev/), try the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml), read the [incubator docs](https://incubator.callstack.com/agent-device/), or contact us at hello@callstack.com.
@@ -0,0 +1,41 @@
1
+ # Android MultiTouch Helper
2
+
3
+ Small instrumentation APK used to inject Android two-pointer gestures through
4
+ `UiAutomation.injectInputEvent`. The helper accepts a compact base64 JSON payload so local ADB,
5
+ remote ADB tunnels, and remote providers that allow `adb install -t` plus `am instrument` can use
6
+ the same contract.
7
+
8
+ The helper is separate from `android-snapshot-helper` because the payload and output protocol are
9
+ gesture-specific. The install/version/cache lifecycle should stay aligned with the snapshot helper.
10
+
11
+ ## Build
12
+
13
+ ```sh
14
+ VERSION="$(node -p 'require("./package.json").version')"
15
+ sh ./scripts/build-android-multitouch-helper.sh "$VERSION" .tmp/android-multitouch-helper
16
+ ```
17
+
18
+ ## Run
19
+
20
+ ```sh
21
+ PAYLOAD="$(printf '%s' '{"kind":"transform","x":672,"y":1500,"dx":80,"dy":-40,"scale":1.8,"degrees":35,"durationMs":700}' | base64)"
22
+ adb install -r -t ".tmp/android-multitouch-helper/agent-device-android-multitouch-helper-$VERSION.apk"
23
+ adb shell am instrument -w \
24
+ -e payloadBase64 "$PAYLOAD" \
25
+ com.callstack.agentdevice.multitouchhelper/.MultiTouchInstrumentation
26
+ ```
27
+
28
+ ## Output Contract
29
+
30
+ The APK emits instrumentation result records using
31
+ `agentDeviceProtocol=android-multitouch-helper-v1`.
32
+
33
+ Successful results include:
34
+
35
+ - `ok=true`
36
+ - `helperApiVersion=1`
37
+ - `kind` (`pinch`, `rotate`, or `transform`)
38
+ - `injectedEvents`
39
+ - `elapsedMs`
40
+
41
+ Failures return `ok=false`, `errorType`, and `message`.
@@ -0,0 +1 @@
1
+ 5f6a857083f2c71e12bbf6a6fdc2b25ce4a548333e8706b74a4f0e439fa4248d agent-device-android-multitouch-helper-0.16.0.apk
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "android-multitouch-helper",
3
+ "version": "0.16.0",
4
+ "assetName": "agent-device-android-multitouch-helper-0.16.0.apk",
5
+ "sha256": "5f6a857083f2c71e12bbf6a6fdc2b25ce4a548333e8706b74a4f0e439fa4248d",
6
+ "packageName": "com.callstack.agentdevice.multitouchhelper",
7
+ "versionCode": 16000,
8
+ "instrumentationRunner": "com.callstack.agentdevice.multitouchhelper/.MultiTouchInstrumentation",
9
+ "statusProtocol": "android-multitouch-helper-v1"
10
+ }
@@ -31,6 +31,7 @@ VERSION="$(node -p 'require("./package.json").version')"
31
31
  adb install -r -t ".tmp/android-snapshot-helper/agent-device-android-snapshot-helper-$VERSION.apk"
32
32
  adb shell am instrument -w \
33
33
  -e waitForIdleTimeoutMs 500 \
34
+ -e waitForIdleQuietMs 100 \
34
35
  -e timeoutMs 8000 \
35
36
  -e maxDepth 128 \
36
37
  -e maxNodes 5000 \
@@ -59,6 +60,7 @@ The final instrumentation result includes:
59
60
  - `ok=true`
60
61
  - `helperApiVersion=1`
61
62
  - `waitForIdleTimeoutMs`
63
+ - `waitForIdleQuietMs`
62
64
  - `timeoutMs`
63
65
  - `maxDepth`
64
66
  - `maxNodes`
@@ -0,0 +1 @@
1
+ e023c8211025186424fba7fce9f03f502d64d5f7cc2a205499e3ad03073ac21f agent-device-android-snapshot-helper-0.16.0.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.15.1",
4
- "releaseTag": "v0.15.1",
5
- "assetName": "agent-device-android-snapshot-helper-0.15.1.apk",
3
+ "version": "0.16.0",
4
+ "releaseTag": "v0.16.0",
5
+ "assetName": "agent-device-android-snapshot-helper-0.16.0.apk",
6
6
  "apkUrl": null,
7
- "sha256": "9d322b9af0fdcd0dd73e8c5123d29a30849d26b71e5b4adc077037f5bb0db987",
8
- "checksumName": "agent-device-android-snapshot-helper-0.15.1.apk.sha256",
7
+ "sha256": "e023c8211025186424fba7fce9f03f502d64d5f7cc2a205499e3ad03073ac21f",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.16.0.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 15001,
10
+ "versionCode": 16000,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
package/dist/src/1231.js CHANGED
@@ -1 +1 @@
1
- import{asAppError as t,AppError as e}from"./9152.js";let r=100,a=new Set(["batch","replay"]),o=new Set(["command","positionals","flags","runtime"]),n=["platform","target","device","udid","serial","verbose","out"];async function s(e,a,o){let n=e.flags,s=n?.batchOnError??"stop";if("stop"!==s)return p("INVALID_ARGS",`Unsupported batch on-error mode: ${s}.`);let i=n?.batchMaxSteps??r;if(!Number.isInteger(i)||i<1||i>1e3)return p("INVALID_ARGS",`Invalid batch max-steps: ${String(n?.batchMaxSteps)}`);try{let t=l(n?.batchSteps,i),r=Date.now(),s=[];for(let r=0;r<t.length;r+=1){let n=t[r],i=await m(e,a,n,o,r+1);if(!i.ok)return{ok:!1,error:{code:i.error.code,message:`Batch failed at step ${i.step} (${n.command}): ${i.error.message}`,hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath,details:{...i.error.details??{},step:i.step,command:n.command,positionals:n.positionals,executed:r,total:t.length,partialResults:s}}};s.push(i.result)}return{ok:!0,data:{total:t.length,executed:t.length,totalDurationMs:Date.now()-r,results:s}}}catch(r){let e=t(r);return p(e.code,e.message,e.details)}}function i(t){let r;try{r=JSON.parse(t)}catch{throw new e("INVALID_ARGS","Batch steps must be valid JSON.")}if(!Array.isArray(r)||0===r.length)throw new e("INVALID_ARGS","Batch steps must be a non-empty JSON array.");return r}function l(t,r){if(!Array.isArray(t)||0===t.length)throw new e("INVALID_ARGS","batch requires a non-empty batchSteps array.");if(t.length>r)throw new e("INVALID_ARGS",`batch has ${t.length} steps; max allowed is ${r}.`);let n=[];for(let r=0;r<t.length;r+=1){let s=t[r];if(!s||"object"!=typeof s)throw new e("INVALID_ARGS",`Invalid batch step at index ${r}.`);let i=Object.keys(s).filter(t=>!o.has(t));if(i.length>0){let t=i.map(t=>`"${t}"`).join(", ");throw new e("INVALID_ARGS",`Batch step ${r+1} has unknown field(s): ${t}. Allowed fields: command, positionals, flags, runtime.`)}let l="string"==typeof s.command?s.command.trim().toLowerCase():"";if(!l)throw new e("INVALID_ARGS",`Batch step ${r+1} requires command.`);if(a.has(l))throw new e("INVALID_ARGS",`Batch step ${r+1} cannot run ${l}.`);if(void 0!==s.positionals&&!Array.isArray(s.positionals))throw new e("INVALID_ARGS",`Batch step ${r+1} positionals must be an array.`);let c=s.positionals??[];if(c.some(t=>"string"!=typeof t))throw new e("INVALID_ARGS",`Batch step ${r+1} positionals must contain only strings.`);if(void 0!==s.flags&&("object"!=typeof s.flags||Array.isArray(s.flags)||!s.flags))throw new e("INVALID_ARGS",`Batch step ${r+1} flags must be an object.`);if(void 0!==s.runtime&&("object"!=typeof s.runtime||Array.isArray(s.runtime)||!s.runtime))throw new e("INVALID_ARGS",`Batch step ${r+1} runtime must be an object.`);n.push({command:l,positionals:c,flags:s.flags??{},runtime:s.runtime})}return n}function c(t,e){let{batchSteps:r,batchOnError:a,batchMaxSteps:o,...n}=e??{};return h(t,n)}function h(t,e){let r=t??{};for(let t of n)void 0===e[t]&&void 0!==r[t]&&(e[t]=r[t]);return e}async function m(t,e,r,a,o){let n=Date.now(),s=c(t.flags,r.flags);void 0===s.session&&(s.session=e);let i=await a({token:t.token,session:e,command:r.command,positionals:r.positionals,flags:s,runtime:void 0===r.runtime?t.runtime:r.runtime,meta:t.meta}),l=Date.now()-n;return i.ok?{ok:!0,step:o,result:{step:o,command:r.command,ok:!0,data:i.data??{},durationMs:l}}:{ok:!1,step:o,error:i.error}}function p(t,e,r){return{ok:!1,error:{code:t,message:e,...r?{details:r}:{}}}}export{a as BATCH_BLOCKED_COMMANDS,r as DEFAULT_BATCH_MAX_STEPS,n as INHERITED_PARENT_FLAG_KEYS,c as buildBatchStepFlags,h as mergeParentFlags,i as parseBatchStepsJson,s as runBatch,l as validateAndNormalizeBatchSteps};
1
+ import{asAppError as t,AppError as e}from"./9152.js";let r=100,a=new Set(["batch","replay"]),o=new Set(["command","positionals","flags","runtime"]),n=["platform","target","device","udid","serial","verbose","out"];async function s(e,a,o){let n=e.flags,s=n?.batchOnError??"stop";if("stop"!==s)return h("INVALID_ARGS",`Unsupported batch on-error mode: ${s}.`);let l=n?.batchMaxSteps??r;if(!Number.isInteger(l)||l<1||l>1e3)return h("INVALID_ARGS",`Invalid batch max-steps: ${String(n?.batchMaxSteps)}`);try{let t=i(n?.batchSteps,l),r=Date.now(),s=[];for(let[r,n]of t.entries()){let i=await m(e,a,n,o,r+1);if(!i.ok)return{ok:!1,error:{code:i.error.code,message:`Batch failed at step ${i.step} (${n.command}): ${i.error.message}`,hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath,details:{...i.error.details??{},step:i.step,command:n.command,positionals:n.positionals,executed:r,total:t.length,partialResults:s}}};s.push(i.result)}return{ok:!0,data:{total:t.length,executed:t.length,totalDurationMs:Date.now()-r,results:s}}}catch(r){let e=t(r);return h(e.code,e.message,e.details)}}function i(t,r){if(!Array.isArray(t)||0===t.length)throw new e("INVALID_ARGS","batch requires a non-empty batchSteps array.");if(t.length>r)throw new e("INVALID_ARGS",`batch has ${t.length} steps; max allowed is ${r}.`);let n=[];for(let r=0;r<t.length;r+=1){let s=t[r];if(!s||"object"!=typeof s)throw new e("INVALID_ARGS",`Invalid batch step at index ${r}.`);let i=Object.keys(s).filter(t=>!o.has(t));if(i.length>0){let t=i.map(t=>`"${t}"`).join(", ");throw new e("INVALID_ARGS",`Batch step ${r+1} has unknown field(s): ${t}. Allowed fields: command, positionals, flags, runtime.`)}let l="string"==typeof s.command?s.command.trim().toLowerCase():"";if(!l)throw new e("INVALID_ARGS",`Batch step ${r+1} requires command.`);if(a.has(l))throw new e("INVALID_ARGS",`Batch step ${r+1} cannot run ${l}.`);if(void 0!==s.positionals&&!Array.isArray(s.positionals))throw new e("INVALID_ARGS",`Batch step ${r+1} positionals must be an array.`);let c=s.positionals??[];if(c.some(t=>"string"!=typeof t))throw new e("INVALID_ARGS",`Batch step ${r+1} positionals must contain only strings.`);if(void 0!==s.flags&&("object"!=typeof s.flags||Array.isArray(s.flags)||!s.flags))throw new e("INVALID_ARGS",`Batch step ${r+1} flags must be an object.`);if(void 0!==s.runtime&&("object"!=typeof s.runtime||Array.isArray(s.runtime)||!s.runtime))throw new e("INVALID_ARGS",`Batch step ${r+1} runtime must be an object.`);n.push({command:l,positionals:c,flags:s.flags??{},runtime:s.runtime})}return n}function l(t,e){let{batchSteps:r,batchOnError:a,batchMaxSteps:o,...n}=e??{};return c(t,n)}function c(t,e){let r=t??{};for(let t of n)void 0===e[t]&&void 0!==r[t]&&(e[t]=r[t]);return e}async function m(t,e,r,a,o){let n=Date.now(),s=l(t.flags,r.flags);void 0===s.session&&(s.session=e);let i=await a({token:t.token,session:e,command:r.command,positionals:r.positionals,flags:s,runtime:void 0===r.runtime?t.runtime:r.runtime,meta:t.meta}),c=Date.now()-n;return i.ok?{ok:!0,step:o,result:{step:o,command:r.command,ok:!0,data:i.data??{},durationMs:c}}:{ok:!1,step:o,error:i.error}}function h(t,e,r){return{ok:!1,error:{code:t,message:e,...r?{details:r}:{}}}}export{a as BATCH_BLOCKED_COMMANDS,r as DEFAULT_BATCH_MAX_STEPS,n as INHERITED_PARENT_FLAG_KEYS,l as buildBatchStepFlags,c as mergeParentFlags,s as runBatch,i as validateAndNormalizeBatchSteps};
package/dist/src/1769.js CHANGED
@@ -1,7 +1,7 @@
1
- import{promises as e}from"node:fs";import t from"node:os";import a from"node:path";import{asAppError as n,AppError as i}from"./9152.js";import{emitDiagnostic as r}from"./7599.js";import{runCmd as o,resolveFileOverridePath as s,whichCmd as l,runCmdDetached as d}from"./9818.js";import{ensureAndroidSdkPathConfigured as u,resolveAndroidArchivePackageName as c}from"./7651.js";import{sleep as p}from"./4829.js";import{resolveAndroidAdbProvider as f,resolveAndroidAdbExecutor as m,installAndroidAdbPackage as w}from"./9639.js";import{materializeInstallablePath as h,isTrustedInstallSourceUrl as A}from"./989.js";function y(e){let t=new Set;for(let a of e.split("\n")){let e=a.trim();if(!e)continue;let n=e.split(/\s+/)[0];if(!n.includes("/"))continue;let i=n.split("/")[0];i.includes(".")&&i&&t.add(i)}return Array.from(t)}function g(e){return e.split("\n").map(e=>{let t=e.trim();return t.startsWith("package:")?t.slice(8):t}).filter(Boolean)}function b(e){let t=e.split("\n");for(let e of["mCurrentFocus=Window{","mFocusedApp=AppWindowToken{","mResumedActivity:","ResumedActivity:"])for(let a of t){let t=a.indexOf(e);if(-1===t)continue;let n=function(e){for(let t of e.trim().split(/\s+/)){let e=t.indexOf("/");if(e<=0)continue;let a=v(t.slice(0,e),!1),n=v(t.slice(e+1),!0);if(a&&n&&a.length===e)return{package:a,activity:n}}return null}(a.slice(t+e.length));if(n)return n}return null}function v(e,t){let a=0;for(;a<e.length&&function(e,t){if(!e)return!1;let a=e.charCodeAt(0);return a>=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122||"_"===e||"."===e||t&&"$"===e}(e[a],t);)a+=1;return e.slice(0,a)}function M(e){let t=e.trim();if(!t||/\s/.test(t))return!1;let a=/^([A-Za-z][A-Za-z0-9+.-]*):(.+)$/.exec(t);if(!a)return!1;let n=a[1]?.toLowerCase(),i=a[2]??"";return"http"!==n&&"https"!==n&&"ws"!==n&&"wss"!==n&&"ftp"!==n&&"ftps"!==n||i.startsWith("//")}function _(e,t){let a,n=e?.trim();return n?n:"http"===(a=t.trim().split(":")[0]?.toLowerCase())||"https"===a?"com.apple.mobilesafari":void 0}function I(e={}){let t=e.ttlMs??3e4,a=e.nowMs??Date.now,n=new Map,i=e=>{var t;let a=[(t=e).platform,t.deviceId,""].join("\0");for(let e of n.keys())e.startsWith(a)&&n.delete(e)};return{get(e,t){let i=N(e,t),r=n.get(i);if(r)return r.expiresAtMs<=a()?void n.delete(i):r.value},set:(e,i,r)=>(n.set(N(e,i),{value:r,expiresAtMs:a()+t}),r),clear(e){i(e)},async invalidateWhile(e,t){i(e);try{return await t()}finally{i(e)}}}}function N(e,t){return[e.platform,e.deviceId,e.variant??"",t.trim().toLowerCase()].join("\0")}function O(e){return["1","true","yes","on"].includes((e??"").trim().toLowerCase())}class C{startedAtMs;expiresAtMs;constructor(e,t){this.startedAtMs=e,this.expiresAtMs=e+Math.max(0,t)}static fromTimeoutMs(e,t=Date.now()){return new C(t,e)}remainingMs(e=Date.now()){return Math.max(0,this.expiresAtMs-e)}elapsedMs(e=Date.now()){return Math.max(0,e-this.startedAtMs)}isExpired(e=Date.now()){return 0>=this.remainingMs(e)}}async function k(e,t={},a={}){let n,r={maxAttempts:t.maxAttempts??3,baseDelayMs:t.baseDelayMs??200,maxDelayMs:t.maxDelayMs??2e3,jitter:t.jitter??.2,shouldRetry:t.shouldRetry};for(let t=1;t<=r.maxAttempts;t+=1){if(a.signal?.aborted)throw new i("COMMAND_FAILED","request canceled",{reason:"request_canceled"});if(a.deadline?.isExpired()&&t>1)break;try{let n=await e({attempt:t,maxAttempts:r.maxAttempts,deadline:a.deadline});return a.onEvent?.({phase:a.phase,event:"succeeded",attempt:t,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs()}),T({phase:a.phase,event:"succeeded",attempt:t,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs()}),n}catch(d){n=d;let e=a.classifyReason?.(d),i={phase:a.phase,event:"attempt_failed",attempt:t,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:e};if(a.onEvent?.(i),T(i),t>=r.maxAttempts||r.shouldRetry&&!r.shouldRetry(d,t))break;let o=function(e,t,a,n){let i=Math.min(t,e*2**(n-1));return Math.max(0,i+i*a*(2*Math.random()-1))}(r.baseDelayMs,r.maxDelayMs,r.jitter,t),s=a.deadline?Math.min(o,a.deadline.remainingMs()):o;if(s<=0)break;let l={phase:a.phase,event:"retry_scheduled",attempt:t,maxAttempts:r.maxAttempts,delayMs:s,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:e};a.onEvent?.(l),T(l),await function(e,t){return new Promise(a=>{if(t?.aborted)return void a();let n=!1,i=()=>{n||(n=!0,t&&t.removeEventListener("abort",o),a())},r=setTimeout(i,e);function o(){clearTimeout(r),i()}t&&t.addEventListener("abort",o,{once:!0})})}(s,a.signal)}}let o={phase:a.phase,event:"exhausted",attempt:r.maxAttempts,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:a.classifyReason?.(n)};if(a.onEvent?.(o),T(o),n)throw n;throw new i("COMMAND_FAILED","retry failed")}async function D(e,t={}){return k(()=>e(),{maxAttempts:t.attempts,baseDelayMs:t.baseDelayMs,maxDelayMs:t.maxDelayMs,jitter:t.jitter,shouldRetry:t.shouldRetry})}function T(e){r({level:"attempt_failed"===e.event||"exhausted"===e.event?"warn":"debug",phase:"retry",data:{...e}})}function E(e){return e?.trim()||void 0}function x(e){return E(e)}function L(e){return new Set(e.split(/[\s,]+/).map(e=>e.trim()).filter(Boolean))}function S(e,t=process.env){let a=E(e)??E(t.AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST);if(a)return L(a)}function R(e){let t=e.error?n(e.error):null,a=e.context?.platform,i=e.context?.phase;if(t?.code==="TOOL_MISSING")return"android"===a?"ADB_TRANSPORT_UNAVAILABLE":"IOS_TOOL_MISSING";let r=t?.details??{},o="string"==typeof r.message?r.message:void 0,s="string"==typeof r.stdout?r.stdout:void 0,l="string"==typeof r.stderr?r.stderr:void 0,d=r.boot&&"object"==typeof r.boot?r.boot:null,u=r.bootstatus&&"object"==typeof r.bootstatus?r.bootstatus:null,c=[e.message,t?.message,e.stdout,e.stderr,o,s,l,"string"==typeof d?.stdout?d.stdout:void 0,"string"==typeof d?.stderr?d.stderr:void 0,"string"==typeof u?.stdout?u.stdout:void 0,"string"==typeof u?.stderr?u.stderr:void 0].filter(Boolean).join("\n").toLowerCase();return"ios"===a&&(c.includes("runner did not accept connection")||"connect"===i&&(c.includes("timed out")||c.includes("timeout")||c.includes("econnrefused")||c.includes("connection refused")||c.includes("fetch failed")||c.includes("socket hang up")))?"IOS_RUNNER_CONNECT_TIMEOUT":"ios"===a&&"boot"===i&&(c.includes("timed out")||c.includes("timeout"))?"IOS_BOOT_TIMEOUT":"android"===a&&"boot"===i&&(c.includes("timed out")||c.includes("timeout"))?"ANDROID_BOOT_TIMEOUT":c.includes("resource temporarily unavailable")||c.includes("killed: 9")||c.includes("cannot allocate memory")||c.includes("system is low on memory")?"CI_RESOURCE_STARVATION_SUSPECTED":"android"===a&&(c.includes("device not found")||c.includes("no devices")||c.includes("device offline")||c.includes("offline")||c.includes("unauthorized")||c.includes("not authorized")||c.includes("unable to locate device")||c.includes("invalid device"))?"ADB_TRANSPORT_UNAVAILABLE":t?.code==="COMMAND_FAILED"||c.length>0?"BOOT_COMMAND_FAILED":"UNKNOWN"}function P(e){switch(e){case"IOS_BOOT_TIMEOUT":return"Retry simulator boot and inspect simctl bootstatus logs; in CI reduce parallel jobs or use a larger runner.";case"IOS_RUNNER_CONNECT_TIMEOUT":return"Retry runner startup, inspect xcodebuild logs, and verify simulator responsiveness before command execution.";case"ANDROID_BOOT_TIMEOUT":return"Retry emulator startup and verify sys.boot_completed reaches 1; consider increasing startup budget in CI.";case"ADB_TRANSPORT_UNAVAILABLE":return"Check adb server/device transport (adb devices -l), restart adb, and ensure the target device is online and authorized.";case"CI_RESOURCE_STARVATION_SUSPECTED":return"CI machine may be resource constrained; reduce parallel jobs or use a larger runner.";case"IOS_TOOL_MISSING":return"Xcode command-line tools are missing or not in PATH; run xcode-select --install and verify xcrun works.";case"BOOT_COMMAND_FAILED":return"Inspect command stderr/stdout for the failing boot phase and retry after environment validation.";default:return"Retry once and inspect verbose logs for the failing phase."}}let F=["android.software.leanback","android.software.leanback_only","android.hardware.type.television"];async function U(e,t,a){let n=Array(e.length),i=0,r=Math.min(t,e.length);return await Promise.all(Array.from({length:r},async()=>{for(;i<e.length;){let t=i;i+=1,n[t]=await a(e[t])}})),n}function B(e){return`${e.stdout}
2
- ${e.stderr}`}function W(e,t){return["-s",e,...t]}function $(e){return e.startsWith("emulator-")}function V(e){return e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim()}async function G(e,t=1e4){return o("adb",W(e,["shell","getprop","sys.boot_completed"]),{allowFailure:!0,timeoutMs:t})}async function K(e,t){let a=t.replace(/_/g," ").trim();if(!$(e))return a||e;let n=await H(e);return n?n.replace(/_/g," "):a||e}async function j(e,t,a){try{return await a("adb",W(e,t),{allowFailure:!0,timeoutMs:1e4})}catch(e){var i;if("COMMAND_FAILED"===(i=n(e)).code&&"number"==typeof i.details?.timeoutMs)return;throw e}}async function H(e,t=o){for(let a of["ro.boot.qemu.avd_name","persist.sys.avd_name"]){let n=await j(e,["shell","getprop",a],t);if(!n)continue;let i=n.stdout.trim();if(0===n.exitCode&&i.length>0)return i}let a=await j(e,["emu","avd","name"],t);if(!a)return;let n=function(e){let t=e.split("\n").map(e=>e.trim()).filter(e=>e.length>0);if(0!==t.length)return"OK"===t.at(-1)&&t.pop(),t.join("\n").trim()||void 0}(a.stdout);if(0===a.exitCode&&n)return n}async function z(e,t){let a=B(await o("adb",W(e,["shell","cmd","package","has-feature",t]),{allowFailure:!0,timeoutMs:1e4})).toLowerCase();return!!a.includes("true")||!a.includes("false")&&null}async function q(e){return(await U(F,2,async t=>await z(e,t))).some(e=>!0===e)}async function Z(e){var t;let a;return"tv"===((a=B(await o("adb",W(e,["shell","getprop","ro.build.characteristics"]),{allowFailure:!0,timeoutMs:1e4})).toLowerCase()).includes("tv")||a.includes("leanback")?"tv":null)||await q(e)?"tv":(t=B(await o("adb",W(e,["shell","pm","list","features"]),{allowFailure:!0,timeoutMs:1e4})),/feature:android\.(software\.leanback(_only)?|hardware\.type\.television)\b/i.test(t))?"tv":"mobile"}async function J(e={}){if(await u(),!await l("adb"))throw new i("TOOL_MISSING","adb not found in PATH");let t=e.serialAllowlist??S(void 0),a=(await X()).filter(e=>!t||t.has(e.serial));return await U(a,3,async({serial:e,rawModel:t})=>{let[a,n,i]=await Promise.all([K(e,t),et(e),Z(e)]);return{platform:"android",id:e,name:a,kind:$(e)?"emulator":"device",target:i,booted:n}})}async function X(){return(await o("adb",["devices","-l"],{timeoutMs:1e4})).stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("List of devices")).map(e=>e.split(/\s+/)).filter(e=>"device"===e[1]).map(e=>({serial:e[0],rawModel:(e.find(e=>e.startsWith("model:"))??"").replace("model:","")}))}async function Q(){let e=await o("emulator",["-list-avds"],{allowFailure:!0,timeoutMs:1e4});if(0!==e.exitCode)throw new i("COMMAND_FAILED","Failed to list Android emulator AVDs",{stdout:e.stdout,stderr:e.stderr,exitCode:e.exitCode,hint:"Verify Android emulator tooling is installed and available in PATH."});return e.stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0)}async function Y(e){let t=Date.now();for(;Date.now()-t<e.timeoutMs;){try{let t=await ee(e.avdName,e.serial);if(t)return{platform:"android",id:t,name:e.avdName,kind:"emulator",target:"mobile",booted:!1}}catch{}await p(1e3)}throw new i("COMMAND_FAILED","Android emulator did not appear in time",{avdName:e.avdName,serial:e.serial,timeoutMs:e.timeoutMs,hint:"Check emulator logs and verify the AVD can start from command line."})}async function ee(e,t){let a=V(e);for(let e of(await X()).filter(e=>(!t||e.serial===t)&&$(e.serial)))if(V(e.rawModel)===a||V(await K(e.serial,e.rawModel))===a)return e.serial}async function et(e){try{let t=await G(e);return"1"===t.stdout.trim()}catch{return!1}}async function ea(e){var t,a;let n;await u();let r=e.avdName.trim();if(!r)throw new i("INVALID_ARGS","Android emulator boot requires a non-empty AVD name.");let o=e.timeoutMs??12e4;if(!await l("adb"))throw new i("TOOL_MISSING","adb not found in PATH");if(!await l("emulator"))throw new i("TOOL_MISSING","emulator not found in PATH");let s=await Q(),c=function(e,t){let a=e.find(e=>e===t);if(a)return a;let n=V(t);return e.find(e=>V(e)===n)}(s,r);if(!c)throw new i("DEVICE_NOT_FOUND",`No Android emulator AVD named ${e.avdName}`,{requestedAvdName:r,availableAvds:s,hint:"Run `emulator -list-avds` and pass an existing AVD name to --device."});let p=Date.now(),f=(t=await J(),a=e.serial,n=V(c),t.find(e=>"android"===e.platform&&"emulator"===e.kind&&(!a||e.id===a)&&V(e.name)===n));if(!f){let t=["-avd",c];e.headless&&t.push("-no-window","-no-audio"),d("emulator",t)}let m=f??await Y({avdName:c,serial:e.serial,timeoutMs:o}),w=Math.max(1e3,o-(Date.now()-p));await en(m.id,w);let h=(await J()).find(e=>e.id===m.id);return h?{...h,name:c,booted:!0}:{...m,name:c,booted:!0}}async function en(e,t=6e4){let a,r=C.fromTimeoutMs(t),o=Math.max(1,Math.ceil(t/1e3)),s=!1;try{await k(async({deadline:n})=>{if(n?.isExpired())throw s=!0,new i("COMMAND_FAILED","Android boot deadline exceeded",{serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),message:"timeout"});let o=Math.max(1e3,n?.remainingMs()??t),l=await G(e,Math.min(o,1e4));if(a=l,"1"!==l.stdout.trim())throw new i("COMMAND_FAILED","Android device is still booting",{serial:e,stdout:l.stdout,stderr:l.stderr,exitCode:l.exitCode})},{maxAttempts:o,baseDelayMs:1e3,maxDelayMs:1e3,jitter:0,shouldRetry:e=>{let t=R({error:e,stdout:a?.stdout,stderr:a?.stderr,context:{platform:"android",phase:"boot"}});return"ADB_TRANSPORT_UNAVAILABLE"!==t&&"ANDROID_BOOT_TIMEOUT"!==t}},{deadline:r,phase:"boot",classifyReason:e=>R({error:e,stdout:a?.stdout,stderr:a?.stderr,context:{platform:"android",phase:"boot"}})})}catch(f){let o=n(f),l=a?.stdout,d=a?.stderr,u=a?.exitCode,c=R({error:f,stdout:l,stderr:d,context:{platform:"android",phase:"boot"}});"BOOT_COMMAND_FAILED"===c&&"Android device is still booting"===o.message&&(c="ANDROID_BOOT_TIMEOUT");let p={serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),reason:c,hint:P(c),stdout:l,stderr:d,exitCode:u};if(s||"ANDROID_BOOT_TIMEOUT"===c)throw new i("COMMAND_FAILED","Android device did not finish booting in time",p);if("TOOL_MISSING"===o.code)throw new i("TOOL_MISSING",o.message,{...p,...o.details??{}});if("ADB_TRANSPORT_UNAVAILABLE"===c)throw new i("COMMAND_FAILED",o.message,{...p,...o.details??{}});throw new i(o.code,o.message,{...p,...o.details??{}},o.cause)}}async function ei(e,t,a){return await m(e)(t,a)}function er(e){return{platform:"android",id:e,name:e,kind:e.startsWith("emulator-")?"emulator":"device",booted:!0}}async function eo(){if(await u(),!await l("adb"))throw new i("TOOL_MISSING","adb not found in PATH")}let es=/\.(?:apk|aab)$/i,el=/^[A-Za-z_][\w]*(\.[A-Za-z_][\w]*)+$/;function ed(e){var t,a;let n=e.trim();return 0===n.length?"other":es.test(n)?n.includes("/")||n.includes("\\")||n.startsWith(".")||n.startsWith("~")||(t=n,!el.test(t))?"binary":"package":(a=n,el.test(a))?"package":"other"}function eu(e){return`Android runtime hints require an installed package name, not "${e}". Install or reinstall the app first, then relaunch by package.`}async function ec(e,t){let n="url"===e.kind&&A(e.url),i=await h({source:e,isInstallablePath:(e,t)=>{var n;let i;return t.isFile()&&(n=e,".apk"===(i=a.extname(n).toLowerCase())||".aab"===i)},installableLabel:"Android installable (.apk or .aab)",allowArchiveExtraction:"url"!==e.kind||n,signal:t?.signal});try{let e=t?.resolveIdentity===!1?{}:await ep(i.installablePath);return{archivePath:i.archivePath,installablePath:i.installablePath,packageName:e.packageName,cleanup:i.cleanup}}catch(e){throw await i.cleanup(),e}}async function ep(e){let t=a.extname(e).toLowerCase();return".apk"!==t&&".aab"!==t?{}:{packageName:await c(e)}}let ef={settings:{type:"intent",value:"android.settings.SETTINGS"}},em="android.intent.category.LAUNCHER",ew="android.intent.category.LEANBACK_LAUNCHER",eh="android.intent.category.DEFAULT",eA="Run agent-device apps --platform android to discover the installed package name, then retry open with that exact package.",ey=I();function eg(e){return{platform:"android",deviceId:e.id,variant:e.target??""}}async function eb(e,t){let a=t.trim();if("package"===ed(a))return{type:"package",value:a};let n=ef[a.toLowerCase()];if(n)return n;let r=eg(e),o=ey.get(r,a);if(o)return o;let s=(await ei(e,["shell","pm","list","packages"])).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(a.toLowerCase()));if(1===s.length)return ey.set(r,a,{type:"package",value:s[0]});if(s.length>1)throw new i("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:s,hint:"Run agent-device apps --platform android to see the exact installed package names before retrying open."});throw new i("APP_NOT_INSTALLED",`No package found matching "${t}"`,{hint:eA})}async function ev(e,t){let a=await eM(e);return("user-installed"===t?(await eI(e)).filter(e=>a.has(e)):Array.from(a)).sort((e,t)=>e.localeCompare(t)).map(e=>({package:e,name:eN(e)}))}async function eM(e){let t=new Set;for(let a of e_(e,{includeFallbackWhenUnknown:!0})){let n=await ei(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c",a],{allowFailure:!0});if(0===n.exitCode&&0!==n.stdout.trim().length)for(let e of y(n.stdout))t.add(e)}return t}function e_(e,t={}){return"tv"===e.target?[ew]:"mobile"===e.target?[em]:t.includeFallbackWhenUnknown?[em,ew]:[em]}async function eI(e){return g((await ei(e,["shell","pm","list","packages","-3"])).stdout)}function eN(e){let t=new Set(["com","android","google","app","apps","service","services","mobile","client"]),a=e.split(".").flatMap(e=>e.split(/[_-]+/)).map(e=>e.trim().toLowerCase()).filter(e=>e.length>0),n=a[a.length-1]??e;for(let e=a.length-1;e>=0;e-=1){let i=a[e];if(!t.has(i)){n=i;break}}return n.split(/[^a-z0-9]+/i).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}async function eO(e){let t=await eC(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let a=await eC(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return a||{}}async function eC(e,t){for(let a of t){let t=b((await ei(e,a,{allowFailure:!0})).stdout??"");if(t)return t}return null}async function ek(e,t,a){e.booted||await en(e.id);let n=t.trim();if(M(n)){if(a)throw new i("INVALID_ARGS","Activity override is not supported when opening a deep link URL");await ei(e,["shell","am","start","-W","-a","android.intent.action.VIEW","-d",n]);return}let r=await eb(e,t),o=e_(e)[0]??em;if("intent"===r.type){if(a)throw new i("INVALID_ARGS","Activity override requires a package name, not an intent");await ei(e,["shell","am","start","-W","-a",r.value]);return}if(a){let t=a.includes("/")?a:`${r.value}/${a.startsWith(".")?a:`.${a}`}`;try{await ei(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",eh,"-c",o,"-n",t])}catch(t){throw await eE(e,r.value,t),t}return}let s=await ei(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",eh,"-c",o,"-p",r.value],{allowFailure:!0});if(0===s.exitCode&&!eS(s.stdout,s.stderr))return;let l=await eL(e,r.value);if(!l){if(!await eT(e,r.value))throw eD(r.value);throw new i("COMMAND_FAILED",`Failed to launch ${r.value}`,{stdout:s.stdout,stderr:s.stderr})}await ei(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",eh,"-c",o,"-n",l])}function eD(e){return new i("APP_NOT_INSTALLED",`No package found matching "${e}"`,{package:e,hint:eA})}async function eT(e,t){let a=await ei(e,["shell","pm","path",t],{allowFailure:!0}),n=`${a.stdout}
3
- ${a.stderr}`;return!!(0===a.exitCode&&/\bpackage:/i.test(n))||(ex(n),!1)}async function eE(e,t,a){if(ex(a instanceof i?`${String(a.details?.stdout??"")}
4
- ${String(a.details?.stderr??"")}`:"")||!await eT(e,t))throw eD(t)}function ex(e){return/\bunknown package\b/i.test(e)||/\bpackage .* (?:was|is) not found\b/i.test(e)||/\bpackage .* does not exist\b/i.test(e)||/\bcould not find package\b/i.test(e)}async function eL(e,t){for(let a of Array.from(new Set(e_(e,{includeFallbackWhenUnknown:!0})))){let n=await ei(e,["shell","cmd","package","resolve-activity","--brief","-a","android.intent.action.MAIN","-c",a,t],{allowFailure:!0});if(0!==n.exitCode)continue;let i=eR(n.stdout);if(i)return i}return null}function eS(e,t){let a=`${e}
5
- ${t}`;return/Error:.*(?:Activity not started|unable to resolve Intent)/i.test(a)}function eR(e){let t=e.split("\n").map(e=>e.trim()).filter(Boolean);for(let e=t.length-1;e>=0;e-=1){let a=t[e];if(a.includes("/"))return a.split(/\s+/)[0]}return null}async function eP(e){e.booted||await en(e.id)}async function eF(e,t){if("settings"===t.trim().toLowerCase())return void await ei(e,["shell","am","force-stop","com.android.settings"]);let a=await eb(e,t);if("intent"===a.type)throw new i("INVALID_ARGS","Close requires a package name, not an intent");await ei(e,["shell","am","force-stop",a.value])}async function eU(e,t){let a=await eb(e,t);if("intent"===a.type)throw new i("INVALID_ARGS","App uninstall requires a package name, not an intent");let n=await ei(e,["uninstall",a.value],{allowFailure:!0});if(0!==n.exitCode){let e=`${n.stdout}
6
- ${n.stderr}`.toLowerCase();if(!e.includes("unknown package")&&!e.includes("not installed"))throw new i("COMMAND_FAILED",`adb uninstall failed for ${a.value}`,{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode})}return{package:a.value}}let eB=null;async function eW(){let e=`${process.env.PATH??""}::${process.env.AGENT_DEVICE_BUNDLETOOL_JAR??""}`;if(eB?.key===e)return eB.invocation;if(await l("bundletool")){let t={cmd:"bundletool",prefixArgs:[]};return eB={key:e,invocation:t},t}let t=await s(process.env.AGENT_DEVICE_BUNDLETOOL_JAR,"AGENT_DEVICE_BUNDLETOOL_JAR");if(!t)throw new i("TOOL_MISSING","bundletool not found in PATH. Install bundletool or set AGENT_DEVICE_BUNDLETOOL_JAR to a bundletool-all.jar path.");let a={cmd:"java",prefixArgs:["-jar",t]};return eB={key:e,invocation:a},a}async function e$(e){let t=await eW();await o(t.cmd,[...t.prefixArgs,...e])}async function eV(n,i){let r=f(n),o="universal";if(r.installBundle)return void await r.installBundle(i,{mode:o});let s=await e.mkdtemp(a.join(t.tmpdir(),"agent-device-aab-")),l=a.join(s,"bundle.apks");try{await e$(["build-apks","--bundle",i,"--output",l,"--mode",o]),await e$(["install-apks","--apks",l,"--device-id",n.id])}finally{await e.rm(s,{recursive:!0,force:!0})}}async function eG(e,t){".aab"===a.extname(t).toLowerCase()?await eV(e,t):await w(t,{device:e,replace:!0})}async function eK(e){return new Set((await ei(e,["shell","pm","list","packages"])).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean))}async function ej(e,t){let a=Array.from(await eK(e)).filter(e=>!t.has(e));if(1===a.length)return a[0]}async function eH(e,t){await ey.invalidateWhile(eg(e),async()=>{e.booted||await en(e.id),await eG(e,t)})}async function ez(e,t,a){let n=a?void 0:await eK(e);return await eH(e,t),a??(n?await ej(e,n):void 0)}async function eq(e,t){e.booted||await en(e.id);let a=await ec({kind:"path",path:t});try{let t=await ez(e,a.installablePath,a.packageName),n=t?eN(t):void 0;return{archivePath:a.archivePath,installablePath:a.installablePath,packageName:t,appName:n,launchTarget:t}}finally{await a.cleanup()}}async function eZ(e,t,a){return await ey.invalidateWhile(eg(e),async()=>{e.booted||await en(e.id);let{package:n}=await eU(e,t),i=await ec({kind:"path",path:a},{resolveIdentity:!1});try{await eH(e,i.installablePath)}finally{await i.cleanup()}return{package:n}})}async function eJ(e,t={}){let a=["logcat","-d","-v","time"];void 0!==t.lines&&a.push("-t",String(Math.max(1,Math.floor(t.lines))));let n=await e(a,{allowFailure:!0,timeoutMs:t.timeoutMs,signal:t.signal});if(0!==n.exitCode)throw new i("COMMAND_FAILED","Failed to capture Android logcat",{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode});return n.stdout}function eX(e,t={}){if(!e.spawn)throw new i("UNSUPPORTED_OPERATION","Android ADB provider does not support streams",{capability:"adb.spawn"});let a=["logcat","-v","time"];t.pid&&a.push("--pid",t.pid);let n=e.spawn(a,{stdio:["ignore","pipe","pipe"],signal:t.signal});return t.output&&n.stdout&&n.stdout.pipe(t.output,{end:!1}),n}let eQ=new Set(["com.google.android.inputmethod.latin","com.samsung.android.honeyboard","com.touchtype.swiftkey","com.microsoft.swiftkey"]);function eY(e){let t=e1(e.packageName),a=(e.resourceId??"").toLowerCase(),n=e1(e.activeInputMethodPackage);if(t&&n&&t===n)return{inputMethodOwned:!0,source:"active-input-method"};if(n&&a.startsWith(`${n}:id/`))return{inputMethodOwned:!0,source:"active-input-method-resource"};if(t&&eQ.has(t))return{inputMethodOwned:!0,source:"known-ime-package"};for(let e of eQ)if(a.startsWith(`${e}:id/`))return{inputMethodOwned:!0,source:"known-ime-resource"};return{inputMethodOwned:!1,source:"app"}}function e0(e){return eY(e).inputMethodOwned}function e1(e){return(e??"").trim().toLowerCase()||void 0}async function e2(e){return await e4(m(e))}async function e4(e){let t=await e(["shell","dumpsys","input_method"],{allowFailure:!0});if(0!==t.exitCode)throw new i("COMMAND_FAILED","Failed to query Android keyboard state",{stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode});return function(e){let t,a=function(e){let t=new Map;for(let a of e.matchAll(/\b(mInputShown|mIsInputViewShown|isInputViewShown)=([a-zA-Z]+)\b/g)){let e=a[1],n=a[2]?.toLowerCase();e&&("true"===n||"false"===n)&&t.set(e,"true"===n)}if(0===t.size)return null;for(let e of t.values())if(e)return!0;return!1}(e),n=a??!1;if(null===a){let t=e.match(/\bmImeWindowVis=0x([0-9a-fA-F]+)\b/);if(t?.[1]){let e=Number.parseInt(t[1],16);Number.isNaN(e)||(n=(1&e)!=0)}}let i=Array.from(e.matchAll(/\binputType=0x([0-9a-fA-F]+)\b/gi)),o=i.length>0?i[i.length-1]?.[1]:void 0,s=o?`0x${o.toLowerCase()}`:void 0,l=e8(e,/\bpackageName=([A-Za-z0-9_.]+)\b/g),d=e8(e,/\b(?:resourceId|resource-id)=([^\s,}]+)/g),u=function(e){for(let t of[/\bmCurMethodId=([^\s]+)/i,/\bmCurId=([^\s]+)/i,/\bmCurrentInputMethodId=([^\s]+)/i,/\bcurMethodId=([^\s]+)/i]){let a=e.match(t),n=function(e){let t=(e??"").trim();if(!t)return;let a=(t.split("/")[0]??"").match(/[a-zA-Z0-9_.]+/);return e1(a?.[0])}(a?.[1]);if(n)return n}}(e),c=function(e,t,a){return e||t?eY({packageName:e,resourceId:t,activeInputMethodPackage:a}).inputMethodOwned?"ime":"app":"unknown"}(l,d,u);return!u&&((t=e1(l))&&eQ.has(t)||function(e){let t=(e??"").toLowerCase();for(let e of eQ)if(t.startsWith(`${e}:id/`))return!0;return!1}(d))&&r({level:"warn",phase:"android_input_ownership_fallback",data:{focusedPackage:l,focusedResourceId:d}}),{visible:n,inputType:s,type:s?function(e){let t=Number.parseInt(e.replace(/^0x/i,""),16);if(Number.isNaN(t))return"unknown";let a=15&t;if(2===a)return"number";if(3===a)return"phone";if(4===a)return"datetime";if(1!==a)return"unknown";let n=4080&t;return 32===n||208===n?"email":128===n||224===n||144===n?"password":"text"}(s):void 0,inputMethodPackage:u,focusedPackage:l,focusedResourceId:d,inputOwner:c}}(t.stdout)}async function e9(e){return await e3(m(e))}async function e3(e){let t=await e4(e),a=t,n=0;for(;a.visible&&n<2;)await e(["shell","input","keyevent","111"]),n+=1,await p(120),a=await e4(e);if(t.visible&&a.visible)throw new i("UNSUPPORTED_OPERATION","Android keyboard dismiss is unavailable for the current IME without back navigation.",{attempts:n,inputType:a.inputType,type:a.type,inputMethodPackage:a.inputMethodPackage,focusedPackage:a.focusedPackage,focusedResourceId:a.focusedResourceId,inputOwner:a.inputOwner});return{attempts:n,wasVisible:t.visible,dismissed:t.visible&&!a.visible,visible:a.visible,inputType:a.inputType,type:a.type,inputMethodPackage:a.inputMethodPackage,focusedPackage:a.focusedPackage,focusedResourceId:a.focusedResourceId,inputOwner:a.inputOwner}}function e8(e,t){let a;for(let n of e.matchAll(t))a=n[1];return a}async function e5(e){return await e6(m(e))}async function e6(e){let t,a;return(a=(t=(await tt(e,["shell","cmd","clipboard","get","text"],"read")).replace(/\r\n/g,"\n").replace(/\n$/,"")).match(/^clipboard text:\s*(.*)$/i))?a[1]??"":"null"===t.trim().toLowerCase()?"":t}async function e7(e,t){await te(m(e),t)}async function te(e,t){await tt(e,["shell","cmd","clipboard","set","text",t],"write")}async function tt(e,t,a){var n,r;let o,s=await e(t,{allowFailure:!0});if(n=s.stdout,r=s.stderr,(o=`${n}
7
- ${r}`.toLowerCase()).includes("no shell command implementation")||o.includes("unknown command"))throw new i("UNSUPPORTED_OPERATION",`Android shell clipboard ${a} is not supported on this device.`);if(0!==s.exitCode)throw new i("COMMAND_FAILED",`Failed to ${a} Android clipboard text`,{stdout:s.stdout,stderr:s.stderr,exitCode:s.exitCode});return s.stdout}export{C as Deadline,er as androidDeviceForSerial,P as bootFailureHint,eJ as captureAndroidLogcatWithAdb,ed as classifyAndroidAppTarget,R as classifyBootFailure,eF as closeAndroidApp,I as createAppResolutionCache,e9 as dismissAndroidKeyboard,e3 as dismissAndroidKeyboardWithAdb,eo as ensureAdb,ea as ensureAndroidEmulatorBooted,eu as formatAndroidInstalledPackageRequiredMessage,eO as getAndroidAppState,e2 as getAndroidKeyboardState,e4 as getAndroidKeyboardStatusWithAdb,eN as inferAndroidAppName,eq as installAndroidApp,ez as installAndroidInstallablePathAndResolvePackageName,eS as isAmStartError,e0 as isAndroidInputMethodOwnedNode,M as isDeepLinkTarget,O as isEnvTruthy,ev as listAndroidApps,J as listAndroidDevices,ek as openAndroidApp,eP as openAndroidDevice,b as parseAndroidForegroundApp,eR as parseAndroidLaunchComponent,y as parseAndroidLaunchablePackages,g as parseAndroidUserInstalledPackages,L as parseSerialAllowlist,ec as prepareAndroidInstallArtifact,e5 as readAndroidClipboardText,e6 as readAndroidClipboardWithAdb,eZ as reinstallAndroidApp,eb as resolveAndroidApp,S as resolveAndroidSerialAllowlist,_ as resolveIosDeviceDeepLinkBundleId,x as resolveIosSimulatorDeviceSetPath,k as retryWithPolicy,ei as runAndroidAdb,eX as streamAndroidLogcatWithAdb,en as waitForAndroidBoot,D as withRetry,e7 as writeAndroidClipboardText,te as writeAndroidClipboardWithAdb};
1
+ import{promises as e}from"node:fs";import t from"node:os";import a from"node:path";import{asAppError as n,AppError as i}from"./9152.js";import{emitDiagnostic as r}from"./3622.js";import{runCmd as o,resolveFileOverridePath as s,whichCmd as l,runCmdDetached as d}from"./9818.js";import{ensureAndroidSdkPathConfigured as u,resolveAndroidArchivePackageName as c}from"./7651.js";import{sleep as p}from"./4829.js";import{createAndroidPortReverseManager as f,resolveAndroidAdbProvider as m,installAndroidAdbPackage as w,resolveAndroidAdbExecutor as h}from"./9639.js";import{materializeInstallablePath as A,isTrustedInstallSourceUrl as y}from"./989.js";let g=["mCurrentFocus=Window{","mFocusedApp=AppWindowToken{","mResumedActivity:","ResumedActivity:"],v=/\bApplication Not Responding:\s*([A-Za-z0-9_.]+)/i,b=/([^{}]*\bis(?:n't| not)\s+responding[^{}]*)/i,M=/\b([A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)+)\b/;function _(e){let t=new Set;for(let a of e.split("\n")){let e=a.trim();if(!e)continue;let n=e.split(/\s+/)[0]??"";if(!n.includes("/"))continue;let i=n.split("/")[0]??"";i.includes(".")&&i&&t.add(i)}return Array.from(t)}function I(e){return e.split("\n").map(e=>{let t=e.trim();return t.startsWith("package:")?t.slice(8):t}).filter(Boolean)}function N(e){return O(e,e=>(function(e){for(let t of e.trim().split(/\s+/)){let e=t.indexOf("/");if(e<=0)continue;let a=k(t.slice(0,e),!1),n=k(t.slice(e+1),!0);if(a&&n&&a.length===e)return{package:a,activity:n}}return null})(e))}function O(e,t){let a=e.split("\n");for(let e of g)for(let n of a){let a=n.indexOf(e);if(-1===a)continue;let i=n.trim(),r=t(n.slice(a+e.length),i);if(r)return r}return null}function k(e,t){let a=0;for(;a<e.length&&function(e,t){if(!e)return!1;let a=e.charCodeAt(0);return a>=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122||"_"===e||"."===e||t&&"$"===e}(e[a],t);)a+=1;return e.slice(0,a)}function C(e){let t=e.trim();if(!t||/\s/.test(t))return!1;let a=/^([A-Za-z][A-Za-z0-9+.-]*):(.+)$/.exec(t);if(!a)return!1;let n=a[1]?.toLowerCase(),i=a[2]??"";return"http"!==n&&"https"!==n&&"ws"!==n&&"wss"!==n&&"ftp"!==n&&"ftps"!==n||i.startsWith("//")}function D(e,t){let a,n=e?.trim();return n?n:"http"===(a=t.trim().split(":")[0]?.toLowerCase())||"https"===a?"com.apple.mobilesafari":void 0}function L(e={}){let t=e.ttlMs??3e4,a=e.nowMs??Date.now,n=new Map,i=e=>{var t;let a=[(t=e).platform,t.deviceId,""].join("\0");for(let e of n.keys())e.startsWith(a)&&n.delete(e)};return{get(e,t){let i=x(e,t),r=n.get(i);if(r)return r.expiresAtMs<=a()?void n.delete(i):r.value},set:(e,i,r)=>(n.set(x(e,i),{value:r,expiresAtMs:a()+t}),r),clear(e){i(e)},async invalidateWhile(e,t){i(e);try{return await t()}finally{i(e)}}}}function x(e,t){return[e.platform,e.deviceId,e.variant??"",t.trim().toLowerCase()].join("\0")}function E(e){return["1","true","yes","on"].includes((e??"").trim().toLowerCase())}class T{startedAtMs;expiresAtMs;constructor(e,t){this.startedAtMs=e,this.expiresAtMs=e+Math.max(0,t)}static fromTimeoutMs(e,t=Date.now()){return new T(t,e)}remainingMs(e=Date.now()){return Math.max(0,this.expiresAtMs-e)}elapsedMs(e=Date.now()){return Math.max(0,e-this.startedAtMs)}isExpired(e=Date.now()){return 0>=this.remainingMs(e)}}async function S(e,t={},a={}){let n,r={maxAttempts:t.maxAttempts??3,baseDelayMs:t.baseDelayMs??200,maxDelayMs:t.maxDelayMs??2e3,jitter:t.jitter??.2,shouldRetry:t.shouldRetry};for(let t=1;t<=r.maxAttempts;t+=1){if(a.signal?.aborted)throw new i("COMMAND_FAILED","request canceled",{reason:"request_canceled"});if(a.deadline?.isExpired()&&t>1)break;try{let n=await e({attempt:t,maxAttempts:r.maxAttempts,deadline:a.deadline});return a.onEvent?.({phase:a.phase,event:"succeeded",attempt:t,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs()}),P({phase:a.phase,event:"succeeded",attempt:t,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs()}),n}catch(d){n=d;let e=a.classifyReason?.(d),i={phase:a.phase,event:"attempt_failed",attempt:t,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:e};if(a.onEvent?.(i),P(i),t>=r.maxAttempts||r.shouldRetry&&!r.shouldRetry(d,t))break;let o=function(e,t,a,n){let i=Math.min(t,e*2**(n-1));return Math.max(0,i+i*a*(2*Math.random()-1))}(r.baseDelayMs,r.maxDelayMs,r.jitter,t),s=a.deadline?Math.min(o,a.deadline.remainingMs()):o;if(s<=0)break;let l={phase:a.phase,event:"retry_scheduled",attempt:t,maxAttempts:r.maxAttempts,delayMs:s,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:e};a.onEvent?.(l),P(l),await function(e,t){return new Promise(a=>{if(t?.aborted)return void a();let n=!1,i=()=>{n||(n=!0,t&&t.removeEventListener("abort",o),a())},r=setTimeout(i,e);function o(){clearTimeout(r),i()}t&&t.addEventListener("abort",o,{once:!0})})}(s,a.signal)}}let o={phase:a.phase,event:"exhausted",attempt:r.maxAttempts,maxAttempts:r.maxAttempts,elapsedMs:a.deadline?.elapsedMs(),remainingMs:a.deadline?.remainingMs(),reason:a.classifyReason?.(n)};if(a.onEvent?.(o),P(o),n)throw n;throw new i("COMMAND_FAILED","retry failed")}async function R(e,t={}){return S(()=>e(),{maxAttempts:t.attempts,baseDelayMs:t.baseDelayMs,maxDelayMs:t.maxDelayMs,jitter:t.jitter,shouldRetry:t.shouldRetry})}function P(e){r({level:"attempt_failed"===e.event||"exhausted"===e.event?"warn":"debug",phase:"retry",data:{...e}})}function F(e){return e?.trim()||void 0}function U(e){return F(e)}function $(e){return new Set(e.split(/[\s,]+/).map(e=>e.trim()).filter(Boolean))}function B(e,t=process.env){let a=F(e)??F(t.AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST);if(a)return $(a)}function W(e){let t=e.error?n(e.error):null,a=e.context?.platform,i=e.context?.phase;if(t?.code==="TOOL_MISSING")return"android"===a?"ADB_TRANSPORT_UNAVAILABLE":"IOS_TOOL_MISSING";let r=t?.details??{},o="string"==typeof r.message?r.message:void 0,s="string"==typeof r.stdout?r.stdout:void 0,l="string"==typeof r.stderr?r.stderr:void 0,d=r.boot&&"object"==typeof r.boot?r.boot:null,u=r.bootstatus&&"object"==typeof r.bootstatus?r.bootstatus:null,c=[e.message,t?.message,e.stdout,e.stderr,o,s,l,"string"==typeof d?.stdout?d.stdout:void 0,"string"==typeof d?.stderr?d.stderr:void 0,"string"==typeof u?.stdout?u.stdout:void 0,"string"==typeof u?.stderr?u.stderr:void 0].filter(Boolean).join("\n").toLowerCase();return"ios"===a&&(c.includes("runner did not accept connection")||"connect"===i&&(c.includes("timed out")||c.includes("timeout")||c.includes("econnrefused")||c.includes("connection refused")||c.includes("fetch failed")||c.includes("socket hang up")))?"IOS_RUNNER_CONNECT_TIMEOUT":"ios"===a&&"boot"===i&&(c.includes("timed out")||c.includes("timeout"))?"IOS_BOOT_TIMEOUT":"android"===a&&"boot"===i&&(c.includes("timed out")||c.includes("timeout"))?"ANDROID_BOOT_TIMEOUT":c.includes("resource temporarily unavailable")||c.includes("killed: 9")||c.includes("cannot allocate memory")||c.includes("system is low on memory")?"CI_RESOURCE_STARVATION_SUSPECTED":"android"===a&&(c.includes("device not found")||c.includes("no devices")||c.includes("device offline")||c.includes("offline")||c.includes("unauthorized")||c.includes("not authorized")||c.includes("unable to locate device")||c.includes("invalid device"))?"ADB_TRANSPORT_UNAVAILABLE":t?.code==="COMMAND_FAILED"||c.length>0?"BOOT_COMMAND_FAILED":"UNKNOWN"}function V(e){switch(e){case"IOS_BOOT_TIMEOUT":return"Retry simulator boot and inspect simctl bootstatus logs; in CI reduce parallel jobs or use a larger runner.";case"IOS_RUNNER_CONNECT_TIMEOUT":return"Retry runner startup, inspect xcodebuild logs, and verify simulator responsiveness before command execution.";case"ANDROID_BOOT_TIMEOUT":return"Retry emulator startup and verify sys.boot_completed reaches 1; consider increasing startup budget in CI.";case"ADB_TRANSPORT_UNAVAILABLE":return"Check adb server/device transport (adb devices -l), restart adb, and ensure the target device is online and authorized.";case"CI_RESOURCE_STARVATION_SUSPECTED":return"CI machine may be resource constrained; reduce parallel jobs or use a larger runner.";case"IOS_TOOL_MISSING":return"Xcode command-line tools are missing or not in PATH; run xcode-select --install and verify xcrun works.";case"BOOT_COMMAND_FAILED":return"Inspect command stderr/stdout for the failing boot phase and retry after environment validation.";default:return"Retry once and inspect verbose logs for the failing phase."}}let G=["android.software.leanback","android.software.leanback_only","android.hardware.type.television"];async function j(e,t,a){let n=Array(e.length),i=0,r=Math.min(t,e.length);return await Promise.all(Array.from({length:r},async()=>{for(;i<e.length;){let t=i;i+=1,n[t]=await a(e[t])}})),n}function K(e){return`${e.stdout}
2
+ ${e.stderr}`}function H(e,t){return["-s",e,...t]}function z(e){return e.startsWith("emulator-")}function q(e){return e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim()}async function Z(e,t=1e4){return o("adb",H(e,["shell","getprop","sys.boot_completed"]),{allowFailure:!0,timeoutMs:t})}async function J(e,t){let a=t.replace(/_/g," ").trim();if(!z(e))return a||e;let n=await Q(e);return n?n.replace(/_/g," "):a||e}async function X(e,t,a){try{return await a("adb",H(e,t),{allowFailure:!0,timeoutMs:1e4})}catch(e){var i;if("COMMAND_FAILED"===(i=n(e)).code&&"number"==typeof i.details?.timeoutMs)return;throw e}}async function Q(e,t=o){for(let a of["ro.boot.qemu.avd_name","persist.sys.avd_name"]){let n=await X(e,["shell","getprop",a],t);if(!n)continue;let i=n.stdout.trim();if(0===n.exitCode&&i.length>0)return i}let a=await X(e,["emu","avd","name"],t);if(!a)return;let n=function(e){let t=e.split("\n").map(e=>e.trim()).filter(e=>e.length>0);if(0!==t.length)return"OK"===t.at(-1)&&t.pop(),t.join("\n").trim()||void 0}(a.stdout);if(0===a.exitCode&&n)return n}async function Y(e,t){let a=K(await o("adb",H(e,["shell","cmd","package","has-feature",t]),{allowFailure:!0,timeoutMs:1e4})).toLowerCase();return!!a.includes("true")||!a.includes("false")&&null}async function ee(e){return(await j(G,2,async t=>await Y(e,t))).some(e=>!0===e)}async function et(e){var t;let a;return"tv"===((a=K(await o("adb",H(e,["shell","getprop","ro.build.characteristics"]),{allowFailure:!0,timeoutMs:1e4})).toLowerCase()).includes("tv")||a.includes("leanback")?"tv":null)||await ee(e)?"tv":(t=K(await o("adb",H(e,["shell","pm","list","features"]),{allowFailure:!0,timeoutMs:1e4})),/feature:android\.(software\.leanback(_only)?|hardware\.type\.television)\b/i.test(t))?"tv":"mobile"}async function ea(e={}){if(await u(),!await l("adb"))throw new i("TOOL_MISSING","adb not found in PATH");let t=e.serialAllowlist??B(void 0),a=(await en()).filter(e=>!t||t.has(e.serial));return await j(a,3,async({serial:e,rawModel:t})=>{let[a,n,i]=await Promise.all([J(e,t),es(e),et(e)]);return{platform:"android",id:e,name:a,kind:z(e)?"emulator":"device",target:i,booted:n}})}async function en(){return(await o("adb",["devices","-l"],{timeoutMs:1e4})).stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("List of devices")).map(e=>e.split(/\s+/)).flatMap(e=>{let t=e[0];return void 0===t||"device"!==e[1]?[]:[{serial:t,rawModel:(e.find(e=>e.startsWith("model:"))??"").replace("model:","")}]})}async function ei(){let e=await o("emulator",["-list-avds"],{allowFailure:!0,timeoutMs:1e4});if(0!==e.exitCode)throw new i("COMMAND_FAILED","Failed to list Android emulator AVDs",{stdout:e.stdout,stderr:e.stderr,exitCode:e.exitCode,hint:"Verify Android emulator tooling is installed and available in PATH."});return e.stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0)}async function er(e){let t=Date.now();for(;Date.now()-t<e.timeoutMs;){try{let t=await eo(e.avdName,e.serial);if(t)return{platform:"android",id:t,name:e.avdName,kind:"emulator",target:"mobile",booted:!1}}catch{}await p(1e3)}throw new i("COMMAND_FAILED","Android emulator did not appear in time",{avdName:e.avdName,serial:e.serial,timeoutMs:e.timeoutMs,hint:"Check emulator logs and verify the AVD can start from command line."})}async function eo(e,t){let a=q(e);for(let e of(await en()).filter(e=>(!t||e.serial===t)&&z(e.serial)))if(q(e.rawModel)===a||q(await J(e.serial,e.rawModel))===a)return e.serial}async function es(e){try{let t=await Z(e);return"1"===t.stdout.trim()}catch{return!1}}async function el(e){var t,a;let n;await u();let r=e.avdName.trim();if(!r)throw new i("INVALID_ARGS","Android emulator boot requires a non-empty AVD name.");let o=e.timeoutMs??12e4;if(!await l("adb"))throw new i("TOOL_MISSING","adb not found in PATH");if(!await l("emulator"))throw new i("TOOL_MISSING","emulator not found in PATH");let s=await ei(),c=function(e,t){let a=e.find(e=>e===t);if(a)return a;let n=q(t);return e.find(e=>q(e)===n)}(s,r);if(!c)throw new i("DEVICE_NOT_FOUND",`No Android emulator AVD named ${e.avdName}`,{requestedAvdName:r,availableAvds:s,hint:"Run `emulator -list-avds` and pass an existing AVD name to --device."});let p=Date.now(),f=(t=await ea(),a=e.serial,n=q(c),t.find(e=>"android"===e.platform&&"emulator"===e.kind&&(!a||e.id===a)&&q(e.name)===n));if(!f){let t=["-avd",c];e.headless&&t.push("-no-window","-no-audio"),d("emulator",t)}let m=f??await er({avdName:c,serial:e.serial,timeoutMs:o}),w=Math.max(1e3,o-(Date.now()-p));await ed(m.id,w);let h=(await ea()).find(e=>e.id===m.id);return h?{...h,name:c,booted:!0}:{...m,name:c,booted:!0}}async function ed(e,t=6e4){let a,r=T.fromTimeoutMs(t),o=Math.max(1,Math.ceil(t/1e3)),s=!1;try{await S(async({deadline:n})=>{if(n?.isExpired())throw s=!0,new i("COMMAND_FAILED","Android boot deadline exceeded",{serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),message:"timeout"});let o=Math.max(1e3,n?.remainingMs()??t),l=await Z(e,Math.min(o,1e4));if(a=l,"1"!==l.stdout.trim())throw new i("COMMAND_FAILED","Android device is still booting",{serial:e,stdout:l.stdout,stderr:l.stderr,exitCode:l.exitCode})},{maxAttempts:o,baseDelayMs:1e3,maxDelayMs:1e3,jitter:0,shouldRetry:e=>{let t=W({error:e,stdout:a?.stdout,stderr:a?.stderr,context:{platform:"android",phase:"boot"}});return"ADB_TRANSPORT_UNAVAILABLE"!==t&&"ANDROID_BOOT_TIMEOUT"!==t}},{deadline:r,phase:"boot",classifyReason:e=>W({error:e,stdout:a?.stdout,stderr:a?.stderr,context:{platform:"android",phase:"boot"}})})}catch(f){let o=n(f),l=a?.stdout,d=a?.stderr,u=a?.exitCode,c=W({error:f,stdout:l,stderr:d,context:{platform:"android",phase:"boot"}});"BOOT_COMMAND_FAILED"===c&&"Android device is still booting"===o.message&&(c="ANDROID_BOOT_TIMEOUT");let p={serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),reason:c,hint:V(c),stdout:l,stderr:d,exitCode:u};if(s||"ANDROID_BOOT_TIMEOUT"===c)throw new i("COMMAND_FAILED","Android device did not finish booting in time",p);if("TOOL_MISSING"===o.code)throw new i("TOOL_MISSING",o.message,{...p,...o.details??{}});if("ADB_TRANSPORT_UNAVAILABLE"===c)throw new i("COMMAND_FAILED",o.message,{...p,...o.details??{}});throw new i(o.code,o.message,{...p,...o.details??{}},o.cause)}}async function eu(e,t,a){return await h(e)(t,a)}function ec(e){return{platform:"android",id:e,name:e,kind:e.startsWith("emulator-")?"emulator":"device",booted:!0}}async function ep(){if(await u(),!await l("adb"))throw new i("TOOL_MISSING","adb not found in PATH")}let ef=/\.(?:apk|aab)$/i,em=/^[A-Za-z_][\w]*(\.[A-Za-z_][\w]*)+$/;function ew(e){var t,a;let n=e.trim();return 0===n.length?"other":ef.test(n)?n.includes("/")||n.includes("\\")||n.startsWith(".")||n.startsWith("~")||(t=n,!em.test(t))?"binary":"package":(a=n,em.test(a))?"package":"other"}function eh(e){return`Android runtime hints require an installed package name, not "${e}". Install or reinstall the app first, then relaunch by package.`}async function eA(e,t){let n="url"===e.kind&&y(e.url),i=await A({source:e,isInstallablePath:(e,t)=>{var n;let i;return t.isFile()&&(n=e,".apk"===(i=a.extname(n).toLowerCase())||".aab"===i)},installableLabel:"Android installable (.apk or .aab)",allowArchiveExtraction:"url"!==e.kind||n,signal:t?.signal});try{let e=t?.resolveIdentity===!1?{}:await ey(i.installablePath);return{archivePath:i.archivePath,installablePath:i.installablePath,packageName:e.packageName,cleanup:i.cleanup}}catch(e){throw await i.cleanup(),e}}async function ey(e){let t=a.extname(e).toLowerCase();return".apk"!==t&&".aab"!==t?{}:{packageName:await c(e)}}let eg={settings:{type:"intent",value:"android.settings.SETTINGS"}},ev="android.intent.category.LAUNCHER",eb="android.intent.category.LEANBACK_LAUNCHER",eM="android.intent.category.DEFAULT",e_="Run agent-device apps --platform android to discover the installed package name, then retry open with that exact package.",eI=new Set(["localhost","127.0.0.1","::1","[::1]"]),eN=L();function eO(e){return{platform:"android",deviceId:e.id,variant:e.target??""}}async function ek(e,t){let a=t.trim();if("package"===ew(a))return{type:"package",value:a};let n=eg[a.toLowerCase()];if(n)return n;let r=eO(e),o=eN.get(r,a);if(o)return o;let s=(await eu(e,["shell","pm","list","packages"])).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(a.toLowerCase())),l=s[0];if(void 0!==l&&1===s.length)return eN.set(r,a,{type:"package",value:l});if(s.length>1)throw new i("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:s,hint:"Run agent-device apps --platform android to see the exact installed package names before retrying open."});throw new i("APP_NOT_INSTALLED",`No package found matching "${t}"`,{hint:e_})}async function eC(e,t){let a=await eD(e);return("user-installed"===t?(await ex(e)).filter(e=>a.has(e)):Array.from(a)).sort((e,t)=>e.localeCompare(t)).map(e=>({package:e,name:eE(e)}))}async function eD(e){let t=new Set;for(let a of eL(e,{includeFallbackWhenUnknown:!0})){let n=await eu(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c",a],{allowFailure:!0});if(0===n.exitCode&&0!==n.stdout.trim().length)for(let e of _(n.stdout))t.add(e)}return t}function eL(e,t={}){return"tv"===e.target?[eb]:"mobile"===e.target?[ev]:t.includeFallbackWhenUnknown?[ev,eb]:[ev]}async function ex(e){return I((await eu(e,["shell","pm","list","packages","-3"])).stdout)}function eE(e){let t=new Set(["com","android","google","app","apps","service","services","mobile","client"]),a=e.split(".").flatMap(e=>e.split(/[_-]+/)).map(e=>e.trim().toLowerCase()).filter(e=>e.length>0),n=a[a.length-1]??e;for(let e=a.length-1;e>=0;e-=1){let i=a[e];if(i&&!t.has(i)){n=i;break}}return n.split(/[^a-z0-9]+/i).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}async function eT(e){let t=await eR(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let a=await eR(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return a||{}}async function eS(e){return await eP(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]])}async function eR(e,t){for(let a of t){let t=N((await eu(e,a,{allowFailure:!0})).stdout??"");if(t)return t}return null}async function eP(e,t){for(let a of t){let t=O((await eu(e,a,{allowFailure:!0})).stdout??"",(e,t)=>(function(e,t){let a=e.split("}")[0]?.trim()??e.trim(),n=v.exec(a);if(n){let e=n[1];return{package:e,focusedWindow:`Application Not Responding: ${e}`,raw:t}}let i=b.exec(a);if(!i)return null;let r=i[1];if(void 0===r)return null;let o=r.trim().replace(/\s+/g," "),s=M.exec(o)?.[1];return{...s?{package:s}:{},focusedWindow:o,raw:t}})(e,t));if(t)return t}return null}async function eF(e,t){let a=function(e){let t;try{t=new URL(e)}catch{return null}let a=t.hostname.toLowerCase();if(!eI.has(a)||!t.port)return null;let n=Number(t.port);return Number.isInteger(n)?`tcp:${n}`:null}(t);if(!a)return;let n=f(m(e));try{await n.ensure({local:a,remote:a})}catch(t){let e={localPort:a.replace("tcp:",""),operation:`adb reverse ${a} ${a}`};throw t instanceof i&&Object.assign(e,{hint:t.details?.hint,diagnosticId:t.details?.diagnosticId,logPath:t.details?.logPath}),new i("COMMAND_FAILED",`Failed to ensure Android port reverse ${a} before opening localhost URL`,e,t)}}async function eU(e,t,a){var n;e.booted||await ed(e.id);let i="string"==typeof(n=a)?{activity:n}:n??{},r=i.activity,o=t.trim();if(C(o))return void await e$(e,o,i);if(void 0!==i.url)return void await eB(e,t,i);let s=await ek(e,t),l=eL(e)[0]??ev;"intent"===s.type?await eW(e,s.value,r):r?await eV(e,s.value,r,l):await eG(e,s.value,l)}async function e$(e,t,a){var n;let r;if(a.activity)throw new i("INVALID_ARGS","Activity override is not supported when opening a deep link URL");await eF(e,t),await eu(e,["shell","am","start","-W","-a","android.intent.action.VIEW","-d",t,...(n=a.appBundleId,(r=n?.trim())?["-p",r]:[])])}async function eB(e,t,a){if(a.activity)throw new i("INVALID_ARGS","Activity override is not supported when opening an app-bound deep link URL");let n=a.url?.trim()??"";if(!C(n))throw new i("INVALID_ARGS","Android app-bound open requires a valid URL target");let r=await eK(e,t,"app-bound open");await eu(e,["shell","am","start","-W","-a","android.intent.action.VIEW","-d",n,"-p",r])}async function eW(e,t,a){if(a)throw new i("INVALID_ARGS","Activity override requires a package name, not an intent");await eu(e,["shell","am","start","-W","-a",t])}async function eV(e,t,a,n){let i=a.includes("/")?a:`${t}/${a.startsWith(".")?a:`.${a}`}`;try{await eu(e,ej(i,n))}catch(a){throw await eq(e,t,a),a}}async function eG(e,t,a){let n=await eu(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",eM,"-c",a,"-p",t],{allowFailure:!0});if(0===n.exitCode&&!eX(n.stdout,n.stderr))return;let r=await eJ(e,t);if(!r){if(!await ez(e,t))throw eH(t);throw new i("COMMAND_FAILED",`Failed to launch ${t}`,{stdout:n.stdout,stderr:n.stderr})}await eu(e,ej(r,a))}function ej(e,t){return["shell","am","start","-W","-a","android.intent.action.MAIN","-c",eM,"-c",t,"-n",e]}async function eK(e,t,a){let n=await ek(e,t);if("intent"===n.type)throw new i("INVALID_ARGS",`Android ${a} requires a package name, not an intent`);return n.value}function eH(e){return new i("APP_NOT_INSTALLED",`No package found matching "${e}"`,{package:e,hint:e_})}async function ez(e,t){let a=await eu(e,["shell","pm","path",t],{allowFailure:!0}),n=`${a.stdout}
3
+ ${a.stderr}`;return!!(0===a.exitCode&&/\bpackage:/i.test(n))||(eZ(n),!1)}async function eq(e,t,a){if(eZ(a instanceof i?`${String(a.details?.stdout??"")}
4
+ ${String(a.details?.stderr??"")}`:"")||!await ez(e,t))throw eH(t)}function eZ(e){return/\bunknown package\b/i.test(e)||/\bpackage .* (?:was|is) not found\b/i.test(e)||/\bpackage .* does not exist\b/i.test(e)||/\bcould not find package\b/i.test(e)}async function eJ(e,t){for(let a of Array.from(new Set(eL(e,{includeFallbackWhenUnknown:!0})))){let n=await eu(e,["shell","cmd","package","resolve-activity","--brief","-a","android.intent.action.MAIN","-c",a,t],{allowFailure:!0});if(0!==n.exitCode)continue;let i=eQ(n.stdout);if(i)return i}return null}function eX(e,t){let a=`${e}
5
+ ${t}`;return/Error:.*(?:Activity not started|unable to resolve Intent)/i.test(a)}function eQ(e){let t=e.split("\n").map(e=>e.trim()).filter(Boolean);for(let e=t.length-1;e>=0;e-=1){let a=t[e];if(void 0===a||!a.includes("/"))continue;let n=a.split(/\s+/)[0];if(void 0!==n)return n}return null}async function eY(e){e.booted||await ed(e.id)}async function e0(e,t){if("settings"===t.trim().toLowerCase())return void await eu(e,["shell","am","force-stop","com.android.settings"]);let a=await ek(e,t);if("intent"===a.type)throw new i("INVALID_ARGS","Close requires a package name, not an intent");await eu(e,["shell","am","force-stop",a.value])}async function e1(e,t){let a=await ek(e,t);if("intent"===a.type)throw new i("INVALID_ARGS","App uninstall requires a package name, not an intent");let n=await eu(e,["uninstall",a.value],{allowFailure:!0});if(0!==n.exitCode){let e=`${n.stdout}
6
+ ${n.stderr}`.toLowerCase();if(!e.includes("unknown package")&&!e.includes("not installed"))throw new i("COMMAND_FAILED",`adb uninstall failed for ${a.value}`,{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode})}return{package:a.value}}let e2=null;async function e9(){let e=`${process.env.PATH??""}::${process.env.AGENT_DEVICE_BUNDLETOOL_JAR??""}`;if(e2?.key===e)return e2.invocation;if(await l("bundletool")){let t={cmd:"bundletool",prefixArgs:[]};return e2={key:e,invocation:t},t}let t=await s(process.env.AGENT_DEVICE_BUNDLETOOL_JAR,"AGENT_DEVICE_BUNDLETOOL_JAR");if(!t)throw new i("TOOL_MISSING","bundletool not found in PATH. Install bundletool or set AGENT_DEVICE_BUNDLETOOL_JAR to a bundletool-all.jar path.");let a={cmd:"java",prefixArgs:["-jar",t]};return e2={key:e,invocation:a},a}async function e4(e){let t=await e9();await o(t.cmd,[...t.prefixArgs,...e])}async function e3(n,i){let r=m(n),o="universal";if(r.installBundle)return void await r.installBundle(i,{mode:o});let s=await e.mkdtemp(a.join(t.tmpdir(),"agent-device-aab-")),l=a.join(s,"bundle.apks");try{await e4(["build-apks","--bundle",i,"--output",l,"--mode",o]),await e4(["install-apks","--apks",l,"--device-id",n.id])}finally{await e.rm(s,{recursive:!0,force:!0})}}async function e8(e,t){".aab"===a.extname(t).toLowerCase()?await e3(e,t):await w(t,{device:e,replace:!0})}async function e6(e){return new Set((await eu(e,["shell","pm","list","packages"])).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean))}async function e5(e,t){let a=Array.from(await e6(e)).filter(e=>!t.has(e));if(1===a.length)return a[0]}async function e7(e,t){await eN.invalidateWhile(eO(e),async()=>{e.booted||await ed(e.id),await e8(e,t)})}async function te(e,t,a){let n=a?void 0:await e6(e);return await e7(e,t),a??(n?await e5(e,n):void 0)}async function tt(e,t){e.booted||await ed(e.id);let a=await eA({kind:"path",path:t});try{let t=await te(e,a.installablePath,a.packageName),n=t?eE(t):void 0;return{archivePath:a.archivePath,installablePath:a.installablePath,packageName:t,appName:n,launchTarget:t}}finally{await a.cleanup()}}async function ta(e,t,a){return await eN.invalidateWhile(eO(e),async()=>{e.booted||await ed(e.id);let{package:n}=await e1(e,t),i=await eA({kind:"path",path:a},{resolveIdentity:!1});try{await e7(e,i.installablePath)}finally{await i.cleanup()}return{package:n}})}async function tn(e,t={}){let a=["logcat","-d","-v","time"];void 0!==t.lines&&a.push("-t",String(Math.max(1,Math.floor(t.lines))));let n=await e(a,{allowFailure:!0,timeoutMs:t.timeoutMs,signal:t.signal});if(0!==n.exitCode)throw new i("COMMAND_FAILED","Failed to capture Android logcat",{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode});return n.stdout}function ti(e,t={}){if(!e.spawn)throw new i("UNSUPPORTED_OPERATION","Android ADB provider does not support streams",{capability:"adb.spawn"});let a=["logcat","-v","time"];t.pid&&a.push("--pid",t.pid);let n=e.spawn(a,{stdio:["ignore","pipe","pipe"],signal:t.signal});return t.output&&n.stdout&&n.stdout.pipe(t.output,{end:!1}),n}let tr=new Set(["com.google.android.inputmethod.latin","com.samsung.android.honeyboard","com.touchtype.swiftkey","com.microsoft.swiftkey"]);function to(e){let t=tl(e.packageName),a=(e.resourceId??"").toLowerCase(),n=tl(e.activeInputMethodPackage);if(t&&n&&t===n)return{inputMethodOwned:!0,source:"active-input-method"};if(n&&a.startsWith(`${n}:id/`))return{inputMethodOwned:!0,source:"active-input-method-resource"};if(t&&tr.has(t))return{inputMethodOwned:!0,source:"known-ime-package"};for(let e of tr)if(a.startsWith(`${e}:id/`))return{inputMethodOwned:!0,source:"known-ime-resource"};return{inputMethodOwned:!1,source:"app"}}function ts(e){return to(e).inputMethodOwned}function tl(e){return(e??"").trim().toLowerCase()||void 0}async function td(e){return await tu(h(e))}async function tu(e){let t=await e(["shell","dumpsys","input_method"],{allowFailure:!0});if(0!==t.exitCode)throw new i("COMMAND_FAILED","Failed to query Android keyboard state",{stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode});return function(e){let t,a=function(e){let t=new Map;for(let a of e.matchAll(/\b(mInputShown|mIsInputViewShown|isInputViewShown)=([a-zA-Z]+)\b/g)){let e=a[1],n=a[2]?.toLowerCase();e&&("true"===n||"false"===n)&&t.set(e,"true"===n)}if(0===t.size)return null;for(let e of t.values())if(e)return!0;return!1}(e),n=a??!1;if(null===a){let t=e.match(/\bmImeWindowVis=0x([0-9a-fA-F]+)\b/);if(t?.[1]){let e=Number.parseInt(t[1],16);Number.isNaN(e)||(n=(1&e)!=0)}}let i=Array.from(e.matchAll(/\binputType=0x([0-9a-fA-F]+)\b/gi)),o=i.length>0?i[i.length-1]?.[1]:void 0,s=o?`0x${o.toLowerCase()}`:void 0,l=tf(e,/\bpackageName=([A-Za-z0-9_.]+)\b/g),d=tf(e,/\b(?:resourceId|resource-id)=([^\s,}]+)/g),u=function(e){for(let t of[/\bmCurMethodId=([^\s]+)/i,/\bmCurId=([^\s]+)/i,/\bmCurrentInputMethodId=([^\s]+)/i,/\bcurMethodId=([^\s]+)/i]){let a=e.match(t),n=function(e){let t=(e??"").trim();if(!t)return;let a=(t.split("/")[0]??"").match(/[a-zA-Z0-9_.]+/);return tl(a?.[0])}(a?.[1]);if(n)return n}}(e),c=function(e,t,a){return e||t?to({packageName:e,resourceId:t,activeInputMethodPackage:a}).inputMethodOwned?"ime":"app":"unknown"}(l,d,u);return!u&&((t=tl(l))&&tr.has(t)||function(e){let t=(e??"").toLowerCase();for(let e of tr)if(t.startsWith(`${e}:id/`))return!0;return!1}(d))&&r({level:"warn",phase:"android_input_ownership_fallback",data:{focusedPackage:l,focusedResourceId:d}}),{visible:n,inputType:s,type:s?function(e){let t=Number.parseInt(e.replace(/^0x/i,""),16);if(Number.isNaN(t))return"unknown";let a=15&t;if(2===a)return"number";if(3===a)return"phone";if(4===a)return"datetime";if(1!==a)return"unknown";let n=4080&t;return 32===n||208===n?"email":128===n||224===n||144===n?"password":"text"}(s):void 0,inputMethodPackage:u,focusedPackage:l,focusedResourceId:d,inputOwner:c}}(t.stdout)}async function tc(e){return await tp(h(e))}async function tp(e){let t=await tu(e),a=t,n=0;for(;a.visible&&n<2;)await e(["shell","input","keyevent","111"]),n+=1,await p(120),a=await tu(e);if(t.visible&&a.visible)throw new i("UNSUPPORTED_OPERATION","Android keyboard dismiss is unavailable for the current IME without back navigation.",{attempts:n,inputType:a.inputType,type:a.type,inputMethodPackage:a.inputMethodPackage,focusedPackage:a.focusedPackage,focusedResourceId:a.focusedResourceId,inputOwner:a.inputOwner});return{attempts:n,wasVisible:t.visible,dismissed:t.visible&&!a.visible,visible:a.visible,inputType:a.inputType,type:a.type,inputMethodPackage:a.inputMethodPackage,focusedPackage:a.focusedPackage,focusedResourceId:a.focusedResourceId,inputOwner:a.inputOwner}}function tf(e,t){let a;for(let n of e.matchAll(t))a=n[1];return a}async function tm(e){return await tw(h(e))}async function tw(e){let t,a;return(a=(t=(await ty(e,["shell","cmd","clipboard","get","text"],"read")).replace(/\r\n/g,"\n").replace(/\n$/,"")).match(/^clipboard text:\s*(.*)$/i))?a[1]??"":"null"===t.trim().toLowerCase()?"":t}async function th(e,t){await tA(h(e),t)}async function tA(e,t){await ty(e,["shell","cmd","clipboard","set","text",t],"write")}async function ty(e,t,a){var n,r;let o,s=await e(t,{allowFailure:!0});if(n=s.stdout,r=s.stderr,(o=`${n}
7
+ ${r}`.toLowerCase()).includes("no shell command implementation")||o.includes("unknown command"))throw new i("UNSUPPORTED_OPERATION",`Android shell clipboard ${a} is not supported on this device.`);if(0!==s.exitCode)throw new i("COMMAND_FAILED",`Failed to ${a} Android clipboard text`,{stdout:s.stdout,stderr:s.stderr,exitCode:s.exitCode});return s.stdout}export{T as Deadline,ec as androidDeviceForSerial,V as bootFailureHint,tn as captureAndroidLogcatWithAdb,ew as classifyAndroidAppTarget,W as classifyBootFailure,e0 as closeAndroidApp,L as createAppResolutionCache,tc as dismissAndroidKeyboard,tp as dismissAndroidKeyboardWithAdb,ep as ensureAdb,el as ensureAndroidEmulatorBooted,eh as formatAndroidInstalledPackageRequiredMessage,eT as getAndroidAppState,eS as getAndroidBlockingDialogFocus,td as getAndroidKeyboardState,tu as getAndroidKeyboardStatusWithAdb,eE as inferAndroidAppName,tt as installAndroidApp,te as installAndroidInstallablePathAndResolvePackageName,eX as isAmStartError,ts as isAndroidInputMethodOwnedNode,C as isDeepLinkTarget,E as isEnvTruthy,eC as listAndroidApps,ea as listAndroidDevices,eU as openAndroidApp,eY as openAndroidDevice,N as parseAndroidForegroundApp,eQ as parseAndroidLaunchComponent,_ as parseAndroidLaunchablePackages,I as parseAndroidUserInstalledPackages,$ as parseSerialAllowlist,eA as prepareAndroidInstallArtifact,tm as readAndroidClipboardText,tw as readAndroidClipboardWithAdb,ta as reinstallAndroidApp,ek as resolveAndroidApp,B as resolveAndroidSerialAllowlist,D as resolveIosDeviceDeepLinkBundleId,U as resolveIosSimulatorDeviceSetPath,S as retryWithPolicy,eu as runAndroidAdb,ti as streamAndroidLogcatWithAdb,ed as waitForAndroidBoot,R as withRetry,th as writeAndroidClipboardText,tA as writeAndroidClipboardWithAdb};