agent-device 0.14.8 → 0.15.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 (49) hide show
  1. package/README.md +8 -6
  2. package/android-snapshot-helper/README.md +4 -2
  3. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.14.8.apk → agent-device-android-snapshot-helper-0.15.0.apk} +0 -0
  4. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.15.0.apk.sha256 +1 -0
  5. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.14.8.manifest.json → agent-device-android-snapshot-helper-0.15.0.manifest.json} +6 -6
  6. package/dist/src/1769.js +7 -0
  7. package/dist/src/2151.js +429 -0
  8. package/dist/src/221.js +4 -4
  9. package/dist/src/2842.js +1 -0
  10. package/dist/src/3572.js +1 -0
  11. package/dist/src/4057.js +1 -1
  12. package/dist/src/840.js +2 -0
  13. package/dist/src/9542.js +2 -2
  14. package/dist/src/9639.js +2 -2
  15. package/dist/src/9818.js +1 -1
  16. package/dist/src/android-adb.d.ts +49 -11
  17. package/dist/src/android-adb.js +1 -1
  18. package/dist/src/android-snapshot-helper.d.ts +35 -2
  19. package/dist/src/cli.js +60 -57
  20. package/dist/src/contracts.d.ts +2 -0
  21. package/dist/src/finders.d.ts +2 -0
  22. package/dist/src/index.d.ts +25 -22
  23. package/dist/src/internal/companion-tunnel.js +1 -1
  24. package/dist/src/internal/daemon.js +51 -23
  25. package/dist/src/remote-config.d.ts +17 -14
  26. package/dist/src/selectors.d.ts +3 -0
  27. package/dist/src/server.js +2 -20
  28. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +7 -1
  29. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +210 -56
  30. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +890 -99
  31. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +94 -7
  32. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +8 -0
  33. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +24 -0
  34. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +2 -0
  35. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +185 -0
  36. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +1 -2
  37. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +26 -0
  38. package/package.json +25 -11
  39. package/server.json +3 -3
  40. package/skills/agent-device/SKILL.md +6 -1
  41. package/skills/dogfood/SKILL.md +3 -1
  42. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.8.apk.sha256 +0 -1
  43. package/dist/src/180.js +0 -1
  44. package/dist/src/6108.js +0 -26
  45. package/dist/src/6642.js +0 -1
  46. package/dist/src/7462.js +0 -1
  47. package/dist/src/8809.js +0 -8
  48. package/dist/src/command-schema.js +0 -381
  49. package/skills/react-devtools/SKILL.md +0 -48
package/README.md CHANGED
@@ -54,14 +54,16 @@ If you install skills separately, keep the CLI on `agent-device >= 0.14.0`. Olde
54
54
  ### AI Agent Entry Points
55
55
 
56
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.
58
- - **MCP router**: use `agent-device mcp` when an MCP-aware client needs install, status, and version-matched help discovery. MCP is intentionally a thin router; device automation still runs through CLI commands.
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
59
 
60
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
61
 
62
62
  ### MCP Router
63
63
 
64
- `agent-device` ships an official stdio MCP router for discovery-oriented clients. It exposes only `status`, `install`, and `help` tools plus workflow prompts/resources; it does not expose device automation or generic shell execution over MCP.
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`.
65
67
 
66
68
  Paste one of these into clients that accept `mcpServers`, such as Cursor project `.cursor/mcp.json` or user-level MCP settings.
67
69
 
@@ -89,7 +91,7 @@ Paste one of these into clients that accept `mcpServers`, such as Cursor project
89
91
  "mcpServers": {
90
92
  "agent-device": {
91
93
  "command": "npx",
92
- "args": ["-y", "agent-device@latest", "mcp"]
94
+ "args": ["-y", "agent-device@<reviewed-version>", "mcp"]
93
95
  }
94
96
  }
95
97
  }
@@ -114,6 +116,7 @@ Try the loop.
114
116
  ```bash
115
117
  # Find the app.
116
118
  agent-device apps --platform ios
119
+ agent-device apps --platform android
117
120
 
118
121
  # Start a session.
119
122
  agent-device open SampleApp --platform ios
@@ -169,7 +172,7 @@ Run:
169
172
  - Capture a baseline `snapshot -i`, screenshot, and `perf --json` sample.
170
173
  - Exercise Home, Catalog, product detail, Checkout, and Settings. Re-snapshot after each mutation and use refs/selectors from fresh snapshots.
171
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.
172
- - Run focused performance checks: compare `perf --json` before and after a navigation or form flow; if React DevTools connects, capture profile slow/rerender output. If it cannot connect, include the status and continue.
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.
173
176
  - Close the session so the `.ad` replay is written.
174
177
 
175
178
  Report:
@@ -221,7 +224,6 @@ Used by teams and developers at Callstack, Expensify, Shopify, Kindred, Total Wi
221
224
  Agent integration:
222
225
 
223
226
  - [agent-device skill](skills/agent-device/SKILL.md)
224
- - [react-devtools skill](skills/react-devtools/SKILL.md)
225
227
  - [dogfood skill](skills/dogfood/SKILL.md)
226
228
  - MCP router: `agent-device mcp`
227
229
  - [agent-device skill on ClawHub](https://clawhub.ai/okwasniewski/agent-device)
@@ -14,7 +14,8 @@ requires a stable signing certificate for `adb install -r` upgrades.
14
14
  ## Build
15
15
 
16
16
  ```sh
17
- sh ./scripts/build-android-snapshot-helper.sh 0.13.3 .tmp/android-snapshot-helper
17
+ VERSION="$(node -p 'require("./package.json").version')"
18
+ sh ./scripts/build-android-snapshot-helper.sh "$VERSION" .tmp/android-snapshot-helper
18
19
  ```
19
20
 
20
21
  The build uses Android SDK command-line tools directly. It expects `ANDROID_HOME` or
@@ -26,7 +27,8 @@ missing or outdated.
26
27
  ## Run
27
28
 
28
29
  ```sh
29
- adb install -r -t .tmp/android-snapshot-helper/agent-device-android-snapshot-helper-0.13.3.apk
30
+ VERSION="$(node -p 'require("./package.json").version')"
31
+ adb install -r -t ".tmp/android-snapshot-helper/agent-device-android-snapshot-helper-$VERSION.apk"
30
32
  adb shell am instrument -w \
31
33
  -e waitForIdleTimeoutMs 500 \
32
34
  -e timeoutMs 8000 \
@@ -0,0 +1 @@
1
+ 5fdbdcd5f57e6c152a6a47967748903c0bd2e6655edf19ff0757d815e1975762 agent-device-android-snapshot-helper-0.15.0.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.14.8",
4
- "releaseTag": "v0.14.8",
5
- "assetName": "agent-device-android-snapshot-helper-0.14.8.apk",
3
+ "version": "0.15.0",
4
+ "releaseTag": "v0.15.0",
5
+ "assetName": "agent-device-android-snapshot-helper-0.15.0.apk",
6
6
  "apkUrl": null,
7
- "sha256": "0669bbeb4c3b549a9084dc3d75e4afa8f055408424c40c2fd9db4b75eb1f6e53",
8
- "checksumName": "agent-device-android-snapshot-helper-0.14.8.apk.sha256",
7
+ "sha256": "5fdbdcd5f57e6c152a6a47967748903c0bd2e6655edf19ff0757d815e1975762",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.15.0.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 14008,
10
+ "versionCode": 15000,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
@@ -0,0 +1,7 @@
1
+ import{promises as t}from"node:fs";import e from"node:os";import a from"node:path";import{asAppError as i,AppError as n}from"./9152.js";import{runCmd as r,resolveFileOverridePath as o,whichCmd as l,runCmdDetached as s}from"./9818.js";import{retryWithPolicy as d,TIMEOUT_PROFILES as u,Deadline as c}from"./840.js";import{ensureAndroidSdkPathConfigured as p,resolveAndroidArchivePackageName as f}from"./7651.js";import{sleep as m}from"./4829.js";import{resolveAndroidAdbProvider as w,resolveAndroidAdbExecutor as h,installAndroidAdbPackage as A}from"./9639.js";import{materializeInstallablePath as g,isTrustedInstallSourceUrl as _}from"./989.js";import{emitDiagnostic as b}from"./7599.js";function y(t){let e=new Set;for(let a of t.split("\n")){let t=a.trim();if(!t)continue;let i=t.split(/\s+/)[0];if(!i.includes("/"))continue;let n=i.split("/")[0];n.includes(".")&&n&&e.add(n)}return Array.from(e)}function v(t){return t.split("\n").map(t=>{let e=t.trim();return e.startsWith("package:")?e.slice(8):e}).filter(Boolean)}function I(t){let e=t.split("\n");for(let t of["mCurrentFocus=Window{","mFocusedApp=AppWindowToken{","mResumedActivity:","ResumedActivity:"])for(let a of e){let e=a.indexOf(t);if(-1===e)continue;let i=function(t){for(let e of t.trim().split(/\s+/)){let t=e.indexOf("/");if(t<=0)continue;let a=O(e.slice(0,t),!1),i=O(e.slice(t+1),!0);if(a&&i&&a.length===t)return{package:a,activity:i}}return null}(a.slice(e+t.length));if(i)return i}return null}function O(t,e){let a=0;for(;a<t.length&&function(t,e){if(!t)return!1;let a=t.charCodeAt(0);return a>=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122||"_"===t||"."===t||e&&"$"===t}(t[a],e);)a+=1;return t.slice(0,a)}function M(t){let e=t.trim();if(!e||/\s/.test(e))return!1;let a=/^([A-Za-z][A-Za-z0-9+.-]*):(.+)$/.exec(e);if(!a)return!1;let i=a[1]?.toLowerCase(),n=a[2]??"";return"http"!==i&&"https"!==i&&"ws"!==i&&"wss"!==i&&"ftp"!==i&&"ftps"!==i||n.startsWith("//")}function N(t,e){let a,i=t?.trim();return i?i:"http"===(a=e.trim().split(":")[0]?.toLowerCase())||"https"===a?"com.apple.mobilesafari":void 0}function C(t={}){let e=t.ttlMs??3e4,a=t.nowMs??Date.now,i=new Map,n=t=>{var e;let a=[(e=t).platform,e.deviceId,""].join("\0");for(let t of i.keys())t.startsWith(a)&&i.delete(t)};return{get(t,e){let n=k(t,e),r=i.get(n);if(r)return r.expiresAtMs<=a()?void i.delete(n):r.value},set:(t,n,r)=>(i.set(k(t,n),{value:r,expiresAtMs:a()+e}),r),clear(t){n(t)},async invalidateWhile(t,e){n(t);try{return await e()}finally{n(t)}}}}function k(t,e){return[t.platform,t.deviceId,t.variant??"",e.trim().toLowerCase()].join("\0")}let E=["AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET","IOS_SIMULATOR_DEVICE_SET"],T=["AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST","ANDROID_DEVICE_ALLOWLIST"];function D(t){return t?.trim()||void 0}function L(t,e){for(let a of t){let t=D(e[a]);if(t)return t}}function S(t,e=process.env){return D(t)??L(E,e)}function R(t){return new Set(t.split(/[\s,]+/).map(t=>t.trim()).filter(Boolean))}function x(t,e=process.env){let a=D(t)??L(T,e);if(a)return R(a)}function P(t){let e=t.error?i(t.error):null,a=t.context?.platform,n=t.context?.phase;if(e?.code==="TOOL_MISSING")return"android"===a?"ADB_TRANSPORT_UNAVAILABLE":"IOS_TOOL_MISSING";let r=e?.details??{},o="string"==typeof r.message?r.message:void 0,l="string"==typeof r.stdout?r.stdout:void 0,s="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=[t.message,e?.message,t.stdout,t.stderr,o,l,s,"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"===n&&(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"===n&&(c.includes("timed out")||c.includes("timeout"))?"IOS_BOOT_TIMEOUT":"android"===a&&"boot"===n&&(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":e?.code==="COMMAND_FAILED"||c.length>0?"BOOT_COMMAND_FAILED":"UNKNOWN"}function U(t){switch(t){case"IOS_BOOT_TIMEOUT":return"Retry simulator boot and inspect simctl bootstatus logs; in CI consider increasing AGENT_DEVICE_IOS_BOOT_TIMEOUT_MS.";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 B(t,e,a){let i=Array(t.length),n=0,r=Math.min(e,t.length);return await Promise.all(Array.from({length:r},async()=>{for(;n<t.length;){let e=n;n+=1,i[e]=await a(t[e])}})),i}function V(t){return`${t.stdout}
2
+ ${t.stderr}`}function W(t,e){return["-s",t,...e]}function $(t){return t.startsWith("emulator-")}function G(t){return t.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim()}async function K(t,e=u.android_boot.operationMs){return r("adb",W(t,["shell","getprop","sys.boot_completed"]),{allowFailure:!0,timeoutMs:e})}async function H(t,e){let a=e.replace(/_/g," ").trim();if(!$(t))return a||t;let i=await z(t);return i?i.replace(/_/g," "):a||t}async function j(t,e,a){try{return await a("adb",W(t,e),{allowFailure:!0,timeoutMs:1e4})}catch(t){var n;if("COMMAND_FAILED"===(n=i(t)).code&&"number"==typeof n.details?.timeoutMs)return;throw t}}async function z(t,e=r){for(let a of["ro.boot.qemu.avd_name","persist.sys.avd_name"]){let i=await j(t,["shell","getprop",a],e);if(!i)continue;let n=i.stdout.trim();if(0===i.exitCode&&n.length>0)return n}let a=await j(t,["emu","avd","name"],e);if(!a)return;let i=function(t){let e=t.split("\n").map(t=>t.trim()).filter(t=>t.length>0);if(0!==e.length)return"OK"===e.at(-1)&&e.pop(),e.join("\n").trim()||void 0}(a.stdout);if(0===a.exitCode&&i)return i}async function q(t,e){let a=V(await r("adb",W(t,["shell","cmd","package","has-feature",e]),{allowFailure:!0,timeoutMs:u.android_boot.operationMs})).toLowerCase();return!!a.includes("true")||!a.includes("false")&&null}async function Z(t){return(await B(F,2,async e=>await q(t,e))).some(t=>!0===t)}async function J(t){var e;let a;return"tv"===((a=V(await r("adb",W(t,["shell","getprop","ro.build.characteristics"]),{allowFailure:!0,timeoutMs:u.android_boot.operationMs})).toLowerCase()).includes("tv")||a.includes("leanback")?"tv":null)||await Z(t)?"tv":(e=V(await r("adb",W(t,["shell","pm","list","features"]),{allowFailure:!0,timeoutMs:u.android_boot.operationMs})),/feature:android\.(software\.leanback(_only)?|hardware\.type\.television)\b/i.test(e))?"tv":"mobile"}async function X(t={}){if(await p(),!await l("adb"))throw new n("TOOL_MISSING","adb not found in PATH");let e=t.serialAllowlist??x(void 0),a=(await Q()).filter(t=>!e||e.has(t.serial));return await B(a,3,async({serial:t,rawModel:e})=>{let[a,i,n]=await Promise.all([H(t,e),ta(t),J(t)]);return{platform:"android",id:t,name:a,kind:$(t)?"emulator":"device",target:n,booted:i}})}async function Q(){return(await r("adb",["devices","-l"],{timeoutMs:u.android_boot.operationMs})).stdout.split("\n").map(t=>t.trim()).filter(t=>t.length>0&&!t.startsWith("List of devices")).map(t=>t.split(/\s+/)).filter(t=>"device"===t[1]).map(t=>({serial:t[0],rawModel:(t.find(t=>t.startsWith("model:"))??"").replace("model:","")}))}async function Y(){let t=await r("emulator",["-list-avds"],{allowFailure:!0,timeoutMs:u.android_boot.operationMs});if(0!==t.exitCode)throw new n("COMMAND_FAILED","Failed to list Android emulator AVDs",{stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode,hint:"Verify Android emulator tooling is installed and available in PATH."});return t.stdout.split("\n").map(t=>t.trim()).filter(t=>t.length>0)}async function tt(t){let e=Date.now();for(;Date.now()-e<t.timeoutMs;){try{let e=await te(t.avdName,t.serial);if(e)return{platform:"android",id:e,name:t.avdName,kind:"emulator",target:"mobile",booted:!1}}catch{}await m(1e3)}throw new n("COMMAND_FAILED","Android emulator did not appear in time",{avdName:t.avdName,serial:t.serial,timeoutMs:t.timeoutMs,hint:"Check emulator logs and verify the AVD can start from command line."})}async function te(t,e){let a=G(t);for(let t of(await Q()).filter(t=>(!e||t.serial===e)&&$(t.serial)))if(G(t.rawModel)===a||G(await H(t.serial,t.rawModel))===a)return t.serial}async function ta(t){try{let e=await K(t);return"1"===e.stdout.trim()}catch{return!1}}async function ti(t){var e,a;let i;await p();let r=t.avdName.trim();if(!r)throw new n("INVALID_ARGS","Android emulator boot requires a non-empty AVD name.");let o=t.timeoutMs??12e4;if(!await l("adb"))throw new n("TOOL_MISSING","adb not found in PATH");if(!await l("emulator"))throw new n("TOOL_MISSING","emulator not found in PATH");let d=await Y(),u=function(t,e){let a=t.find(t=>t===e);if(a)return a;let i=G(e);return t.find(t=>G(t)===i)}(d,r);if(!u)throw new n("DEVICE_NOT_FOUND",`No Android emulator AVD named ${t.avdName}`,{requestedAvdName:r,availableAvds:d,hint:"Run `emulator -list-avds` and pass an existing AVD name to --device."});let c=Date.now(),f=(e=await X(),a=t.serial,i=G(u),e.find(t=>"android"===t.platform&&"emulator"===t.kind&&(!a||t.id===a)&&G(t.name)===i));if(!f){let e=["-avd",u];t.headless&&e.push("-no-window","-no-audio"),s("emulator",e)}let m=f??await tt({avdName:u,serial:t.serial,timeoutMs:o}),w=Math.max(1e3,o-(Date.now()-c));await tn(m.id,w);let h=(await X()).find(t=>t.id===m.id);return h?{...h,name:u,booted:!0}:{...m,name:u,booted:!0}}async function tn(t,e=6e4){let a,r=c.fromTimeoutMs(e),o=Math.max(1,Math.ceil(e/1e3)),l=!1;try{await d(async({deadline:i})=>{if(i?.isExpired())throw l=!0,new n("COMMAND_FAILED","Android boot deadline exceeded",{serial:t,timeoutMs:e,elapsedMs:r.elapsedMs(),message:"timeout"});let o=Math.max(1e3,i?.remainingMs()??e),s=await K(t,Math.min(o,u.android_boot.operationMs));if(a=s,"1"!==s.stdout.trim())throw new n("COMMAND_FAILED","Android device is still booting",{serial:t,stdout:s.stdout,stderr:s.stderr,exitCode:s.exitCode})},{maxAttempts:o,baseDelayMs:1e3,maxDelayMs:1e3,jitter:0,shouldRetry:t=>{let e=P({error:t,stdout:a?.stdout,stderr:a?.stderr,context:{platform:"android",phase:"boot"}});return"ADB_TRANSPORT_UNAVAILABLE"!==e&&"ANDROID_BOOT_TIMEOUT"!==e}},{deadline:r,phase:"boot",classifyReason:t=>P({error:t,stdout:a?.stdout,stderr:a?.stderr,context:{platform:"android",phase:"boot"}})})}catch(f){let o=i(f),s=a?.stdout,d=a?.stderr,u=a?.exitCode,c=P({error:f,stdout:s,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:t,timeoutMs:e,elapsedMs:r.elapsedMs(),reason:c,hint:U(c),stdout:s,stderr:d,exitCode:u};if(l||"ANDROID_BOOT_TIMEOUT"===c)throw new n("COMMAND_FAILED","Android device did not finish booting in time",p);if("TOOL_MISSING"===o.code)throw new n("TOOL_MISSING",o.message,{...p,...o.details??{}});if("ADB_TRANSPORT_UNAVAILABLE"===c)throw new n("COMMAND_FAILED",o.message,{...p,...o.details??{}});throw new n(o.code,o.message,{...p,...o.details??{}},o.cause)}}async function tr(t,e,a){return await h(t)(e,a)}function to(t){return{platform:"android",id:t,name:t,kind:t.startsWith("emulator-")?"emulator":"device",booted:!0}}async function tl(){if(await p(),!await l("adb"))throw new n("TOOL_MISSING","adb not found in PATH")}let ts=/\.(?:apk|aab)$/i,td=/^[A-Za-z_][\w]*(\.[A-Za-z_][\w]*)+$/;function tu(t){var e,a;let i=t.trim();return 0===i.length?"other":ts.test(i)?i.includes("/")||i.includes("\\")||i.startsWith(".")||i.startsWith("~")||(e=i,!td.test(e))?"binary":"package":(a=i,td.test(a))?"package":"other"}function tc(t){return`Android runtime hints require an installed package name, not "${t}". Install or reinstall the app first, then relaunch by package.`}async function tp(t,e){let i="url"===t.kind&&_(t.url),n=await g({source:t,isInstallablePath:(t,e)=>{var i;let n;return e.isFile()&&(i=t,".apk"===(n=a.extname(i).toLowerCase())||".aab"===n)},installableLabel:"Android installable (.apk or .aab)",allowArchiveExtraction:"url"!==t.kind||i,signal:e?.signal});try{let t=e?.resolveIdentity===!1?{}:await tf(n.installablePath);return{archivePath:n.archivePath,installablePath:n.installablePath,packageName:t.packageName,cleanup:n.cleanup}}catch(t){throw await n.cleanup(),t}}async function tf(t){let e=a.extname(t).toLowerCase();return".apk"!==e&&".aab"!==e?{}:{packageName:await f(t)}}let tm={settings:{type:"intent",value:"android.settings.SETTINGS"}},tw="android.intent.category.LAUNCHER",th="android.intent.category.LEANBACK_LAUNCHER",tA="android.intent.category.DEFAULT",tg="Run agent-device apps --platform android to discover the installed package name, then retry open with that exact package.",t_=C();function tb(t){return{platform:"android",deviceId:t.id,variant:t.target??""}}async function ty(t,e){let a=e.trim();if("package"===tu(a))return{type:"package",value:a};let i=tm[a.toLowerCase()];if(i)return i;let r=tb(t),o=t_.get(r,a);if(o)return o;let l=(await tr(t,["shell","pm","list","packages"])).stdout.split("\n").map(t=>t.replace("package:","").trim()).filter(Boolean).filter(t=>t.toLowerCase().includes(a.toLowerCase()));if(1===l.length)return t_.set(r,a,{type:"package",value:l[0]});if(l.length>1)throw new n("INVALID_ARGS",`Multiple packages matched "${e}"`,{matches:l,hint:"Run agent-device apps --platform android to see the exact installed package names before retrying open."});throw new n("APP_NOT_INSTALLED",`No package found matching "${e}"`,{hint:tg})}async function tv(t,e){let a=await tI(t);return("user-installed"===e?(await tM(t)).filter(t=>a.has(t)):Array.from(a)).sort((t,e)=>t.localeCompare(e)).map(t=>({package:t,name:tN(t)}))}async function tI(t){let e=new Set;for(let a of tO(t,{includeFallbackWhenUnknown:!0})){let i=await tr(t,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c",a],{allowFailure:!0});if(0===i.exitCode&&0!==i.stdout.trim().length)for(let t of y(i.stdout))e.add(t)}return e}function tO(t,e={}){return"tv"===t.target?[th]:"mobile"===t.target?[tw]:e.includeFallbackWhenUnknown?[tw,th]:[tw]}async function tM(t){return v((await tr(t,["shell","pm","list","packages","-3"])).stdout)}function tN(t){let e=new Set(["com","android","google","app","apps","service","services","mobile","client"]),a=t.split(".").flatMap(t=>t.split(/[_-]+/)).map(t=>t.trim().toLowerCase()).filter(t=>t.length>0),i=a[a.length-1]??t;for(let t=a.length-1;t>=0;t-=1){let n=a[t];if(!e.has(n)){i=n;break}}return i.split(/[^a-z0-9]+/i).filter(Boolean).map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}async function tC(t){let e=await tk(t,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(e)return e;let a=await tk(t,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return a||{}}async function tk(t,e){for(let a of e){let e=I((await tr(t,a,{allowFailure:!0})).stdout??"");if(e)return e}return null}async function tE(t,e,a){t.booted||await tn(t.id);let i=e.trim();if(M(i)){if(a)throw new n("INVALID_ARGS","Activity override is not supported when opening a deep link URL");await tr(t,["shell","am","start","-W","-a","android.intent.action.VIEW","-d",i]);return}let r=await ty(t,e),o=tO(t)[0]??tw;if("intent"===r.type){if(a)throw new n("INVALID_ARGS","Activity override requires a package name, not an intent");await tr(t,["shell","am","start","-W","-a",r.value]);return}if(a){let e=a.includes("/")?a:`${r.value}/${a.startsWith(".")?a:`.${a}`}`;try{await tr(t,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",tA,"-c",o,"-n",e])}catch(e){throw await tL(t,r.value,e),e}return}let l=await tr(t,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",tA,"-c",o,"-p",r.value],{allowFailure:!0});if(0===l.exitCode&&!tx(l.stdout,l.stderr))return;let s=await tR(t,r.value);if(!s){if(!await tD(t,r.value))throw tT(r.value);throw new n("COMMAND_FAILED",`Failed to launch ${r.value}`,{stdout:l.stdout,stderr:l.stderr})}await tr(t,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",tA,"-c",o,"-n",s])}function tT(t){return new n("APP_NOT_INSTALLED",`No package found matching "${t}"`,{package:t,hint:tg})}async function tD(t,e){let a=await tr(t,["shell","pm","path",e],{allowFailure:!0}),i=`${a.stdout}
3
+ ${a.stderr}`;return!!(0===a.exitCode&&/\bpackage:/i.test(i))||(tS(i),!1)}async function tL(t,e,a){if(tS(a instanceof n?`${String(a.details?.stdout??"")}
4
+ ${String(a.details?.stderr??"")}`:"")||!await tD(t,e))throw tT(e)}function tS(t){return/\bunknown package\b/i.test(t)||/\bpackage .* (?:was|is) not found\b/i.test(t)||/\bpackage .* does not exist\b/i.test(t)||/\bcould not find package\b/i.test(t)}async function tR(t,e){for(let a of Array.from(new Set(tO(t,{includeFallbackWhenUnknown:!0})))){let i=await tr(t,["shell","cmd","package","resolve-activity","--brief","-a","android.intent.action.MAIN","-c",a,e],{allowFailure:!0});if(0!==i.exitCode)continue;let n=tP(i.stdout);if(n)return n}return null}function tx(t,e){let a=`${t}
5
+ ${e}`;return/Error:.*(?:Activity not started|unable to resolve Intent)/i.test(a)}function tP(t){let e=t.split("\n").map(t=>t.trim()).filter(Boolean);for(let t=e.length-1;t>=0;t-=1){let a=e[t];if(a.includes("/"))return a.split(/\s+/)[0]}return null}async function tU(t){t.booted||await tn(t.id)}async function tF(t,e){if("settings"===e.trim().toLowerCase())return void await tr(t,["shell","am","force-stop","com.android.settings"]);let a=await ty(t,e);if("intent"===a.type)throw new n("INVALID_ARGS","Close requires a package name, not an intent");await tr(t,["shell","am","force-stop",a.value])}async function tB(t,e){let a=await ty(t,e);if("intent"===a.type)throw new n("INVALID_ARGS","App uninstall requires a package name, not an intent");let i=await tr(t,["uninstall",a.value],{allowFailure:!0});if(0!==i.exitCode){let t=`${i.stdout}
6
+ ${i.stderr}`.toLowerCase();if(!t.includes("unknown package")&&!t.includes("not installed"))throw new n("COMMAND_FAILED",`adb uninstall failed for ${a.value}`,{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode})}return{package:a.value}}let tV=null;async function tW(){let t=`${process.env.PATH??""}::${process.env.AGENT_DEVICE_BUNDLETOOL_JAR??""}`;if(tV?.key===t)return tV.invocation;if(await l("bundletool")){let e={cmd:"bundletool",prefixArgs:[]};return tV={key:t,invocation:e},e}let e=await o(process.env.AGENT_DEVICE_BUNDLETOOL_JAR,"AGENT_DEVICE_BUNDLETOOL_JAR");if(!e)throw new n("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",e]};return tV={key:t,invocation:a},a}async function t$(t){let e=await tW();await r(e.cmd,[...e.prefixArgs,...t])}async function tG(i,n){let r,o=w(i),l=(r=process.env.AGENT_DEVICE_ANDROID_BUNDLETOOL_MODE?.trim())&&r.length>0?r:"universal";if(o.installBundle)return void await o.installBundle(n,{mode:l});let s=await t.mkdtemp(a.join(e.tmpdir(),"agent-device-aab-")),d=a.join(s,"bundle.apks");try{await t$(["build-apks","--bundle",n,"--output",d,"--mode",l]),await t$(["install-apks","--apks",d,"--device-id",i.id])}finally{await t.rm(s,{recursive:!0,force:!0})}}async function tK(t,e){".aab"===a.extname(e).toLowerCase()?await tG(t,e):await A(e,{device:t,replace:!0})}async function tH(t){return new Set((await tr(t,["shell","pm","list","packages"])).stdout.split("\n").map(t=>t.replace("package:","").trim()).filter(Boolean))}async function tj(t,e){let a=Array.from(await tH(t)).filter(t=>!e.has(t));if(1===a.length)return a[0]}async function tz(t,e){await t_.invalidateWhile(tb(t),async()=>{t.booted||await tn(t.id),await tK(t,e)})}async function tq(t,e,a){let i=a?void 0:await tH(t);return await tz(t,e),a??(i?await tj(t,i):void 0)}async function tZ(t,e){t.booted||await tn(t.id);let a=await tp({kind:"path",path:e});try{let e=await tq(t,a.installablePath,a.packageName),i=e?tN(e):void 0;return{archivePath:a.archivePath,installablePath:a.installablePath,packageName:e,appName:i,launchTarget:e}}finally{await a.cleanup()}}async function tJ(t,e,a){return await t_.invalidateWhile(tb(t),async()=>{t.booted||await tn(t.id);let{package:i}=await tB(t,e),n=await tp({kind:"path",path:a},{resolveIdentity:!1});try{await tz(t,n.installablePath)}finally{await n.cleanup()}return{package:i}})}async function tX(t,e={}){let a=["logcat","-d","-v","time"];void 0!==e.lines&&a.push("-t",String(Math.max(1,Math.floor(e.lines))));let i=await t(a,{allowFailure:!0,timeoutMs:e.timeoutMs,signal:e.signal});if(0!==i.exitCode)throw new n("COMMAND_FAILED","Failed to capture Android logcat",{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode});return i.stdout}function tQ(t,e={}){if(!t.spawn)throw new n("UNSUPPORTED_OPERATION","Android ADB provider does not support streams",{capability:"adb.spawn"});let a=["logcat","-v","time"];e.pid&&a.push("--pid",e.pid);let i=t.spawn(a,{stdio:["ignore","pipe","pipe"],signal:e.signal});return e.output&&i.stdout&&i.stdout.pipe(e.output,{end:!1}),i}let tY=new Set(["com.google.android.inputmethod.latin","com.samsung.android.honeyboard","com.touchtype.swiftkey","com.microsoft.swiftkey"]);function t0(t){let e=t9(t.packageName),a=(t.resourceId??"").toLowerCase(),i=t9(t.activeInputMethodPackage);if(e&&i&&e===i)return{inputMethodOwned:!0,source:"active-input-method"};if(i&&a.startsWith(`${i}:id/`))return{inputMethodOwned:!0,source:"active-input-method-resource"};if(e&&tY.has(e))return{inputMethodOwned:!0,source:"known-ime-package"};for(let t of tY)if(a.startsWith(`${t}:id/`))return{inputMethodOwned:!0,source:"known-ime-resource"};return{inputMethodOwned:!1,source:"app"}}function t1(t){return t0(t).inputMethodOwned}function t9(t){return(t??"").trim().toLowerCase()||void 0}async function t2(t){return await t3(h(t))}async function t3(t){let e=await t(["shell","dumpsys","input_method"],{allowFailure:!0});if(0!==e.exitCode)throw new n("COMMAND_FAILED","Failed to query Android keyboard state",{stdout:e.stdout,stderr:e.stderr,exitCode:e.exitCode});return function(t){let e,a=function(t){let e=new Map;for(let a of t.matchAll(/\b(mInputShown|mIsInputViewShown|isInputViewShown)=([a-zA-Z]+)\b/g)){let t=a[1],i=a[2]?.toLowerCase();t&&("true"===i||"false"===i)&&e.set(t,"true"===i)}if(0===e.size)return null;for(let t of e.values())if(t)return!0;return!1}(t),i=a??!1;if(null===a){let e=t.match(/\bmImeWindowVis=0x([0-9a-fA-F]+)\b/);if(e?.[1]){let t=Number.parseInt(e[1],16);Number.isNaN(t)||(i=(1&t)!=0)}}let n=Array.from(t.matchAll(/\binputType=0x([0-9a-fA-F]+)\b/gi)),r=n.length>0?n[n.length-1]?.[1]:void 0,o=r?`0x${r.toLowerCase()}`:void 0,l=t5(t,/\bpackageName=([A-Za-z0-9_.]+)\b/g),s=t5(t,/\b(?:resourceId|resource-id)=([^\s,}]+)/g),d=function(t){for(let e of[/\bmCurMethodId=([^\s]+)/i,/\bmCurId=([^\s]+)/i,/\bmCurrentInputMethodId=([^\s]+)/i,/\bcurMethodId=([^\s]+)/i]){let a=t.match(e),i=function(t){let e=(t??"").trim();if(!e)return;let a=(e.split("/")[0]??"").match(/[a-zA-Z0-9_.]+/);return t9(a?.[0])}(a?.[1]);if(i)return i}}(t),u=function(t,e,a){return t||e?t0({packageName:t,resourceId:e,activeInputMethodPackage:a}).inputMethodOwned?"ime":"app":"unknown"}(l,s,d);return!d&&((e=t9(l))&&tY.has(e)||function(t){let e=(t??"").toLowerCase();for(let t of tY)if(e.startsWith(`${t}:id/`))return!0;return!1}(s))&&b({level:"warn",phase:"android_input_ownership_fallback",data:{focusedPackage:l,focusedResourceId:s}}),{visible:i,inputType:o,type:o?function(t){let e=Number.parseInt(t.replace(/^0x/i,""),16);if(Number.isNaN(e))return"unknown";let a=15&e;if(2===a)return"number";if(3===a)return"phone";if(4===a)return"datetime";if(1!==a)return"unknown";let i=4080&e;return 32===i||208===i?"email":128===i||224===i||144===i?"password":"text"}(o):void 0,inputMethodPackage:d,focusedPackage:l,focusedResourceId:s,inputOwner:u}}(e.stdout)}async function t4(t){return await t8(h(t))}async function t8(t){let e=await t3(t),a=e,i=0;for(;a.visible&&i<2;)await t(["shell","input","keyevent","111"]),i+=1,await m(120),a=await t3(t);if(e.visible&&a.visible)throw new n("UNSUPPORTED_OPERATION","Android keyboard dismiss is unavailable for the current IME without back navigation.",{attempts:i,inputType:a.inputType,type:a.type,inputMethodPackage:a.inputMethodPackage,focusedPackage:a.focusedPackage,focusedResourceId:a.focusedResourceId,inputOwner:a.inputOwner});return{attempts:i,wasVisible:e.visible,dismissed:e.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 t5(t,e){let a;for(let i of t.matchAll(e))a=i[1];return a}async function t6(t){return await t7(h(t))}async function t7(t){let e,a;return(a=(e=(await ea(t,["shell","cmd","clipboard","get","text"],"read")).replace(/\r\n/g,"\n").replace(/\n$/,"")).match(/^clipboard text:\s*(.*)$/i))?a[1]??"":"null"===e.trim().toLowerCase()?"":e}async function et(t,e){await ee(h(t),e)}async function ee(t,e){await ea(t,["shell","cmd","clipboard","set","text",e],"write")}async function ea(t,e,a){var i,r;let o,l=await t(e,{allowFailure:!0});if(i=l.stdout,r=l.stderr,(o=`${i}
7
+ ${r}`.toLowerCase()).includes("no shell command implementation")||o.includes("unknown command"))throw new n("UNSUPPORTED_OPERATION",`Android shell clipboard ${a} is not supported on this device.`);if(0!==l.exitCode)throw new n("COMMAND_FAILED",`Failed to ${a} Android clipboard text`,{stdout:l.stdout,stderr:l.stderr,exitCode:l.exitCode});return l.stdout}export{to as androidDeviceForSerial,U as bootFailureHint,tX as captureAndroidLogcatWithAdb,tu as classifyAndroidAppTarget,P as classifyBootFailure,tF as closeAndroidApp,C as createAppResolutionCache,t4 as dismissAndroidKeyboard,t8 as dismissAndroidKeyboardWithAdb,tl as ensureAdb,ti as ensureAndroidEmulatorBooted,tc as formatAndroidInstalledPackageRequiredMessage,tC as getAndroidAppState,t2 as getAndroidKeyboardState,t3 as getAndroidKeyboardStatusWithAdb,tN as inferAndroidAppName,tZ as installAndroidApp,tq as installAndroidInstallablePathAndResolvePackageName,tx as isAmStartError,t1 as isAndroidInputMethodOwnedNode,M as isDeepLinkTarget,tv as listAndroidApps,X as listAndroidDevices,tE as openAndroidApp,tU as openAndroidDevice,I as parseAndroidForegroundApp,tP as parseAndroidLaunchComponent,y as parseAndroidLaunchablePackages,v as parseAndroidUserInstalledPackages,R as parseSerialAllowlist,tp as prepareAndroidInstallArtifact,t6 as readAndroidClipboardText,t7 as readAndroidClipboardWithAdb,tJ as reinstallAndroidApp,ty as resolveAndroidApp,x as resolveAndroidSerialAllowlist,N as resolveIosDeviceDeepLinkBundleId,S as resolveIosSimulatorDeviceSetPath,tr as runAndroidAdb,tQ as streamAndroidLogcatWithAdb,tn as waitForAndroidBoot,et as writeAndroidClipboardText,ee as writeAndroidClipboardWithAdb};