agent-device 0.18.0 → 0.18.2

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 (159) hide show
  1. package/README.md +3 -2
  2. package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.18.0.apk → agent-device-android-multitouch-helper-0.18.2.apk} +0 -0
  3. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.18.2.apk.sha256 +1 -0
  4. package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.18.0.manifest.json → agent-device-android-multitouch-helper-0.18.2.manifest.json} +4 -4
  5. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.18.0.apk → agent-device-android-snapshot-helper-0.18.2.apk} +0 -0
  6. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.18.2.apk.sha256 +1 -0
  7. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.18.0.manifest.json → agent-device-android-snapshot-helper-0.18.2.manifest.json} +6 -6
  8. package/apple-runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +156 -0
  9. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +10 -16
  10. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +2 -0
  11. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +3 -1
  12. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +2 -0
  13. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +2 -4
  14. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +9 -36
  15. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +2 -0
  16. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +2 -0
  17. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +2 -0
  18. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +2 -0
  19. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +0 -1
  20. package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +2 -1
  21. package/dist/src/1986.js +1 -0
  22. package/dist/src/2748.js +1 -1
  23. package/dist/src/2948.js +21 -21
  24. package/dist/src/314.js +1 -1
  25. package/dist/src/3340.js +8 -7
  26. package/dist/src/4418.js +1 -1
  27. package/dist/src/4428.js +3 -3
  28. package/dist/src/485.js +1 -0
  29. package/dist/src/495.js +5 -0
  30. package/dist/src/5484.js +1 -1
  31. package/dist/src/587.js +1 -1
  32. package/dist/src/5913.js +4 -0
  33. package/dist/src/6849.js +1 -0
  34. package/dist/src/6918.js +1 -1
  35. package/dist/src/6967.js +1 -0
  36. package/dist/src/760.js +1 -0
  37. package/dist/src/7871.js +1 -0
  38. package/dist/src/8184.js +1 -1
  39. package/dist/src/8225.js +1 -1
  40. package/dist/src/8391.js +1 -0
  41. package/dist/src/8404.js +2 -0
  42. package/dist/src/8414.js +2 -0
  43. package/dist/src/8875.js +4 -1
  44. package/dist/src/8936.js +1 -1
  45. package/dist/src/9581.js +3 -3
  46. package/dist/src/9624.js +1 -1
  47. package/dist/src/9722.js +37 -32
  48. package/dist/src/android-adb.d.ts +19 -54
  49. package/dist/src/android-adb.js +1 -1
  50. package/dist/src/android.js +5 -5
  51. package/dist/src/apps.js +9 -11
  52. package/dist/src/audio-probe.js +1 -0
  53. package/dist/src/batch.d.ts +34 -26
  54. package/dist/src/batch.js +1 -1
  55. package/dist/src/cli-help.js +61 -9
  56. package/dist/src/cli.js +34 -22
  57. package/dist/src/contracts.d.ts +33 -123
  58. package/dist/src/contracts.js +1 -1
  59. package/dist/src/debug-symbols.js +3 -0
  60. package/dist/src/devices.js +1 -1
  61. package/dist/src/devices~1.js +1 -1
  62. package/dist/src/devices~2.js +1 -1
  63. package/dist/src/find.js +1 -1
  64. package/dist/src/finders.d.ts +2 -6
  65. package/dist/src/finders.js +1 -1
  66. package/dist/src/generic.js +1 -9
  67. package/dist/src/handler-utils.js +1 -0
  68. package/dist/src/index.d.ts +474 -180
  69. package/dist/src/index.js +1 -1
  70. package/dist/src/input-actions.js +1 -1
  71. package/dist/src/input-actions~1.js +1 -1
  72. package/dist/src/install-source.d.ts +0 -26
  73. package/dist/src/install-source.js +1 -1
  74. package/dist/src/interaction.js +1 -1
  75. package/dist/src/interactor.js +1 -0
  76. package/dist/src/internal/bin.js +1 -1
  77. package/dist/src/internal/companion-tunnel.js +1 -1
  78. package/dist/src/internal/png-worker.js +1 -1
  79. package/dist/src/lease.js +1 -1
  80. package/dist/src/linux.js +1 -1
  81. package/dist/src/metro.d.ts +10 -134
  82. package/dist/src/metro.js +1 -1
  83. package/dist/src/notifications.js +1 -1
  84. package/dist/src/react-native.js +1 -1
  85. package/dist/src/record-trace-recording.js +3 -3
  86. package/dist/src/record-trace.js +1 -1
  87. package/dist/src/remote-config.d.ts +15 -17
  88. package/dist/src/remote-config.js +1 -1
  89. package/dist/src/runner-sequence.js +1 -0
  90. package/dist/src/selector-runtime.js +1 -1
  91. package/dist/src/selectors.d.ts +15 -7
  92. package/dist/src/selectors.js +1 -1
  93. package/dist/src/server.js +2 -2
  94. package/dist/src/session.js +11 -11
  95. package/dist/src/simulator.js +2 -2
  96. package/dist/src/snapshot.js +2 -2
  97. package/dist/src/web.js +1 -1
  98. package/macos-helper/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +297 -0
  99. package/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +30 -0
  100. package/package.json +15 -11
  101. package/server.json +2 -2
  102. package/android-multitouch-helper/README.md +0 -41
  103. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.18.0.apk.sha256 +0 -1
  104. package/android-snapshot-helper/README.md +0 -88
  105. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.18.0.apk.sha256 +0 -1
  106. package/dist/src/1848.js +0 -4
  107. package/dist/src/2577.js +0 -1
  108. package/dist/src/2954.js +0 -2
  109. package/dist/src/4291.js +0 -1
  110. package/dist/src/432.js +0 -4
  111. package/dist/src/4723.js +0 -1
  112. package/dist/src/5341.js +0 -1
  113. package/dist/src/5910.js +0 -2
  114. package/dist/src/6024.js +0 -1
  115. package/dist/src/733.js +0 -1
  116. package/dist/src/7394.js +0 -6
  117. package/dist/src/8275.js +0 -2
  118. package/dist/src/9919.js +0 -6
  119. package/dist/src/android-snapshot-helper.d.ts +0 -359
  120. package/dist/src/android-snapshot-helper.js +0 -1
  121. package/dist/src/apple.js +0 -1
  122. package/dist/src/device-ready.js +0 -2
  123. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.swift +0 -17
  124. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AccentColor.colorset/Contents.json +0 -11
  125. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -36
  126. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/logo.jpg +0 -0
  127. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Contents.json +0 -6
  128. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Logo.imageset/Contents.json +0 -21
  129. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Logo.imageset/logo.jpg +0 -0
  130. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/Contents.json +0 -21
  131. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/powered-by.png +0 -0
  132. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/ContentView.swift +0 -34
  133. package/ios-runner/README.md +0 -53
  134. package/ios-runner/RUNNER_PROTOCOL.md +0 -76
  135. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  136. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
  137. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
  138. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
  139. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +0 -0
  140. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +0 -0
  141. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
  142. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
  143. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +0 -0
  144. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +0 -0
  145. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +0 -0
  146. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
  147. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
  148. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +0 -0
  149. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +0 -0
  150. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
  151. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +0 -0
  152. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +0 -0
  153. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
  154. /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
  155. /package/{ios-runner → apple-runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
  156. /package/{ios-runner → apple-runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
  157. /package/{ios-runner → apple-runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
  158. /package/dist/src/{4069.js → 4917.js} +0 -0
  159. /package/dist/src/{9836.js → 893.js} +0 -0
@@ -1,2 +1,2 @@
1
- import{AppError as t}from"./733.js";import{classifyBootFailure as e,retryWithPolicy as o,bootFailureHint as i,Deadline as r}from"./3340.js";import{runXcrun as s,buildSimctlArgs as d,buildSimctlArgsForDevice as a,runAppleToolCommand as u}from"./9722.js";let l=["Simulator"],n=["Device Hub","Simulator"];function c(e,o){if("simulator"!==e.kind)throw new t("UNSUPPORTED_OPERATION",`${o} is only supported on iOS simulators`)}async function m(t={}){let e=t.deviceHub?n:l,o=t.background?["-g","-a"]:["-a"];for(let t of e)if(0===(await u("open",[...o,t],{allowFailure:!0,timeoutMs:1e4})).exitCode)return}async function f(d,u={}){let l,n;if("simulator"!==d.kind)return;if("Booted"===await w(d)){u.focusExisting&&await m({background:u.deviceHub,deviceHub:u.deviceHub});return}let c=r.fromTimeoutMs(18e4);try{await o(async({deadline:e})=>{if(e?.isExpired())throw new t("COMMAND_FAILED","iOS simulator boot deadline exceeded",{timeoutMs:18e4});let o=Math.max(1e3,e?.remainingMs()??18e4),i=await s(a(d,["boot",d.id]),{allowFailure:!0,timeoutMs:o});l={stdout:String(i.stdout??""),stderr:String(i.stderr??""),exitCode:i.exitCode};let r=`${l.stdout}
2
- ${l.stderr}`.toLowerCase(),u=r.includes("already booted")||r.includes("current state: booted");if(0!==l.exitCode&&!u)throw new t("COMMAND_FAILED","simctl boot failed",{stdout:l.stdout,stderr:l.stderr,exitCode:l.exitCode});let c=await s(a(d,["bootstatus",d.id,"-b"]),{allowFailure:!0,timeoutMs:o});if(n={stdout:String(c.stdout??""),stderr:String(c.stderr??""),exitCode:c.exitCode},0!==n.exitCode)throw new t("COMMAND_FAILED","simctl bootstatus failed",{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode});let m=await w(d);if("Booted"!==m)throw new t("COMMAND_FAILED","Simulator is still booting",{state:m})},{maxAttempts:3,baseDelayMs:500,maxDelayMs:2e3,jitter:.2,shouldRetry:t=>{let o=e({error:t,stdout:n?.stdout??l?.stdout,stderr:n?.stderr??l?.stderr,context:{platform:"ios",phase:"boot"}});return"IOS_BOOT_TIMEOUT"!==o&&"CI_RESOURCE_STARVATION_SUSPECTED"!==o}},{deadline:c,phase:"boot",classifyReason:t=>e({error:t,stdout:n?.stdout??l?.stdout,stderr:n?.stderr??l?.stderr,context:{platform:"ios",phase:"boot"}})})}catch(r){let o=e({error:r,stdout:n?.stdout??l?.stdout,stderr:n?.stderr??l?.stderr,context:{platform:"ios",phase:"boot"}});throw new t("COMMAND_FAILED","iOS simulator failed to boot",{platform:"ios",deviceId:d.id,timeoutMs:18e4,elapsedMs:c.elapsedMs(),reason:o,hint:i(o),boot:l,bootstatus:n})}await m({deviceHub:u.deviceHub})}async function S(t){let e=a(t,["shutdown",t.id]),o=await s(e,{allowFailure:!0,timeoutMs:15e3});return{success:0===o.exitCode,exitCode:o.exitCode,stdout:String(o.stdout??""),stderr:String(o.stderr??"")}}async function w(t){let e="string"==typeof t?t:t.id,o="string"==typeof t?d(["list","devices","-j"]):a(t,["list","devices","-j"]),i=await s(o,{allowFailure:!0,timeoutMs:6e4});if(0!==i.exitCode)return null;try{let t=JSON.parse(String(i.stdout??""));for(let o of Object.values(t.devices??{})){let t=o.find(t=>t.udid===e);if(t)return t.state}return null}catch{return null}}export{f as ensureBootedSimulator,w as getSimulatorState,c as requireSimulatorDevice,S as shutdownSimulator};
1
+ import{AppError as t}from"./485.js";import{classifyBootFailure as e,retryWithPolicy as o,bootFailureHint as i,Deadline as r}from"./3340.js";import{runXcrun as s,buildSimctlArgs as d,buildSimctlArgsForDevice as a,runAppleToolCommand as u}from"./9722.js";let l=["Simulator"],n=["Device Hub","Simulator"],c=new Map;function m(t){return`${t.id}|${t.simulatorSetPath??""}`}function f(t){c.set(m(t),Date.now())}function S(e,o){if("simulator"!==e.kind)throw new t("UNSUPPORTED_OPERATION",`${o} is only supported on iOS simulators`)}async function w(t={}){let e=t.deviceHub?n:l,o=t.background?["-g","-a"]:["-a"];for(let t of e)if(0===(await u("open",[...o,t],{allowFailure:!0,timeoutMs:1e4})).exitCode)return}async function C(d,u={}){let l,n,S,M;if("simulator"!==d.kind)return;if("Booted"===((S=m(d),void 0!==(M=c.get(S))&&(!(Date.now()-M>5e3)||(c.delete(S),0)))?"Booted":await b(d))){f(d),u.focusExisting&&await w({background:u.deviceHub,deviceHub:u.deviceHub});return}u.onColdBootStart?.(d);let _=r.fromTimeoutMs(18e4);try{await o(async({deadline:e})=>{if(e?.isExpired())throw new t("COMMAND_FAILED","iOS simulator boot deadline exceeded",{timeoutMs:18e4});let o=Math.max(1e3,e?.remainingMs()??18e4),i=await s(a(d,["boot",d.id]),{allowFailure:!0,timeoutMs:o});l={stdout:String(i.stdout??""),stderr:String(i.stderr??""),exitCode:i.exitCode};let r=`${l.stdout}
2
+ ${l.stderr}`.toLowerCase(),u=r.includes("already booted")||r.includes("current state: booted");if(0!==l.exitCode&&!u)throw new t("COMMAND_FAILED","simctl boot failed",{stdout:l.stdout,stderr:l.stderr,exitCode:l.exitCode});let c=await s(a(d,["bootstatus",d.id,"-b"]),{allowFailure:!0,timeoutMs:o});if(n={stdout:String(c.stdout??""),stderr:String(c.stderr??""),exitCode:c.exitCode},0!==n.exitCode)throw new t("COMMAND_FAILED","simctl bootstatus failed",{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode});let m=await b(d);if("Booted"!==m)throw new t("COMMAND_FAILED","Simulator is still booting",{state:m})},{maxAttempts:3,baseDelayMs:500,maxDelayMs:2e3,jitter:.2,shouldRetry:t=>{let o=e({error:t,stdout:n?.stdout??l?.stdout,stderr:n?.stderr??l?.stderr,context:{platform:"ios",phase:"boot"}});return"IOS_BOOT_TIMEOUT"!==o&&"CI_RESOURCE_STARVATION_SUSPECTED"!==o}},{deadline:_,phase:"boot",classifyReason:t=>e({error:t,stdout:n?.stdout??l?.stdout,stderr:n?.stderr??l?.stderr,context:{platform:"ios",phase:"boot"}})})}catch(r){let o=e({error:r,stdout:n?.stdout??l?.stdout,stderr:n?.stderr??l?.stderr,context:{platform:"ios",phase:"boot"}});throw new t("COMMAND_FAILED","iOS simulator failed to boot",{platform:"ios",deviceId:d.id,timeoutMs:18e4,elapsedMs:_.elapsedMs(),reason:o,hint:i(o),boot:l,bootstatus:n})}f(d),await w({deviceHub:u.deviceHub})}async function M(t){c.delete(m(t));let e=a(t,["shutdown",t.id]),o=await s(e,{allowFailure:!0,timeoutMs:15e3});return{success:0===o.exitCode,exitCode:o.exitCode,stdout:String(o.stdout??""),stderr:String(o.stderr??"")}}async function b(t){let e="string"==typeof t?t:t.id,o="string"==typeof t?d(["list","devices","-j"]):a(t,["list","devices","-j"]),i=await s(o,{allowFailure:!0,timeoutMs:6e4});if(0!==i.exitCode)return null;try{let t=JSON.parse(String(i.stdout??""));for(let o of Object.values(t.devices??{})){let t=o.find(t=>t.udid===e);if(t)return t.state}return null}catch{return null}}export{C as ensureBootedSimulator,b as getSimulatorState,f as markSimulatorBooted,S as requireSimulatorDevice,M as shutdownSimulator};
@@ -1,2 +1,2 @@
1
- import{__webpack_require__ as e}from"./rslib-runtime.js";import{promises as t}from"node:fs";import a from"node:os";import n from"node:path";import{centerOfRect as s}from"./4291.js";import{normalizeError as o,AppError as r}from"./733.js";import{withDiagnosticTimer as i,emitDiagnostic as l}from"./432.js";import{pressAndroid as d,backAndroid as p}from"./input-actions.js";import{createDaemonRuntimeSessionStore as u,captureSnapshot as c,dispatchCommand as f,resolveSnapshotScope as m,runMacOsAlertAction as h,errorResponse as w,runIosRunnerCommand as g,isCommandSupportedOnDevice as y,snapshotAndroid as _,annotateScreenshotWithRefs as v,context_contextFromFlags as S}from"./9722.js";import{SETTINGS_INVALID_ARGS_MESSAGE as b,parseTimeout as I,getUnsupportedMacOsSettingMessage as A,successText as R,isMacOsSettingSupported as C,snapshotCaptureAnnotationsFrom as N}from"./2948.js";import{sleep as D}from"./5560.js";import{buildSnapshotSession as P,dispatchWaitViaRuntime as k,createDaemonRuntimePolicy as M,recordIfSession as E,resolveSessionDevice as O,withSessionlessRunnerCleanup as K}from"./selector-runtime.js";import{createAgentDevice as U}from"./2577.js";import{summarizeSnapshotDiagnostics as x}from"./1848.js";var L={};e.r(L),e.d(L,{handleSnapshotCommands:()=>ew});let B=new Set(["com.android.permissioncontroller","com.google.android.permissioncontroller","com.google.android.packageinstaller","com.android.packageinstaller"]),$=new Set(["android","com.android.systemui"]),H=/^android:id\/(?:alertTitle|message|button[123]|parentPanel|buttonPanel|contentPanel)$/i,q=/^android:id\/button[123]$/i,F=/(?:^|:)id\/permission_/i,T=/\b(?:is(?:n't| not) responding|keeps stopping|has stopped|close app|app info)\b/i,j=/^(?:ok|allow|allow all|while using the app|only this time|yes|continue|save|confirm|turn on|open settings)$/i,X=/^(?:cancel|deny|don.t allow|don’t allow|not now|no|dismiss|close|close app|later|skip)$/i;function z(e,t){let a=t[0];if(!a)return;let n=G(e,a.index);for(let a of t.slice(1)){let t=new Set(G(e,a.index));for(let e=n.length-1;e>=0;e-=1)t.has(n[e])||n.splice(e,1)}return n[n.length-1]}function G(e,t){let a=new Map(e.map(e=>[e.index,e])),n=[],s=a.get(t);for(;s;)n.push(s.index),s=void 0===s.parentIndex?void 0:a.get(s.parentIndex);return n.reverse()}function V(e,t){let a=new Map;for(let t of e){if(void 0===t.parentIndex)continue;let e=a.get(t.parentIndex)??[];e.push(t),a.set(t.parentIndex,e)}let n=new Set([t]),s=[t];for(let e of s)for(let t of a.get(e)??[])n.has(t.index)||(n.add(t.index),s.push(t.index));return e.filter(e=>n.has(e.index))}function W(e){let t=e.type??"",a=e.identifier??"";return!!(e.hittable||/\bbutton\b/i.test(t)||q.test(a)||/(?:^|:)id\/permission_(?:allow|deny)/i.test(a))}function J(e){if(!e)return"";let t=[e.label,e.value].filter(e=>"string"==typeof e&&e.trim().length>0);return t[0]?.trim()??""}async function Q(e,t,a={}){if("wait"===t)return await Y(e,a.timeoutMs??1e4);if("get"===t){var n;let t=await et(e);return{kind:"alertStatus",platform:"android",action:"get",alert:n=t?.alert??null,...n?R("Alert visible"):R("No alert visible")}}return await Z(e,t)}async function Y(e,t){let a=Date.now(),n=await ee(e,t);if(!n)throw new r("COMMAND_FAILED","alert wait timed out");return{kind:"alertWait",platform:"android",action:"wait",alert:n.alert,waitedMs:Date.now()-a,...R("Alert visible")}}async function Z(e,t){var a;let n,s,o=await ee(e,2e3);if(!o)throw new r("COMMAND_FAILED","alert not found",{hint:"If a sheet is visible in snapshot but alert reports no alert, it is likely app-owned UI. Use snapshot -i and press the visible label/ref."});let i=(a=o.buttons,n="accept"===t?"accept":"dismiss",(s=a.find(e=>e.role===n))||("dismiss"===t?a.find(e=>"neutral"===e.role)??null:1===a.length?a[0]??null:null));if(i)return await d(e,i.x,i.y),ea(t,o.alert,i.label);if("dismiss"===t)return await p(e),ea(t,o.alert,"Back");throw new r("COMMAND_FAILED","alert accept found an alert but no accept button",{alert:o.alert,hint:"Inspect alert get --json for visible buttons, then use press by visible label/ref if needed."})}async function ee(e,t){let a=Date.now();for(;Date.now()-a<t;){let t=await et(e);if(t)return t;await D(300)}return null}async function et(e){return function(e){var t,a,n,o;let r,i=function(e){let t=e.filter(e=>{var t;let a;return a=(t=e).bundleId??"",B.has(a)||F.test(t.identifier??"")});if(t.length)return{nodes:t,source:"permission"};let a=function(e){let t=e.filter(e=>{var t;let a;return a=(t=e).bundleId??"",$.has(a)&&T.test(J(t))});if(0===t.length)return[];let a=z(e,t);return void 0===a?t:V(e,a).filter(e=>e.bundleId&&$.has(e.bundleId))}(e);return a.length?{nodes:a,source:"system-dialog"}:{nodes:function(e){var t;let a,n,s=e.filter(e=>{var t;return t=e.type??"",/(?:^|[.$])[^.]*Dialog$/i.test(t)}),o=e.filter(e=>H.test(e.identifier??"")),r=s.length?[...s,...o]:(a=(t=o).some(e=>q.test(e.identifier??"")),n=t.some(e=>!q.test(e.identifier??"")),a&&n?t:[]);if(0===r.length)return[];let i=z(e,r);return void 0===i?r:V(e,i)}(e),source:"native-dialog"}}(e),l=i.nodes;if(0===l.length)return null;let d=function(e){let t=new Set,a=[];for(let n of e){let e=J(n);if(!e||!n.rect||!W(n))continue;let o=e.trim().toLowerCase();if(!o||t.has(o))continue;t.add(o);let r=s(n.rect);a.push({label:e,x:r.x,y:r.y,role:function(e,t){var a;let n=(a=e.identifier??"",/(?:^|:)id\/button1$/i.test(a)?"accept":/(?:^|:)id\/button2$/i.test(a)?"dismiss":/(?:^|:)id\/button3$/i.test(a)?"neutral":/(?:^|:)id\/permission_allow/i.test(a)?"accept":/(?:^|:)id\/permission_deny/i.test(a)?"dismiss":null);return n||(j.test(t.trim())?"accept":X.test(t.trim())?"dismiss":"neutral")}(n,e)})}return a}(l),p=l.filter(e=>J(e)&&!W(e)),u=J((t=p).find(e=>/(?:^|:)id\/(?:alertTitle|permission_message)$/i.test(e.identifier??"")))||J(t[0]),c=(a=p,n=u,(r=a.map(e=>J(e)).filter(e=>e&&e!==n)).length?[...new Set(r)].join("\n"):void 0),f=(o=l,o.find(e=>e.bundleId)?.bundleId);return{alert:{...u?{title:u}:{},...c?{message:c}:{},buttons:d.map(e=>e.label),platform:"android",source:i.source,...f?{packageName:f}:{}},buttons:d}}((await i("snapshot_capture",async()=>await _(e,{helperWaitForIdleTimeoutMs:0,includeHiddenContentHints:!1}),{backend:"android",purpose:"alert"})).nodes)}function ea(e,t,a){return{kind:"alertHandled",platform:"android",action:e,handled:!0,alert:t,button:a,...R(`Alert ${e}ed`)}}async function en(e){var t;let{req:a,logPath:n,session:s,device:o}=e,r="accept"===(t=a.positionals?.[0])||"dismiss"===t||"wait"===t?t:"get",i=s?"frontmost-app"===s.surface?{surface:"frontmost-app"}:{bundleId:s.appBundleId,surface:s.surface}:{};if(!y("alert",o))return w("UNSUPPORTED_OPERATION","alert is not supported on this device");if("android"===o.platform){let t=I(a.positionals?.[1])??1e4;return ei(e,await Q(o,r,{timeoutMs:t}))}if("macos"===o.platform){let t=async e=>await h(e,i);return await es(e,r,t)}let l={verbose:a.flags?.verbose,logPath:n,traceLogPath:s?.trace?.outPath,requestId:a.meta?.requestId,iosXctestrunFile:a.flags?.iosXctestrunFile,iosXctestDerivedDataPath:a.flags?.iosXctestDerivedDataPath,iosXctestEnvDir:a.flags?.iosXctestEnvDir},d=async e=>await g(o,{command:"alert",action:e,appBundleId:s?.appBundleId},l);return await es(e,r,d)}async function es(e,t,a){if("wait"===t)return await eo(e,a);let n="accept"===t||"dismiss"===t?t:"get";return"accept"===n||"dismiss"===n?await er(e,n,a):ei(e,await a("get"))}async function eo(e,t){let a=I(e.req.positionals?.[1])??1e4,n=Date.now();for(;Date.now()-n<a;){try{return ei(e,await t("get"))}catch{}await D(300)}return w("COMMAND_FAILED","alert wait timed out")}async function er(e,t,a){var n,s;let o,i,l=Date.now();for(;Date.now()-l<2e3;){try{return ei(e,await a(t))}catch(t){i=t;let e=String(t?.message??"").toLowerCase();if(!e.includes("alert not found")&&!e.includes("no alert"))break}await D(300)}throw(n=i)instanceof r&&(s=n,(o=String(s?.message??"").toLowerCase()).includes("alert not found")||o.includes("no alert"))?new r(n.code,n.message,{...n.details??{},hint:"If the permission sheet is visible in snapshot or screenshot but alert reports no alert, take a scoped snapshot around the visible button label and use press @ref."}):n}function ei(e,t){return E(e.sessionStore,e.session,e.req,t),{ok:!0,data:t}}async function el(e){let{req:t,logPath:a,sessionStore:n,session:s,device:o,parsed:r}=e,{setting:i,state:l,appBundleId:d,permissionTarget:p,latitude:u,longitude:c}=r;if(!y("settings",o))return w("UNSUPPORTED_OPERATION","settings is not supported on this device");if("macos"===o.platform&&!C(i))return w("INVALID_ARGS",A(i));let m=d??s?.appBundleId;if("clear-app-state"===i&&!m)return w("INVALID_ARGS","settings clear-app-state requires an app id when no app is bound to the session");let h="clear-app-state"===i?[i,l,m??""]:"permission"===i?[i,l,p??"",t.positionals?.[3]??"",m??""]:"location"===i&&"set"===l?[i,l,u??"",c??"",m??""]:[i,l,m??""],g=await f(o,"settings",h,t.flags?.out,{...S(a,t.flags,m,s?.trace?.outPath)});return E(n,s,t,g??{setting:i,state:l}),{ok:!0,data:g??{setting:i,state:l}}}async function ed(e){var t,a;let n;if("snapshot"!==e.command||"android"!==e.device.platform)return;let s=o(e.error);if("COMMAND_FAILED"===(t=s).code&&(a=t,n=`${a.message}
2
- ${a.hint??""}`,/Android UI hierarchy dump timed out/i.test(n)||/Stock UIAutomator fallback was skipped/i.test(n)||/Android accessibility snapshots can be blocked/i.test(n)||function(e){if(!e||"object"!=typeof e)return!1;let t=String(e.errorType??""),a=String(e.message??"");return/TimeoutException/i.test(t)||/timed out/i.test(a)}(t.details?.helper)||function(e){var t;if(!e)return!1;let a=e?.timeoutMs,n=e?.cmd,s=Array.isArray(t=e?.args)?t.map(String):"string"==typeof t?t.split(/\s+/):[];return"number"==typeof a&&"adb"===n&&s.includes("uiautomator")&&s.includes("dump")}(t.details)))return{ok:!1,error:{...s,details:{...s.details??{},androidSnapshotTimeoutScreenshot:await ep(e)}}}}async function ep(e){try{var s,r,i;let o=await t.mkdtemp(n.join(a.tmpdir(),"agent-device-android-snapshot-timeout-")),d=n.join(o,"snapshot-timeout-overlay-refs.png"),p=(s=await f(e.device,"screenshot",[d],void 0,{...S(e.logPath,{screenshotNoStabilize:!0},e.session?.appBundleId,e.session?.trace?.outPath),surface:e.session?.surface}),r=d,(i=s,"object"==typeof i&&null!==i&&"path"in i&&"string"==typeof i.path)?s.path:r);await t.access(p);let u=await eu(p,e.session);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_captured",data:{path:p,overlayRefCount:"overlayRefCount"in u?u.overlayRefCount:void 0,overlayRefsAnnotated:"overlayRefsAnnotated"in u?u.overlayRefsAnnotated:void 0}}),u}catch(t){let e=o(t);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_failed",data:{error:e.message}}),{captureFailed:!0,error:e.message}}}async function eu(e,t){if(!t?.snapshot)return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"unavailable",overlayRefCount:0};try{let a=await v({screenshotPath:e,snapshot:t.snapshot});return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:a.length>0,overlayRefCount:a.length,overlayRefSource:"session-snapshot",overlayRefs:a}}catch(a){let t=o(a);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_overlay_failed",data:{path:e,error:t.message}}),{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"session-snapshot",overlayRefCount:0,overlayAnnotationError:t.message}}}async function ec(e){return await em({...e,command:"snapshot",unsupportedMessage:"snapshot is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.snapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw,forceFull:a.flags?.snapshotForceFull});return{data:s,record:{kind:"snapshot",nodes:s.nodes.length,truncated:s.truncated}}}})}async function ef(e){return await em({...e,command:"diff",unsupportedMessage:"diff is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.diffSnapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw});return{data:s,record:{kind:"diff",mode:"snapshot",baselineInitialized:s.baselineInitialized,summary:s.summary}}}})}async function em(e){var t,a;let{req:n,sessionName:s,logPath:o,sessionStore:i}=e,{session:l,device:d}=await O(i,s,n.flags);if(!y(e.command,d))return w("UNSUPPORTED_OPERATION",e.unsupportedMessage);let p=m(n.flags?.snapshotScope,l);if(!p.ok)return p;let f=(t=e.command,a=l,"ios"!==d.platform||a?.appBundleId?null:w("SESSION_NOT_FOUND",`iOS ${t} requires an active app session on the target device. Run open first (for example: open --session ${a?.name??"sim"} --platform ios --device "<name>" <app>).`));return f||await K(l,d,async()=>{var t,a;let f,m,h=function(e){let{req:t,sessionName:a,logPath:n,sessionStore:s,session:o,device:i,snapshotScope:l}=e;return U({backend:function(e){let{req:t,logPath:a,session:n,device:s,snapshotScope:o}=e;return{platform:s.platform,captureSnapshot:async(e,r)=>{let i=await c({device:s,session:n,flags:t.flags,outPath:r?.outPath??t.flags?.out,logPath:a,snapshotScope:o}),l=x(n);return{snapshot:i.snapshot,...N(i),...l?{snapshotDiagnostics:l}:{},appName:n?.appBundleId?n.appName??n.appBundleId:void 0,appBundleId:n?.appBundleId}}}}({req:t,logPath:n,session:o,device:i,snapshotScope:l}),...M("snapshot"),sessions:u({sessionName:a,getSession:()=>s.get(a),recordOptions:{includeSnapshot:!0},setRecord:e=>{var n;let o=function(e){if(!e.snapshot)throw new r("UNKNOWN","snapshot runtime did not produce session state");return e}(e),l=s.get(a);s.set(a,function(e){var t,a;let{current:n,sessionName:s,device:o,record:r,refScopedSnapshot:i}=e,l=(t=n,a=r,i&&a.snapshot?.nodes.length===0&&t?.snapshot!==void 0),d=l?n.snapshot:r.snapshot,p=P({session:n,sessionName:s,device:o,snapshot:d,appBundleId:r.appBundleId});return p.snapshotScopeSource=function(e){let{current:t,keepCurrentSnapshot:a,refScopedSnapshot:n}=e;if(n)return a?t?.snapshotScopeSource:t?.snapshotScopeSource??t?.snapshot}({current:n,keepCurrentSnapshot:l,refScopedSnapshot:i}),r.appName&&(p.appName=r.appName),p}({current:l,sessionName:a,device:i,record:o,refScopedSnapshot:(n=t,n.flags?.snapshotScope?.trim().startsWith("@")===!0)}))}})})}({req:n,sessionName:s,logPath:o,sessionStore:i,session:l,device:d,snapshotScope:p.scope});try{m=await e.execute({runtime:h,sessionName:s,req:n,snapshotScope:p.scope})}catch(a){let t=await ed({error:a,command:e.command,logPath:o,session:l,device:d});if(!t)throw a;return t}return(f=(t={req:n,sessionName:s,sessionStore:i,result:m.record}).sessionStore.get(t.sessionName))&&t.sessionStore.recordAction(f,{command:t.req.command,positionals:t.req.positionals??[],flags:t.req.flags??{},result:"snapshot"===(a=t.result).kind?{nodes:a.nodes,truncated:a.truncated}:{mode:a.mode,baselineInitialized:a.baselineInitialized,summary:a.summary}}),{ok:!0,data:m.data}})}let eh={snapshot:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>await ec({req:e,sessionName:t,logPath:a,sessionStore:n}),diff:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>e.positionals?.[0]!=="snapshot"?w("INVALID_ARGS","diff currently supports only: diff snapshot"):await ef({req:e,sessionName:t,logPath:a,sessionStore:n}),wait:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>await k({req:e,sessionName:t,logPath:a,sessionStore:n}),alert:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>{let{session:s,device:o}=await O(n,t,e.flags);return await K(s,o,async()=>await en({req:e,logPath:a,sessionStore:n,session:s,device:o}))},settings:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>{let s,o,r,i=(s=e.positionals?.[0]?.toLowerCase(),o=e.positionals?.[1]?.toLowerCase(),r=e.positionals?.[2]?.toLowerCase(),"clear-app-state"===s?{ok:!0,parsed:{setting:s,state:"clear",appBundleId:"clear"===o?e.positionals?.[2]:e.positionals?.[1]}}:s&&o&&("permission"!==s||r)&&("location"!==s||"set"!==o||e.positionals?.[2]&&e.positionals?.[3])?{ok:!0,parsed:{setting:s,state:o,permissionTarget:r,latitude:e.positionals?.[2],longitude:e.positionals?.[3]}}:w("INVALID_ARGS",b));if(!i.ok)return i;let{session:l,device:d}=await O(n,t,e.flags);return await K(l,d,async()=>await el({req:e,logPath:a,sessionStore:n,session:l,device:d,parsed:i.parsed}))}};async function ew(e){let t=eh[e.req.command];return t?await t(e):null}export{L as snapshot_namespaceObject};
1
+ import{__webpack_require__ as e}from"./rslib-runtime.js";import{promises as t}from"node:fs";import a from"node:os";import n from"node:path";import{isMacOs as s,centerOfRect as o,isIosFamily as r,publicPlatformString as i}from"./6849.js";import{normalizeError as l,AppError as d}from"./485.js";import{withDiagnosticTimer as p,emitDiagnostic as u}from"./8875.js";import{pressAndroid as c,backAndroid as f}from"./input-actions.js";import{runAppleRunnerCommand as m,createDaemonRuntimeSessionStore as h,captureSnapshot as w,dispatchCommand as g,requireCommandSupported as y,runMacOsAlertAction as _,errorResponse as v,annotateScreenshotWithRefs as S,resolveSnapshotScope as b,snapshotAndroid as I,context_contextFromFlags as A}from"./9722.js";import{SETTINGS_INVALID_ARGS_MESSAGE as C,parseTimeout as R,getUnsupportedMacOsSettingMessage as N,successText as M,isMacOsSettingSupported as k,snapshotCaptureAnnotationsFrom as D}from"./2948.js";import{sleep as K}from"./5560.js";import{buildAppleRunnerRequestOptions as x}from"./8414.js";import{buildSnapshotSession as P,dispatchWaitViaRuntime as E,createDaemonRuntimePolicy as U,recordIfSession as L,resolveSessionDevice as O,withSessionlessRunnerCleanup as B}from"./selector-runtime.js";import{createAgentDevice as H}from"./7871.js";import{summarizeSnapshotDiagnostics as $}from"./5913.js";var q={};e.r(q),e.d(q,{handleSnapshotCommands:()=>ev});let F=new Set(["com.android.permissioncontroller","com.google.android.permissioncontroller","com.google.android.packageinstaller","com.android.packageinstaller"]),j=new Set(["android","com.android.systemui"]),T=/^android:id\/(?:alertTitle|message|button[123]|parentPanel|buttonPanel|contentPanel)$/i,z=/^android:id\/button[123]$/i,G=/(?:^|:)id\/permission_/i,V=/\b(?:is(?:n't| not) responding|keeps stopping|has stopped|close app|app info)\b/i,W=/^(?:ok|allow|allow all|while using the app|only this time|yes|continue|save|confirm|turn on|open settings)$/i,J=/^(?:cancel|deny|don.t allow|don’t allow|not now|no|dismiss|close|close app|later|skip)$/i;function Q(e,t){let a=t[0];if(!a)return;let n=X(e,a.index);for(let a of t.slice(1)){let t=new Set(X(e,a.index));for(let e=n.length-1;e>=0;e-=1)t.has(n[e])||n.splice(e,1)}return n[n.length-1]}function X(e,t){let a=new Map(e.map(e=>[e.index,e])),n=[],s=a.get(t);for(;s;)n.push(s.index),s=void 0===s.parentIndex?void 0:a.get(s.parentIndex);return n.reverse()}function Y(e,t){let a=new Map;for(let t of e){if(void 0===t.parentIndex)continue;let e=a.get(t.parentIndex)??[];e.push(t),a.set(t.parentIndex,e)}let n=new Set([t]),s=[t];for(let e of s)for(let t of a.get(e)??[])n.has(t.index)||(n.add(t.index),s.push(t.index));return e.filter(e=>n.has(e.index))}function Z(e){let t=e.type??"",a=e.identifier??"";return!!(e.hittable||/\bbutton\b/i.test(t)||z.test(a)||/(?:^|:)id\/permission_(?:allow|deny)/i.test(a))}function ee(e){if(!e)return"";let t=[e.label,e.value].filter(e=>"string"==typeof e&&e.trim().length>0);return t[0]?.trim()??""}async function et(e,t,a={}){if("wait"===t)return await ea(e,a.timeoutMs??1e4);if("get"===t){var n;let t=await eo(e);return{kind:"alertStatus",platform:"android",action:"get",alert:n=t?.alert??null,...n?M("Alert visible"):M("No alert visible")}}return await en(e,t)}async function ea(e,t){let a=Date.now(),n=await es(e,t);if(!n)throw new d("COMMAND_FAILED","alert wait timed out");return{kind:"alertWait",platform:"android",action:"wait",alert:n.alert,waitedMs:Date.now()-a,...M("Alert visible")}}async function en(e,t){var a;let n,s,o=await es(e,2e3);if(!o)throw new d("COMMAND_FAILED","alert not found",{hint:"If a sheet is visible in snapshot but alert reports no alert, it is likely app-owned UI. Use snapshot -i and press the visible label/ref."});let r=(a=o.buttons,n="accept"===t?"accept":"dismiss",(s=a.find(e=>e.role===n))||("dismiss"===t?a.find(e=>"neutral"===e.role)??null:1===a.length?a[0]??null:null));if(r)return await c(e,r.x,r.y),er(t,o.alert,r.label);if("dismiss"===t)return await f(e),er(t,o.alert,"Back");throw new d("COMMAND_FAILED","alert accept found an alert but no accept button",{alert:o.alert,hint:"Inspect alert get --json for visible buttons, then use press by visible label/ref if needed."})}async function es(e,t){let a=Date.now();for(;Date.now()-a<t;){let t=await eo(e);if(t)return t;await K(300)}return null}async function eo(e){return function(e){var t,a,n,s;let r,i=function(e){let t=e.filter(e=>{var t;let a;return a=(t=e).bundleId??"",F.has(a)||G.test(t.identifier??"")});if(t.length)return{nodes:t,source:"permission"};let a=function(e){let t=e.filter(e=>{var t;let a;return a=(t=e).bundleId??"",j.has(a)&&V.test(ee(t))});if(0===t.length)return[];let a=Q(e,t);return void 0===a?t:Y(e,a).filter(e=>e.bundleId&&j.has(e.bundleId))}(e);return a.length?{nodes:a,source:"system-dialog"}:{nodes:function(e){var t;let a,n,s=e.filter(e=>{var t;return t=e.type??"",/(?:^|[.$])[^.]*Dialog$/i.test(t)}),o=e.filter(e=>T.test(e.identifier??"")),r=s.length?[...s,...o]:(a=(t=o).some(e=>z.test(e.identifier??"")),n=t.some(e=>!z.test(e.identifier??"")),a&&n?t:[]);if(0===r.length)return[];let i=Q(e,r);return void 0===i?r:Y(e,i)}(e),source:"native-dialog"}}(e),l=i.nodes;if(0===l.length)return null;let d=function(e){let t=new Set,a=[];for(let n of e){let e=ee(n);if(!e||!n.rect||!Z(n))continue;let s=e.trim().toLowerCase();if(!s||t.has(s))continue;t.add(s);let r=o(n.rect);a.push({label:e,x:r.x,y:r.y,role:function(e,t){var a;let n=(a=e.identifier??"",/(?:^|:)id\/button1$/i.test(a)?"accept":/(?:^|:)id\/button2$/i.test(a)?"dismiss":/(?:^|:)id\/button3$/i.test(a)?"neutral":/(?:^|:)id\/permission_allow/i.test(a)?"accept":/(?:^|:)id\/permission_deny/i.test(a)?"dismiss":null);return n||(W.test(t.trim())?"accept":J.test(t.trim())?"dismiss":"neutral")}(n,e)})}return a}(l),p=l.filter(e=>ee(e)&&!Z(e)),u=ee((t=p).find(e=>/(?:^|:)id\/(?:alertTitle|permission_message)$/i.test(e.identifier??"")))||ee(t[0]),c=(a=p,n=u,(r=a.map(e=>ee(e)).filter(e=>e&&e!==n)).length?[...new Set(r)].join("\n"):void 0),f=(s=l,s.find(e=>e.bundleId)?.bundleId);return{alert:{...u?{title:u}:{},...c?{message:c}:{},buttons:d.map(e=>e.label),platform:"android",source:i.source,...f?{packageName:f}:{}},buttons:d}}((await p("snapshot_capture",async()=>await I(e,{helperWaitForIdleTimeoutMs:0,includeHiddenContentHints:!1}),{backend:"android",purpose:"alert"})).nodes)}function er(e,t,a){return{kind:"alertHandled",platform:"android",action:e,handled:!0,alert:t,button:a,...M(`Alert ${e}ed`)}}async function ei(e){var t;let{req:a,logPath:n,session:o,device:r}=e,i="accept"===(t=a.positionals?.[0])||"dismiss"===t||"wait"===t?t:"get",l=o?"frontmost-app"===o.surface?{surface:"frontmost-app"}:{bundleId:o.appBundleId,surface:o.surface}:{},d=y("alert",r);if(d)return d;if("android"===r.platform){let t=R(a.positionals?.[1])??1e4;return eu(e,await et(r,i,{timeoutMs:t}))}if(s(r)){let t=async e=>await _(e,l);return await el(e,i,t)}let p=x({req:a,logPath:n,traceLogPath:o?.trace?.outPath}),u=async e=>await m(r,{command:"alert",action:e,appBundleId:o?.appBundleId},p);return await el(e,i,u)}async function el(e,t,a){if("wait"===t)return await ed(e,a);let n="accept"===t||"dismiss"===t?t:"get";return"accept"===n||"dismiss"===n?await ep(e,n,a):eu(e,await a("get"))}async function ed(e,t){let a=R(e.req.positionals?.[1])??1e4,n=Date.now();for(;Date.now()-n<a;){try{return eu(e,await t("get"))}catch{}await K(300)}return v("COMMAND_FAILED","alert wait timed out")}async function ep(e,t,a){var n,s;let o,r,i=Date.now();for(;Date.now()-i<2e3;){try{return eu(e,await a(t))}catch(t){r=t;let e=String(t?.message??"").toLowerCase();if(!e.includes("alert not found")&&!e.includes("no alert"))break}await K(300)}throw(n=r)instanceof d&&(s=n,(o=String(s?.message??"").toLowerCase()).includes("alert not found")||o.includes("no alert"))?new d(n.code,n.message,{...n.details??{},hint:"If the permission sheet is visible in snapshot or screenshot but alert reports no alert, take a scoped snapshot around the visible button label and use press @ref."}):n}function eu(e,t){return L(e.sessionStore,e.session,e.req,t),{ok:!0,data:t}}async function ec(e){let{req:t,logPath:a,sessionStore:n,session:o,device:r,parsed:i}=e,{setting:l,state:d,appBundleId:p,permissionTarget:u,latitude:c,longitude:f}=i,m=y("settings",r);if(m)return m;if(s(r)&&!k(l))return v("INVALID_ARGS",N(l));let h=p??o?.appBundleId;if("clear-app-state"===l&&!h)return v("INVALID_ARGS","settings clear-app-state requires an app id when no app is bound to the session");let w="clear-app-state"===l?[l,d,h??""]:"permission"===l?[l,d,u??"",t.positionals?.[3]??"",h??""]:"location"===l&&"set"===d?[l,d,c??"",f??"",h??""]:[l,d,h??""],_=await g(r,"settings",w,t.flags?.out,{...A(a,t.flags,h,o?.trace?.outPath)});return L(n,o,t,_??{setting:l,state:d}),{ok:!0,data:_??{setting:l,state:d}}}async function ef(e){var t,a;let n;if("snapshot"!==e.command||"android"!==e.device.platform)return;let s=l(e.error);if("COMMAND_FAILED"===(t=s).code&&(a=t,n=`${a.message}
2
+ ${a.hint??""}`,/Android UI hierarchy dump timed out/i.test(n)||/Stock UIAutomator fallback was skipped/i.test(n)||/Android accessibility snapshots can be blocked/i.test(n)||function(e){if(!e||"object"!=typeof e)return!1;let t=String(e.errorType??""),a=String(e.message??"");return/TimeoutException/i.test(t)||/timed out/i.test(a)}(t.details?.helper)||function(e){var t;if(!e)return!1;let a=e?.timeoutMs,n=e?.cmd,s=Array.isArray(t=e?.args)?t.map(String):"string"==typeof t?t.split(/\s+/):[];return"number"==typeof a&&"adb"===n&&s.includes("uiautomator")&&s.includes("dump")}(t.details)))return{ok:!1,error:{...s,details:{...s.details??{},androidSnapshotTimeoutScreenshot:await em(e)}}}}async function em(e){try{var s,o,r;let i=await t.mkdtemp(n.join(a.tmpdir(),"agent-device-android-snapshot-timeout-")),l=n.join(i,"snapshot-timeout-overlay-refs.png"),d=(s=await g(e.device,"screenshot",[l],void 0,{...A(e.logPath,{screenshotNoStabilize:!0},e.session?.appBundleId,e.session?.trace?.outPath),surface:e.session?.surface}),o=l,(r=s,"object"==typeof r&&null!==r&&"path"in r&&"string"==typeof r.path)?s.path:o);await t.access(d);let p=await eh(d,e.session);return u({level:"warn",phase:"android_snapshot_timeout_screenshot_captured",data:{path:d,overlayRefCount:"overlayRefCount"in p?p.overlayRefCount:void 0,overlayRefsAnnotated:"overlayRefsAnnotated"in p?p.overlayRefsAnnotated:void 0}}),p}catch(t){let e=l(t);return u({level:"warn",phase:"android_snapshot_timeout_screenshot_failed",data:{error:e.message}}),{captureFailed:!0,error:e.message}}}async function eh(e,t){if(!t?.snapshot)return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"unavailable",overlayRefCount:0};try{let a=await S({screenshotPath:e,snapshot:t.snapshot});return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:a.length>0,overlayRefCount:a.length,overlayRefSource:"session-snapshot",overlayRefs:a}}catch(a){let t=l(a);return u({level:"warn",phase:"android_snapshot_timeout_screenshot_overlay_failed",data:{path:e,error:t.message}}),{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"session-snapshot",overlayRefCount:0,overlayAnnotationError:t.message}}}async function ew(e){return await ey({...e,command:"snapshot",unsupportedMessage:"snapshot is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.snapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw,forceFull:a.flags?.snapshotForceFull});return{data:s,record:{kind:"snapshot",nodes:s.nodes.length,truncated:s.truncated}}}})}async function eg(e){return await ey({...e,command:"diff",unsupportedMessage:"diff is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.diffSnapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw});return{data:s,record:{kind:"diff",mode:"snapshot",baselineInitialized:s.baselineInitialized,summary:s.summary}}}})}async function ey(e){var t,a;let{req:n,sessionName:s,logPath:o,sessionStore:l}=e,{session:p,device:u}=await O(l,s,n.flags),c=y(e.command,u,{message:e.unsupportedMessage});if(c)return c;let f=b(n.flags?.snapshotScope,p);if(!f.ok)return f;let m=(t=e.command,a=p,!r(u)||a?.appBundleId?null:v("SESSION_NOT_FOUND",`iOS ${t} requires an active app session on the target device. Run open first (for example: open --session ${a?.name??"sim"} --platform ios --device "<name>" <app>).`));return m||await B(p,u,async()=>{var t,a;let r,c,m=function(e){let{req:t,sessionName:a,logPath:n,sessionStore:s,session:o,device:r,snapshotScope:l}=e;return H({backend:function(e){let{req:t,logPath:a,session:n,device:s,snapshotScope:o}=e;return{platform:i(s),captureSnapshot:async(e,r)=>{let i=await w({device:s,session:n,flags:t.flags,outPath:r?.outPath??t.flags?.out,logPath:a,snapshotScope:o}),l=$(n);return{snapshot:i.snapshot,...D(i),...l?{snapshotDiagnostics:l}:{},appName:n?.appBundleId?n.appName??n.appBundleId:void 0,appBundleId:n?.appBundleId}}}}({req:t,logPath:n,session:o,device:r,snapshotScope:l}),...U("snapshot"),sessions:h({sessionName:a,getSession:()=>s.get(a),recordOptions:{includeSnapshot:!0},setRecord:e=>{var n;let o=function(e){if(!e.snapshot)throw new d("UNKNOWN","snapshot runtime did not produce session state");return e}(e),i=s.get(a);s.set(a,function(e){var t,a;let{current:n,sessionName:s,device:o,record:r,refScopedSnapshot:i}=e,l=(t=n,a=r,i&&a.snapshot?.nodes.length===0&&t?.snapshot!==void 0),d=l?n.snapshot:r.snapshot,p=P({session:n,sessionName:s,device:o,snapshot:d,appBundleId:r.appBundleId});return p.snapshotScopeSource=function(e){let{current:t,keepCurrentSnapshot:a,refScopedSnapshot:n}=e;if(n)return a?t?.snapshotScopeSource:t?.snapshotScopeSource??t?.snapshot}({current:n,keepCurrentSnapshot:l,refScopedSnapshot:i}),r.appName&&(p.appName=r.appName),p}({current:i,sessionName:a,device:r,record:o,refScopedSnapshot:(n=t,n.flags?.snapshotScope?.trim().startsWith("@")===!0)}))}})})}({req:n,sessionName:s,logPath:o,sessionStore:l,session:p,device:u,snapshotScope:f.scope});try{c=await e.execute({runtime:m,sessionName:s,req:n,snapshotScope:f.scope})}catch(a){let t=await ef({error:a,command:e.command,logPath:o,session:p,device:u});if(!t)throw a;return t}return(r=(t={req:n,sessionName:s,sessionStore:l,result:c.record}).sessionStore.get(t.sessionName))&&t.sessionStore.recordAction(r,{command:t.req.command,positionals:t.req.positionals??[],flags:t.req.flags??{},result:"snapshot"===(a=t.result).kind?{nodes:a.nodes,truncated:a.truncated}:{mode:a.mode,baselineInitialized:a.baselineInitialized,summary:a.summary}}),{ok:!0,data:c.data}})}let e_={snapshot:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>await ew({req:e,sessionName:t,logPath:a,sessionStore:n}),diff:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>e.positionals?.[0]!=="snapshot"?v("INVALID_ARGS","diff currently supports only: diff snapshot"):await eg({req:e,sessionName:t,logPath:a,sessionStore:n}),wait:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>await E({req:e,sessionName:t,logPath:a,sessionStore:n}),alert:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>{let{session:s,device:o}=await O(n,t,e.flags);return await B(s,o,async()=>await ei({req:e,logPath:a,sessionStore:n,session:s,device:o}))},settings:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>{let s,o,r,i=(s=e.positionals?.[0]?.toLowerCase(),o=e.positionals?.[1]?.toLowerCase(),r=e.positionals?.[2]?.toLowerCase(),"clear-app-state"===s?{ok:!0,parsed:{setting:s,state:"clear",appBundleId:"clear"===o?e.positionals?.[2]:e.positionals?.[1]}}:s&&o&&("permission"!==s||r)&&("location"!==s||"set"!==o||e.positionals?.[2]&&e.positionals?.[3])?{ok:!0,parsed:{setting:s,state:o,permissionTarget:r,latitude:e.positionals?.[2],longitude:e.positionals?.[3]}}:v("INVALID_ARGS",C));if(!i.ok)return i;let{session:l,device:d}=await O(n,t,e.flags);return await B(l,d,async()=>await ec({req:e,logPath:a,sessionStore:n,session:l,device:d,parsed:i.parsed}))}};async function ev(e){let t=e_[e.req.command];return t?await t(e):null}export{q as snapshot_namespaceObject};
package/dist/src/web.js CHANGED
@@ -1 +1 @@
1
- import{AppError as e}from"./733.js";import{withDiagnosticTimer as t}from"./432.js";import{resolveWebProvider as r}from"./provider.js";function o(){return{open:(e,t)=>r().open(t?.url??e,{url:t?.url}),openDevice:()=>r().open("about:blank"),close:e=>r().close(e),tap:(e,t)=>r().click(e,t),doubleTap:()=>n("doubleTap"),swipe:()=>n("swipe"),pan:()=>n("pan"),fling:()=>n("fling"),longPress:()=>n("longPress"),focus:(e,t)=>r().click(e,t),type:(e,t)=>r().typeText(e,{delayMs:t}),fill:(e,t,o,n)=>r().fill(e,t,o,{delayMs:n}),scroll:(e,t)=>r().scroll(e,t),pinch:()=>n("pinch"),screenshot:(e,t)=>r().screenshot(e,t),setViewport:(e,t)=>r().setViewport(e,t),snapshot:async e=>{let o=await t("snapshot_capture",async()=>await r().snapshot(e),{backend:"web"});return{nodes:o.nodes,truncated:o.truncated??!1,backend:"web"}},back:()=>n("back"),home:()=>n("home"),rotate:()=>n("rotate"),rotateGesture:()=>n("rotateGesture"),transformGesture:()=>n("transformGesture"),appSwitcher:()=>n("appSwitcher"),readClipboard:()=>n("readClipboard"),writeClipboard:()=>n("writeClipboard"),setSetting:()=>n("setSetting")}}async function n(t){throw new e("UNSUPPORTED_OPERATION",`${t} is not supported on web`)}export{o as createWebInteractor};
1
+ import{AppError as e}from"./485.js";import{withDiagnosticTimer as t}from"./8875.js";import{resolveWebProvider as r}from"./provider.js";function o(){return{open:(e,t)=>r().open(t?.url??e,{url:t?.url}),openDevice:()=>r().open("about:blank"),close:e=>r().close(e),tap:(e,t)=>r().click(e,t),doubleTap:()=>n("doubleTap"),swipe:()=>n("swipe"),pan:()=>n("pan"),fling:()=>n("fling"),longPress:()=>n("longPress"),focus:(e,t)=>r().click(e,t),type:(e,t)=>r().typeText(e,{delayMs:t}),fill:(e,t,o,n)=>r().fill(e,t,o,{delayMs:n}),scroll:(e,t)=>r().scroll(e,t),pinch:()=>n("pinch"),screenshot:(e,t)=>r().screenshot(e,t),setViewport:(e,t)=>r().setViewport(e,t),snapshot:async e=>{let o=await t("snapshot_capture",async()=>await r().snapshot(e),{backend:"web"});return{nodes:o.nodes,truncated:o.truncated??!1,backend:"web"}},back:()=>n("back"),home:()=>n("home"),rotate:()=>n("rotate"),rotateGesture:()=>n("rotateGesture"),transformGesture:()=>n("transformGesture"),appSwitcher:()=>n("appSwitcher"),readClipboard:()=>n("readClipboard"),writeClipboard:()=>n("writeClipboard"),setSetting:()=>n("setSetting")}}async function n(t){throw new e("UNSUPPORTED_OPERATION",`${t} is not supported on web`)}export{o as createWebInteractor};
@@ -0,0 +1,297 @@
1
+ import AVFoundation
2
+ import AudioToolbox
3
+ import CoreMedia
4
+ import Foundation
5
+ import ScreenCaptureKit
6
+
7
+ private let audioProbeSilenceDb = -90
8
+
9
+ struct AudioProbeResponse: Codable {
10
+ let audio: String
11
+ let state: String
12
+ let active: Bool
13
+ let heard: Bool
14
+ let source: String
15
+ let backend: String
16
+ let durationMs: Int
17
+ let elapsedMs: Int
18
+ let bucketMs: Int
19
+ let sampleCount: Int
20
+ let sourceCount: Int
21
+ let rmsDbfs: [Int]
22
+ let peakDbfs: [Int]
23
+ let startedAt: String
24
+ let stoppedAt: String?
25
+ let reason: String?
26
+ }
27
+
28
+ private struct AudioProbeBucket {
29
+ var totalSquares: Double = 0
30
+ var totalSamples: Int = 0
31
+ var peak: Double = 0
32
+ }
33
+
34
+ private final class AudioProbeStatusWriter {
35
+ private let outPath: String
36
+ private let startedAt = Date()
37
+ private let durationMs: Int
38
+ private let bucketMs: Int
39
+ private let lock = NSLock()
40
+ private var current = AudioProbeBucket()
41
+ private var rmsDbfs: [Int] = []
42
+ private var peakDbfs: [Int] = []
43
+ private var heard = false
44
+ private var stoppedAt: Date?
45
+ private var reason: String?
46
+
47
+ init(outPath: String, durationMs: Int, bucketMs: Int) {
48
+ self.outPath = outPath
49
+ self.durationMs = durationMs
50
+ self.bucketMs = bucketMs
51
+ }
52
+
53
+ func add(sampleBuffer: CMSampleBuffer) {
54
+ guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) else {
55
+ return
56
+ }
57
+ let format = AVAudioFormat(cmAudioFormatDescription: formatDescription)
58
+ let frameCount = AVAudioFrameCount(CMSampleBufferGetNumSamples(sampleBuffer))
59
+ guard frameCount > 0,
60
+ let pcmBuffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount)
61
+ else {
62
+ return
63
+ }
64
+ let status = CMSampleBufferCopyPCMDataIntoAudioBufferList(
65
+ sampleBuffer,
66
+ at: 0,
67
+ frameCount: Int32(frameCount),
68
+ into: pcmBuffer.mutableAudioBufferList
69
+ )
70
+ guard status == noErr else {
71
+ return
72
+ }
73
+ pcmBuffer.frameLength = frameCount
74
+ add(audioBufferList: pcmBuffer.mutableAudioBufferList, format: format)
75
+ }
76
+
77
+ func flushRunning() throws {
78
+ lock.lock()
79
+ appendCurrentBucket()
80
+ let response = buildResponse(state: "running", active: true)
81
+ lock.unlock()
82
+ try write(response)
83
+ }
84
+
85
+ func finish(reason: String) throws {
86
+ lock.lock()
87
+ if current.totalSamples > 0 || rmsDbfs.isEmpty {
88
+ appendCurrentBucket()
89
+ }
90
+ self.stoppedAt = Date()
91
+ self.reason = reason
92
+ let response = buildResponse(state: "stopped", active: false)
93
+ lock.unlock()
94
+ try write(response)
95
+ }
96
+
97
+ private func add(audioBufferList: UnsafeMutablePointer<AudioBufferList>, format: AVAudioFormat) {
98
+ let streamDescription = format.streamDescription.pointee
99
+ let bitsPerChannel = Int(streamDescription.mBitsPerChannel)
100
+ guard bitsPerChannel > 0 else {
101
+ return
102
+ }
103
+ let bytesPerSample = max(1, bitsPerChannel / 8)
104
+ let isFloat = (streamDescription.mFormatFlags & kAudioFormatFlagIsFloat) != 0
105
+ let isSignedInteger = (streamDescription.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0
106
+ var bucket = AudioProbeBucket()
107
+ for buffer in UnsafeMutableAudioBufferListPointer(audioBufferList) {
108
+ guard let data = buffer.mData else {
109
+ continue
110
+ }
111
+ let sampleCount = Int(buffer.mDataByteSize) / bytesPerSample
112
+ if isFloat && bytesPerSample == 4 {
113
+ let samples = data.bindMemory(to: Float.self, capacity: sampleCount)
114
+ for index in 0..<sampleCount {
115
+ bucket.add(Double(samples[index]))
116
+ }
117
+ } else if isSignedInteger && bytesPerSample == 2 {
118
+ let samples = data.bindMemory(to: Int16.self, capacity: sampleCount)
119
+ for index in 0..<sampleCount {
120
+ bucket.add(Double(samples[index]) / Double(Int16.max))
121
+ }
122
+ } else if isSignedInteger && bytesPerSample == 4 {
123
+ let samples = data.bindMemory(to: Int32.self, capacity: sampleCount)
124
+ for index in 0..<sampleCount {
125
+ bucket.add(Double(samples[index]) / Double(Int32.max))
126
+ }
127
+ }
128
+ }
129
+ guard bucket.totalSamples > 0 else {
130
+ return
131
+ }
132
+ lock.lock()
133
+ current.totalSquares += bucket.totalSquares
134
+ current.totalSamples += bucket.totalSamples
135
+ current.peak = max(current.peak, bucket.peak)
136
+ lock.unlock()
137
+ }
138
+
139
+ private func appendCurrentBucket() {
140
+ let rms = current.totalSamples > 0
141
+ ? sqrt(current.totalSquares / Double(current.totalSamples))
142
+ : 0
143
+ let rmsDb = dbfs(rms)
144
+ let peakDb = dbfs(current.peak)
145
+ rmsDbfs.append(rmsDb)
146
+ peakDbfs.append(peakDb)
147
+ if rmsDb > audioProbeSilenceDb || peakDb > audioProbeSilenceDb {
148
+ heard = true
149
+ }
150
+ current = AudioProbeBucket()
151
+ }
152
+
153
+ private func buildResponse(state: String, active: Bool) -> AudioProbeResponse {
154
+ let end = stoppedAt ?? Date()
155
+ let elapsed = min(durationMs, max(0, Int(end.timeIntervalSince(startedAt) * 1000)))
156
+ return AudioProbeResponse(
157
+ audio: "probe",
158
+ state: state,
159
+ active: active,
160
+ heard: heard,
161
+ source: "system-audio",
162
+ backend: "macos-screencapturekit",
163
+ durationMs: durationMs,
164
+ elapsedMs: elapsed,
165
+ bucketMs: bucketMs,
166
+ sampleCount: rmsDbfs.count,
167
+ sourceCount: 1,
168
+ rmsDbfs: rmsDbfs,
169
+ peakDbfs: peakDbfs,
170
+ startedAt: iso8601(startedAt),
171
+ stoppedAt: stoppedAt.map(iso8601),
172
+ reason: reason
173
+ )
174
+ }
175
+
176
+ private func write(_ response: AudioProbeResponse) throws {
177
+ let outputURL = URL(fileURLWithPath: outPath)
178
+ try FileManager.default.createDirectory(
179
+ at: outputURL.deletingLastPathComponent(),
180
+ withIntermediateDirectories: true
181
+ )
182
+ let data = try JSONEncoder().encode(response)
183
+ try data.write(to: outputURL, options: .atomic)
184
+ }
185
+ }
186
+
187
+ private final class AudioProbeStreamOutput: NSObject, SCStreamOutput {
188
+ private let writer: AudioProbeStatusWriter
189
+
190
+ init(writer: AudioProbeStatusWriter) {
191
+ self.writer = writer
192
+ }
193
+
194
+ func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
195
+ guard type == .audio else {
196
+ return
197
+ }
198
+ writer.add(sampleBuffer: sampleBuffer)
199
+ }
200
+ }
201
+
202
+ extension AudioProbeBucket {
203
+ fileprivate mutating func add(_ value: Double) {
204
+ guard value.isFinite else {
205
+ return
206
+ }
207
+ let clipped = min(1, max(-1, value))
208
+ totalSquares += clipped * clipped
209
+ totalSamples += 1
210
+ peak = max(peak, abs(clipped))
211
+ }
212
+ }
213
+
214
+ func runAudioProbe(durationMs: Int, bucketMs: Int, outPath: String) throws -> AudioProbeResponse {
215
+ guard #available(macOS 13.0, *) else {
216
+ throw HelperError.commandFailed("audio probe requires macOS 13 or newer")
217
+ }
218
+ let semaphore = DispatchSemaphore(value: 0)
219
+ var response: AudioProbeResponse?
220
+ var runError: Error?
221
+ Task {
222
+ do {
223
+ response = try await runAudioProbeAsync(durationMs: durationMs, bucketMs: bucketMs, outPath: outPath)
224
+ } catch {
225
+ runError = error
226
+ }
227
+ semaphore.signal()
228
+ }
229
+ semaphore.wait()
230
+ if let runError {
231
+ throw runError
232
+ }
233
+ guard let response else {
234
+ throw HelperError.commandFailed("audio probe failed")
235
+ }
236
+ return response
237
+ }
238
+
239
+ @available(macOS 13.0, *)
240
+ private func runAudioProbeAsync(durationMs: Int, bucketMs: Int, outPath: String) async throws -> AudioProbeResponse {
241
+ let content: SCShareableContent
242
+ do {
243
+ content = try await SCShareableContent.current
244
+ } catch {
245
+ throw HelperError.commandFailed(
246
+ "audio probe requires Screen Recording permission on macOS",
247
+ details: ["permission": "screen-recording", "error": error.localizedDescription]
248
+ )
249
+ }
250
+ guard let display = content.displays.first else {
251
+ throw HelperError.commandFailed("audio probe could not resolve a macOS display")
252
+ }
253
+
254
+ let configuration = SCStreamConfiguration()
255
+ configuration.width = max(2, display.width)
256
+ configuration.height = max(2, display.height)
257
+ configuration.minimumFrameInterval = CMTime(value: 1, timescale: 1)
258
+ configuration.queueDepth = 1
259
+ configuration.capturesAudio = true
260
+ configuration.sampleRate = 48_000
261
+ configuration.channelCount = 2
262
+ configuration.excludesCurrentProcessAudio = true
263
+
264
+ let filter = SCContentFilter(display: display, excludingWindows: [])
265
+ let writer = AudioProbeStatusWriter(outPath: outPath, durationMs: durationMs, bucketMs: bucketMs)
266
+ let output = AudioProbeStreamOutput(writer: writer)
267
+ let stream = SCStream(filter: filter, configuration: configuration, delegate: nil)
268
+ let queue = DispatchQueue(label: "com.callstack.agent-device.audio-probe")
269
+ try stream.addStreamOutput(output, type: .audio, sampleHandlerQueue: queue)
270
+ try await stream.startCapture()
271
+ try writer.flushRunning()
272
+
273
+ let deadline = Date().addingTimeInterval(Double(durationMs) / 1000)
274
+ while Date() < deadline {
275
+ let remainingMs = Int(deadline.timeIntervalSinceNow * 1000)
276
+ let sleepMs = max(1, min(bucketMs, remainingMs))
277
+ try await Task.sleep(nanoseconds: UInt64(sleepMs) * 1_000_000)
278
+ try writer.flushRunning()
279
+ }
280
+
281
+ try await stream.stopCapture()
282
+ try writer.finish(reason: "completed")
283
+ let data = try Data(contentsOf: URL(fileURLWithPath: outPath))
284
+ return try JSONDecoder().decode(AudioProbeResponse.self, from: data)
285
+ }
286
+
287
+ private func dbfs(_ value: Double) -> Int {
288
+ if !value.isFinite || value <= 0 {
289
+ return audioProbeSilenceDb
290
+ }
291
+ let db = Int((20 * log10(value)).rounded())
292
+ return max(audioProbeSilenceDb, min(0, db))
293
+ }
294
+
295
+ private func iso8601(_ date: Date) -> String {
296
+ ISO8601DateFormatter().string(from: date)
297
+ }
@@ -120,6 +120,8 @@ struct AgentDeviceMacOSHelper {
120
120
  return try handlePress(arguments: Array(arguments.dropFirst()))
121
121
  case "screenshot":
122
122
  return try handleScreenshot(arguments: Array(arguments.dropFirst()))
123
+ case "audio-probe":
124
+ return try handleAudioProbe(arguments: Array(arguments.dropFirst()))
123
125
  default:
124
126
  throw HelperError.invalidArgs("unknown command: \(command)")
125
127
  }
@@ -390,6 +392,27 @@ struct AgentDeviceMacOSHelper {
390
392
  try captureSurfaceScreenshot(surface: surface, outPath: outPath, fullscreen: fullscreen)
391
393
  return SuccessEnvelope(data: ScreenshotResponse(path: outPath, surface: surface, fullscreen: fullscreen))
392
394
  }
395
+
396
+ static func handleAudioProbe(arguments: [String]) throws -> any Encodable {
397
+ let durationMs = intOption(arguments: arguments, name: "--duration-ms") ?? 10_000
398
+ let bucketMs = intOption(arguments: arguments, name: "--bucket-ms") ?? 1_000
399
+ guard durationMs >= 100, durationMs <= 120_000 else {
400
+ throw HelperError.invalidArgs("audio-probe --duration-ms must be in range 100..120000")
401
+ }
402
+ guard bucketMs >= 100, bucketMs <= 10_000 else {
403
+ throw HelperError.invalidArgs("audio-probe --bucket-ms must be in range 100..10000")
404
+ }
405
+ guard let outPath = optionValue(arguments: arguments, name: "--out")?
406
+ .trimmingCharacters(in: .whitespacesAndNewlines),
407
+ !outPath.isEmpty
408
+ else {
409
+ throw HelperError.invalidArgs("audio-probe requires --out <path>")
410
+ }
411
+
412
+ return SuccessEnvelope(
413
+ data: try runAudioProbe(durationMs: durationMs, bucketMs: bucketMs, outPath: outPath)
414
+ )
415
+ }
393
416
  }
394
417
 
395
418
  private func optionValue(arguments: [String], name: String) -> String? {
@@ -399,6 +422,13 @@ private func optionValue(arguments: [String], name: String) -> String? {
399
422
  return arguments[index + 1]
400
423
  }
401
424
 
425
+ private func intOption(arguments: [String], name: String) -> Int? {
426
+ guard let value = optionValue(arguments: arguments, name: name) else {
427
+ return nil
428
+ }
429
+ return Int(value)
430
+ }
431
+
402
432
  private func readTextAtPosition(bundleId: String?, surface: String?, x: Double, y: Double) throws -> String {
403
433
  let targetApp: NSRunningApplication?
404
434
  if surface == "frontmost-app" || (surface == nil && bundleId != nil) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
4
4
  "description": "Agent-native CLI for AI app automation across iOS, Android, tvOS, Android TV, macOS, Linux, and web.",
5
5
  "mcpName": "io.github.callstackincubator/agent-device",
6
6
  "license": "MIT",
@@ -50,10 +50,6 @@
50
50
  "import": "./dist/src/android-adb.js",
51
51
  "types": "./dist/src/android-adb.d.ts"
52
52
  },
53
- "./android-snapshot-helper": {
54
- "import": "./dist/src/android-snapshot-helper.js",
55
- "types": "./dist/src/android-snapshot-helper.d.ts"
56
- },
57
53
  "./contracts": {
58
54
  "import": "./dist/src/contracts.js",
59
55
  "types": "./dist/src/contracts.d.ts"
@@ -81,6 +77,7 @@
81
77
  "clean:xcuitest:ios": "node scripts/clean-xcuitest-derived.mjs ios",
82
78
  "clean:xcuitest:macos": "node scripts/clean-xcuitest-derived.mjs macos",
83
79
  "clean:xcuitest:tvos": "node scripts/clean-xcuitest-derived.mjs tvos",
80
+ "clean:xcuitest:visionos": "node scripts/clean-xcuitest-derived.mjs visionos",
84
81
  "build:node": "pnpm build && pnpm clean:daemon",
85
82
  "build:xcuitest": "pnpm build:xcuitest:ios && pnpm build:xcuitest:macos",
86
83
  "build:xcuitest:ios": "AGENT_DEVICE_XCUITEST_PLATFORM=ios sh ./scripts/build-xcuitest-apple.sh",
@@ -88,6 +85,8 @@
88
85
  "build:xcuitest:macos": "AGENT_DEVICE_XCUITEST_PLATFORM=macos sh ./scripts/build-xcuitest-apple.sh",
89
86
  "build:xcuitest:tvos": "AGENT_DEVICE_XCUITEST_PLATFORM=tvos sh ./scripts/build-xcuitest-apple.sh",
90
87
  "build:xcuitest:tvos:clean": "pnpm clean:xcuitest:tvos && pnpm build:xcuitest:tvos",
88
+ "build:xcuitest:visionos": "AGENT_DEVICE_XCUITEST_PLATFORM=visionos sh ./scripts/build-xcuitest-apple.sh",
89
+ "build:xcuitest:visionos:clean": "pnpm clean:xcuitest:visionos && pnpm build:xcuitest:visionos",
91
90
  "build:android-snapshot-helper": "sh ./scripts/build-android-snapshot-helper.sh $(node -p \"require('./package.json').version\") .tmp/android-snapshot-helper",
92
91
  "package:android-snapshot-helper": "sh ./scripts/package-android-snapshot-helper.sh $(node -p \"require('./package.json').version\") v$(node -p \"require('./package.json').version\") .tmp/android-snapshot-helper",
93
92
  "package:android-snapshot-helper:npm": "rm -rf android-snapshot-helper/dist && sh ./scripts/package-android-snapshot-helper.sh $(node -p \"require('./package.json').version\") v$(node -p \"require('./package.json').version\") android-snapshot-helper/dist",
@@ -109,11 +108,12 @@
109
108
  "fallow:all": "fallow --summary",
110
109
  "fallow:baseline": "(fallow dead-code --save-baseline fallow-baselines/dead-code.json --summary || true) && (fallow health --save-baseline fallow-baselines/health.json --summary || true)",
111
110
  "check:fallow": "fallow audit",
111
+ "check:layering": "node --experimental-strip-types scripts/layering/check.ts",
112
112
  "check:quick": "pnpm lint && pnpm typecheck",
113
113
  "sync:mcp-metadata": "node scripts/sync-mcp-metadata.mjs",
114
114
  "check:mcp-metadata": "node scripts/sync-mcp-metadata.mjs --check",
115
115
  "version": "node scripts/sync-mcp-metadata.mjs && git add server.json",
116
- "check:tooling": "pnpm lint && pnpm typecheck && pnpm check:mcp-metadata && pnpm build",
116
+ "check:tooling": "pnpm lint && pnpm typecheck && pnpm check:layering && pnpm check:mcp-metadata && pnpm build",
117
117
  "check:unit": "pnpm test:unit && pnpm test:smoke",
118
118
  "check": "pnpm check:tooling && pnpm check:fallow && pnpm check:unit",
119
119
  "prepack": "pnpm check:mcp-metadata && pnpm build:all && pnpm package:android-snapshot-helper:npm && pnpm package:android-multitouch-helper:npm",
@@ -149,17 +149,21 @@
149
149
  "files": [
150
150
  "bin",
151
151
  "dist",
152
- "ios-runner",
153
- "!ios-runner/**/.build",
154
- "!ios-runner/**/.swiftpm",
155
- "!ios-runner/**/xcuserdata",
156
- "!ios-runner/**/*.xcuserstate",
152
+ "apple-runner",
153
+ "!apple-runner/**/.build",
154
+ "!apple-runner/**/.swiftpm",
155
+ "!apple-runner/**/xcuserdata",
156
+ "!apple-runner/**/*.xcuserstate",
157
+ "!apple-runner/README.md",
158
+ "!apple-runner/RUNNER_PROTOCOL.md",
157
159
  "macos-helper",
158
160
  "!macos-helper/**/.build",
159
161
  "android-snapshot-helper/dist",
160
162
  "!android-snapshot-helper/dist/*.idsig",
163
+ "!android-snapshot-helper/README.md",
161
164
  "android-multitouch-helper/dist",
162
165
  "!android-multitouch-helper/dist/*.idsig",
166
+ "!android-multitouch-helper/README.md",
163
167
  "src/platforms/linux/atspi-dump.py",
164
168
  "skills",
165
169
  "server.json",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/callstack/agent-device",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.18.0",
10
+ "version": "0.18.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "agent-device",
15
- "version": "0.18.0",
15
+ "version": "0.18.2",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -1,41 +0,0 @@
1
- # Android MultiTouch Helper
2
-
3
- Small instrumentation APK used to inject Android touch 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` (`swipe`, `pinch`, `rotate`, or `transform`)
38
- - `injectedEvents`
39
- - `elapsedMs`
40
-
41
- Failures return `ok=false`, `errorType`, and `message`.
@@ -1 +0,0 @@
1
- ee175f4b073047b40a3669e38caa85e83cdfe17921fe466bacfb1930551e8022 agent-device-android-multitouch-helper-0.18.0.apk