agent-device 0.17.5 → 0.17.7

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 (134) hide show
  1. package/README.md +5 -5
  2. package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.5.apk → agent-device-android-multitouch-helper-0.17.7.apk} +0 -0
  3. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.7.apk.sha256 +1 -0
  4. package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.5.manifest.json → agent-device-android-multitouch-helper-0.17.7.manifest.json} +4 -4
  5. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.5.apk → agent-device-android-snapshot-helper-0.17.7.apk} +0 -0
  6. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.17.7.apk.sha256 +1 -0
  7. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.5.manifest.json → agent-device-android-snapshot-helper-0.17.7.manifest.json} +6 -6
  8. package/dist/src/2133.js +1 -0
  9. package/dist/src/2748.js +1 -0
  10. package/dist/src/{4012.js → 2892.js} +1 -1
  11. package/dist/src/2948.js +1 -0
  12. package/dist/src/2954.js +2 -0
  13. package/dist/src/{7651.js → 3006.js} +1 -1
  14. package/dist/src/314.js +1 -0
  15. package/dist/src/{8806.js → 3340.js} +5 -5
  16. package/dist/src/368.js +1 -0
  17. package/dist/src/4291.js +1 -0
  18. package/dist/src/{7599.js → 432.js} +1 -1
  19. package/dist/src/4418.js +1 -0
  20. package/dist/src/4428.js +3 -0
  21. package/dist/src/4723.js +1 -0
  22. package/dist/src/{113.js → 5448.js} +1 -1
  23. package/dist/src/5484.js +1 -0
  24. package/dist/src/5628.js +1 -0
  25. package/dist/src/587.js +1 -0
  26. package/dist/src/5910.js +2 -0
  27. package/dist/src/6311.js +27 -0
  28. package/dist/src/6918.js +1 -0
  29. package/dist/src/7244.js +1 -0
  30. package/dist/src/7394.js +6 -0
  31. package/dist/src/8184.js +1 -0
  32. package/dist/src/8225.js +1 -0
  33. package/dist/src/{1974.js → 8275.js} +1 -1
  34. package/dist/src/8875.js +1 -0
  35. package/dist/src/9581.js +11 -0
  36. package/dist/src/9624.js +1 -0
  37. package/dist/src/9722.js +38 -0
  38. package/dist/src/9836.js +1 -0
  39. package/dist/src/9873.js +1 -0
  40. package/dist/src/9919.js +6 -0
  41. package/dist/src/android-adb.d.ts +3 -3
  42. package/dist/src/android-adb.js +1 -1
  43. package/dist/src/android-snapshot-helper.js +1 -1
  44. package/dist/src/android.js +1 -1
  45. package/dist/src/apple.js +1 -1
  46. package/dist/src/apps.js +6 -6
  47. package/dist/src/artifacts.js +1 -1
  48. package/dist/src/batch.d.ts +5 -5
  49. package/dist/src/batch.js +1 -1
  50. package/dist/src/{args.js → cli-help.js} +135 -36
  51. package/dist/src/cli-output.js +4 -30
  52. package/dist/src/cli.js +18 -17
  53. package/dist/src/command-surface.js +1 -1
  54. package/dist/src/contracts.d.ts +7 -7
  55. package/dist/src/contracts.js +1 -1
  56. package/dist/src/devices.js +1 -1
  57. package/dist/src/find.js +1 -1
  58. package/dist/src/finders.d.ts +1 -1
  59. package/dist/src/finders.js +1 -1
  60. package/dist/src/generic.js +8 -14
  61. package/dist/src/index.d.ts +34 -28
  62. package/dist/src/index.js +1 -1
  63. package/dist/src/input-actions.js +1 -1
  64. package/dist/src/input-actions~1.js +1 -1
  65. package/dist/src/install-source.js +1 -1
  66. package/dist/src/interaction.js +1 -1
  67. package/dist/src/internal/bin.js +5 -5
  68. package/dist/src/internal/companion-tunnel.js +1 -1
  69. package/dist/src/internal/daemon.js +1 -1
  70. package/dist/src/internal/png-worker.js +1 -1
  71. package/dist/src/internal/update-check-entry.js +1 -1
  72. package/dist/src/io.js +1 -1
  73. package/dist/src/lease.js +1 -1
  74. package/dist/src/linux.js +1 -1
  75. package/dist/src/metro.js +1 -1
  76. package/dist/src/notifications.js +1 -1
  77. package/dist/src/provider.js +1 -0
  78. package/dist/src/react-native.js +1 -1
  79. package/dist/src/record-trace-recording.js +3 -3
  80. package/dist/src/record-trace.js +1 -1
  81. package/dist/src/recording-provider.js +1 -1
  82. package/dist/src/remote-config.d.ts +6 -6
  83. package/dist/src/remote-config.js +1 -1
  84. package/dist/src/rslib-runtime.js +1 -1
  85. package/dist/src/selector-runtime.js +1 -1
  86. package/dist/src/selectors.d.ts +2 -2
  87. package/dist/src/selectors.js +1 -1
  88. package/dist/src/server.js +1 -1
  89. package/dist/src/session.js +8 -8
  90. package/dist/src/snapshot.js +2 -2
  91. package/dist/src/web.js +1 -0
  92. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +6 -6
  93. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +1 -1
  94. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +9 -5
  95. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +5 -2
  96. package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +44 -8
  97. package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-resize.swift +59 -16
  98. package/ios-runner/RUNNER_PROTOCOL.md +1 -1
  99. package/package.json +18 -14
  100. package/server.json +3 -3
  101. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.5.apk.sha256 +0 -1
  102. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.17.5.apk.sha256 +0 -1
  103. package/dist/src/1010.js +0 -1
  104. package/dist/src/123.js +0 -1
  105. package/dist/src/1231.js +0 -1
  106. package/dist/src/1352.js +0 -1
  107. package/dist/src/208.js +0 -1
  108. package/dist/src/221.js +0 -6
  109. package/dist/src/2403.js +0 -3
  110. package/dist/src/2415.js +0 -38
  111. package/dist/src/3675.js +0 -1
  112. package/dist/src/5611.js +0 -1
  113. package/dist/src/5827.js +0 -1
  114. package/dist/src/6088.js +0 -1
  115. package/dist/src/6133.js +0 -1
  116. package/dist/src/6629.js +0 -1
  117. package/dist/src/7556.js +0 -1
  118. package/dist/src/7719.js +0 -1
  119. package/dist/src/7847.js +0 -1
  120. package/dist/src/8133.js +0 -1
  121. package/dist/src/8656.js +0 -1
  122. package/dist/src/9010.js +0 -1
  123. package/dist/src/9471.js +0 -1
  124. package/dist/src/9542.js +0 -6
  125. package/dist/src/9616.js +0 -2
  126. package/dist/src/9639.js +0 -2
  127. package/dist/src/9671.js +0 -1
  128. package/dist/src/9818.js +0 -1
  129. package/dist/src/989.js +0 -1
  130. /package/dist/src/{2301.js → 4069.js} +0 -0
  131. /package/dist/src/{4829.js → 5560.js} +0 -0
  132. /package/dist/src/{4778.js → 6024.js} +0 -0
  133. /package/dist/src/{9152.js → 733.js} +0 -0
  134. /package/dist/src/{3267.js → 9612.js} +0 -0
@@ -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"./9616.js";import{normalizeError as o,AppError as r}from"./9152.js";import{withDiagnosticTimer as i,emitDiagnostic as l}from"./7599.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"./2415.js";import{SETTINGS_INVALID_ARGS_MESSAGE as b,parseTimeout as I,getUnsupportedMacOsSettingMessage as A,successText as R,isMacOsSettingSupported as C,snapshotCaptureAnnotationsFrom as N}from"./6133.js";import{sleep as k}from"./4829.js";import{buildSnapshotSession as M,dispatchWaitViaRuntime as P,createDaemonRuntimePolicy as D,recordIfSession as E,resolveSessionDevice as O,withSessionlessRunnerCleanup as K}from"./selector-runtime.js";import{createAgentDevice as U}from"./123.js";import{summarizeSnapshotDiagnostics as x}from"./4012.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,T=/(?:^|:)id\/permission_/i,F=/\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,z=/^(?:cancel|deny|don.t allow|don’t allow|not now|no|dismiss|close|close app|later|skip)$/i;function G(e,t){let a=t[0];if(!a)return;let n=V(e,a.index);for(let a of t.slice(1)){let t=new Set(V(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 V(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 W(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 J(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 Q(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 X(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 k(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)||T.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)&&F.test(Q(t))});if(0===t.length)return[];let a=G(e,t);return void 0===a?t:W(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=G(e,r);return void 0===i?r:W(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=Q(n);if(!e||!n.rect||!J(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":z.test(t.trim())?"dismiss":"neutral")}(n,e)})}return a}(l),p=l.filter(e=>Q(e)&&!J(e)),u=Q((t=p).find(e=>/(?:^|:)id\/(?:alertTitle|permission_message)$/i.test(e.identifier??"")))||Q(t[0]),c=(a=p,n=u,(r=a.map(e=>Q(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 X(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},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 k(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 k(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}),...D("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=M({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 P({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{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"./9873.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"./368.js";import{summarizeSnapshotDiagnostics as x}from"./2892.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};
@@ -0,0 +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),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};
@@ -420,24 +420,24 @@ extension RunnerTests {
420
420
  if let requestedFps = command.fps, (requestedFps < minRecordingFps || requestedFps > maxRecordingFps) {
421
421
  return Response(ok: false, error: ErrorPayload(message: "recordStart fps must be between \(minRecordingFps) and \(maxRecordingFps)"))
422
422
  }
423
- if let requestedQuality = command.quality, (requestedQuality < minRecordingQuality || requestedQuality > maxRecordingQuality) {
424
- return Response(ok: false, error: ErrorPayload(message: "recordStart quality must be between \(minRecordingQuality) and \(maxRecordingQuality)"))
423
+ if let requestedMaxSize = command.maxSize, requestedMaxSize < 1 {
424
+ return Response(ok: false, error: ErrorPayload(message: "recordStart maxSize must be a positive integer"))
425
425
  }
426
426
  do {
427
427
  let resolvedOutPath = resolveRecordingOutPath(requestedOutPath)
428
428
  let fpsLabel = command.fps.map(String.init) ?? String(RunnerTests.defaultRecordingFps)
429
- let qualityLabel = command.quality.map(String.init) ?? "native"
429
+ let maxSizeLabel = command.maxSize.map(String.init) ?? "native"
430
430
  NSLog(
431
- "AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@ quality=%@",
431
+ "AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@ maxSize=%@",
432
432
  requestedOutPath,
433
433
  resolvedOutPath,
434
434
  fpsLabel,
435
- qualityLabel
435
+ maxSizeLabel
436
436
  )
437
437
  let recorder = ScreenRecorder(
438
438
  outputPath: resolvedOutPath,
439
439
  fps: command.fps.map { Int32($0) },
440
- quality: command.quality
440
+ maxSize: command.maxSize
441
441
  )
442
442
  try recorder.start { [weak self] in
443
443
  return self?.captureRunnerFrame()
@@ -136,7 +136,7 @@ struct Command: Codable {
136
136
  let velocity: Double?
137
137
  let outPath: String?
138
138
  let fps: Int?
139
- let quality: Int?
139
+ let maxSize: Int?
140
140
  let interactiveOnly: Bool?
141
141
  let depth: Int?
142
142
  let scope: String?
@@ -7,7 +7,7 @@ extension RunnerTests {
7
7
  final class ScreenRecorder {
8
8
  private let outputPath: String
9
9
  private let fps: Int32?
10
- private let quality: Int?
10
+ private let maxSize: Int?
11
11
  private var effectiveFps: Int32 {
12
12
  max(1, fps ?? RunnerTests.defaultRecordingFps)
13
13
  }
@@ -26,10 +26,10 @@ extension RunnerTests {
26
26
  private var startedSession = false
27
27
  private var startError: Error?
28
28
 
29
- init(outputPath: String, fps: Int32?, quality: Int?) {
29
+ init(outputPath: String, fps: Int32?, maxSize: Int?) {
30
30
  self.outputPath = outputPath
31
31
  self.fps = fps
32
- self.quality = quality
32
+ self.maxSize = maxSize
33
33
  }
34
34
 
35
35
  func start(captureFrame: @escaping () -> RunnerImage?) throws {
@@ -262,10 +262,14 @@ extension RunnerTests {
262
262
  }
263
263
 
264
264
  private func scaledDimensions(width: Int, height: Int) -> CGSize {
265
- guard let quality, quality < 10 else {
265
+ guard let maxSize, maxSize > 0 else {
266
266
  return CGSize(width: width, height: height)
267
267
  }
268
- let scale = Double(quality) / 10.0
268
+ let longest = max(width, height)
269
+ guard longest > maxSize else {
270
+ return CGSize(width: width, height: height)
271
+ }
272
+ let scale = Double(maxSize) / Double(longest)
269
273
  return CGSize(
270
274
  width: scaledEvenDimension(width, scale: scale),
271
275
  height: scaledEvenDimension(height, scale: scale)
@@ -49,8 +49,6 @@ final class RunnerTests: XCTestCase {
49
49
  let xctestIdleKeepaliveInterval: TimeInterval = 5.0
50
50
  let minRecordingFps = 1
51
51
  let maxRecordingFps = 120
52
- let minRecordingQuality = 5
53
- let maxRecordingQuality = 10
54
52
  var needsPostSnapshotInteractionDelay = false
55
53
  var needsFirstInteractionDelay = false
56
54
  var activeRecording: ScreenRecorder?
@@ -91,6 +89,11 @@ final class RunnerTests: XCTestCase {
91
89
 
92
90
  @MainActor
93
91
  func testCommand() throws {
92
+ if RunnerEnv.isTruthy("AGENT_DEVICE_RUNNER_NOOP_STARTUP") {
93
+ NSLog("AGENT_DEVICE_RUNNER_NOOP_STARTUP=1")
94
+ return
95
+ }
96
+
94
97
  doneExpectation = expectation(description: "agent-device command handled")
95
98
  NSLog("AGENT_DEVICE_RUNNER_HEADLESS_STARTUP=1")
96
99
  let desiredPort = RunnerEnv.resolvePort()
@@ -146,11 +146,9 @@ func run() throws {
146
146
  )
147
147
 
148
148
  // Overlay burn-in forces a full re-encode; medium quality keeps simulator videos readable
149
- // while avoiding very slow highest-quality exports.
150
- let presetName = AVAssetExportSession.exportPresets(compatibleWith: composition)
151
- .contains(AVAssetExportPresetMediumQuality)
152
- ? AVAssetExportPresetMediumQuality
153
- : AVAssetExportPresetHighestQuality
149
+ // while avoiding very slow highest-quality exports. Pass --quality high to opt into
150
+ // the slower highest-quality export.
151
+ let presetName = exportPresetName(for: parsedArgs.exportQuality, compatibleWith: composition)
154
152
  guard let exporter = AVAssetExportSession(asset: composition, presetName: presetName) else {
155
153
  throw OverlayError.exportFailed("Failed to create export session.")
156
154
  }
@@ -174,10 +172,20 @@ func run() throws {
174
172
  }
175
173
  }
176
174
 
177
- func parseArguments(_ arguments: [String]) throws -> (inputPath: String, outputPath: String, eventsPath: String) {
175
+ enum ExportQuality: String {
176
+ case medium
177
+ case high
178
+ }
179
+
180
+ func parseArguments(
181
+ _ arguments: [String]
182
+ ) throws -> (inputPath: String, outputPath: String, eventsPath: String, exportQuality: ExportQuality) {
178
183
  var inputPath: String?
179
184
  var outputPath: String?
180
185
  var eventsPath: String?
186
+ // Export quality defaults to medium so existing callers keep the fast, simulator-friendly
187
+ // export. Pass --quality high to opt into a slower highest-quality export.
188
+ var exportQuality: ExportQuality = .medium
181
189
  var index = 0
182
190
 
183
191
  while index < arguments.count {
@@ -196,15 +204,43 @@ func parseArguments(_ arguments: [String]) throws -> (inputPath: String, outputP
196
204
  guard nextIndex < arguments.count else { throw OverlayError.invalidArgs("--events requires a value") }
197
205
  eventsPath = arguments[nextIndex]
198
206
  index += 2
207
+ case "--quality":
208
+ guard nextIndex < arguments.count else {
209
+ throw OverlayError.invalidArgs("--quality requires a value")
210
+ }
211
+ guard let parsed = ExportQuality(rawValue: arguments[nextIndex]) else {
212
+ throw OverlayError.invalidArgs("--quality must be one of: medium, high")
213
+ }
214
+ exportQuality = parsed
215
+ index += 2
199
216
  default:
200
217
  throw OverlayError.invalidArgs("Unknown argument: \(argument)")
201
218
  }
202
219
  }
203
220
 
204
221
  guard let inputPath, let outputPath, let eventsPath else {
205
- throw OverlayError.invalidArgs("Usage: recording-overlay.swift --input <video> --output <video> --events <json>")
222
+ throw OverlayError.invalidArgs(
223
+ "Usage: recording-overlay.swift --input <video> --output <video> --events <json> [--quality <medium|high>]"
224
+ )
225
+ }
226
+ return (inputPath, outputPath, eventsPath, exportQuality)
227
+ }
228
+
229
+ func exportPresetName(
230
+ for exportQuality: ExportQuality,
231
+ compatibleWith asset: AVAsset
232
+ ) -> String {
233
+ switch exportQuality {
234
+ case .high:
235
+ return AVAssetExportPresetHighestQuality
236
+ case .medium:
237
+ // Prefer the faster medium preset, falling back to highest quality only when medium is
238
+ // not available for this composition.
239
+ let compatible = AVAssetExportSession.exportPresets(compatibleWith: asset)
240
+ return compatible.contains(AVAssetExportPresetMediumQuality)
241
+ ? AVAssetExportPresetMediumQuality
242
+ : AVAssetExportPresetHighestQuality
206
243
  }
207
- return (inputPath, outputPath, eventsPath)
208
244
  }
209
245
 
210
246
  func resolvedRenderSize(for track: AVAssetTrack) -> CGSize {
@@ -40,7 +40,13 @@ func run() throws {
40
40
  throw ResizeError.missingVideoTrack
41
41
  }
42
42
 
43
- let renderSize = scaledRenderSize(for: sourceVideoTrack, quality: parsedArgs.quality)
43
+ let sourceRenderSize = resolvedRenderSize(for: sourceVideoTrack)
44
+ if max(sourceRenderSize.width, sourceRenderSize.height) <= CGFloat(parsedArgs.maxSize) {
45
+ try FileManager.default.copyItem(at: inputURL, to: outputURL)
46
+ return
47
+ }
48
+
49
+ let renderSize = scaledRenderSize(sourceRenderSize, maxSize: parsedArgs.maxSize)
44
50
  let composition = AVMutableComposition()
45
51
  let fullRange = CMTimeRange(start: .zero, duration: asset.duration)
46
52
 
@@ -60,7 +66,7 @@ func run() throws {
60
66
  try? compositionAudioTrack.insertTimeRange(fullRange, of: sourceAudioTrack, at: .zero)
61
67
  }
62
68
 
63
- let scale = CGFloat(parsedArgs.quality) / 10.0
69
+ let scale = renderSize.width / sourceRenderSize.width
64
70
  let videoComposition = AVMutableVideoComposition()
65
71
  videoComposition.renderSize = renderSize
66
72
  videoComposition.frameDuration = resolvedFrameDuration(for: sourceVideoTrack)
@@ -74,7 +80,8 @@ func run() throws {
74
80
  instruction.layerInstructions = [layerInstruction]
75
81
  videoComposition.instructions = [instruction]
76
82
 
77
- guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
83
+ let presetName = exportPresetName(for: parsedArgs.exportQuality, compatibleWith: composition)
84
+ guard let exporter = AVAssetExportSession(asset: composition, presetName: presetName) else {
78
85
  throw ResizeError.exportFailed("Failed to create export session.")
79
86
  }
80
87
 
@@ -97,10 +104,20 @@ func run() throws {
97
104
  }
98
105
  }
99
106
 
100
- func parseArguments(_ arguments: [String]) throws -> (inputPath: String, outputPath: String, quality: Int) {
107
+ enum ExportQuality: String {
108
+ case medium
109
+ case high
110
+ }
111
+
112
+ func parseArguments(
113
+ _ arguments: [String]
114
+ ) throws -> (inputPath: String, outputPath: String, maxSize: Int, exportQuality: ExportQuality) {
101
115
  var inputPath: String?
102
116
  var outputPath: String?
103
- var quality: Int?
117
+ var maxSize: Int?
118
+ // Export quality defaults to medium so re-encoded recordings stay fast by default.
119
+ // Pass --quality high to opt into a slower highest-quality export.
120
+ var exportQuality: ExportQuality = .medium
104
121
  var index = 0
105
122
 
106
123
  while index < arguments.count {
@@ -115,24 +132,50 @@ func parseArguments(_ arguments: [String]) throws -> (inputPath: String, outputP
115
132
  guard nextIndex < arguments.count else { throw ResizeError.invalidArgs("--output requires a value") }
116
133
  outputPath = arguments[nextIndex]
117
134
  index += 2
135
+ case "--max-size":
136
+ guard nextIndex < arguments.count else { throw ResizeError.invalidArgs("--max-size requires a value") }
137
+ guard let parsed = Int(arguments[nextIndex]), parsed >= 1 else {
138
+ throw ResizeError.invalidArgs("--max-size must be a positive integer")
139
+ }
140
+ maxSize = parsed
141
+ index += 2
118
142
  case "--quality":
119
- guard nextIndex < arguments.count else { throw ResizeError.invalidArgs("--quality requires a value") }
120
- guard let parsed = Int(arguments[nextIndex]), parsed >= 5, parsed <= 10 else {
121
- throw ResizeError.invalidArgs("--quality must be an integer between 5 and 10")
143
+ guard nextIndex < arguments.count else {
144
+ throw ResizeError.invalidArgs("--quality requires a value")
145
+ }
146
+ guard let parsed = ExportQuality(rawValue: arguments[nextIndex]) else {
147
+ throw ResizeError.invalidArgs("--quality must be one of: medium, high")
122
148
  }
123
- quality = parsed
149
+ exportQuality = parsed
124
150
  index += 2
125
151
  default:
126
152
  throw ResizeError.invalidArgs("Unknown argument: \(argument)")
127
153
  }
128
154
  }
129
155
 
130
- guard let inputPath, let outputPath, let quality else {
156
+ guard let inputPath, let outputPath, let maxSize else {
131
157
  throw ResizeError.invalidArgs(
132
- "Usage: recording-resize.swift --input <video> --output <video> --quality <5-10>"
158
+ "Usage: recording-resize.swift --input <video> --output <video> --max-size <px> [--quality <medium|high>]"
133
159
  )
134
160
  }
135
- return (inputPath, outputPath, quality)
161
+ return (inputPath, outputPath, maxSize, exportQuality)
162
+ }
163
+
164
+ func exportPresetName(
165
+ for exportQuality: ExportQuality,
166
+ compatibleWith asset: AVAsset
167
+ ) -> String {
168
+ switch exportQuality {
169
+ case .high:
170
+ return AVAssetExportPresetHighestQuality
171
+ case .medium:
172
+ // Mirror the touch-overlay export: prefer the faster medium preset, falling back to
173
+ // highest quality only when medium is not available for this composition.
174
+ let compatible = AVAssetExportSession.exportPresets(compatibleWith: asset)
175
+ return compatible.contains(AVAssetExportPresetMediumQuality)
176
+ ? AVAssetExportPresetMediumQuality
177
+ : AVAssetExportPresetHighestQuality
178
+ }
136
179
  }
137
180
 
138
181
  func resolvedRenderSize(for track: AVAssetTrack) -> CGSize {
@@ -140,10 +183,10 @@ func resolvedRenderSize(for track: AVAssetTrack) -> CGSize {
140
183
  return CGSize(width: abs(transformed.width), height: abs(transformed.height))
141
184
  }
142
185
 
143
- func scaledRenderSize(for track: AVAssetTrack, quality: Int) -> CGSize {
144
- let renderSize = resolvedRenderSize(for: track)
145
- guard quality < 10 else { return renderSize }
146
- let scale = CGFloat(quality) / 10.0
186
+ func scaledRenderSize(_ renderSize: CGSize, maxSize: Int) -> CGSize {
187
+ let longest = max(renderSize.width, renderSize.height)
188
+ guard longest > CGFloat(maxSize) else { return renderSize }
189
+ let scale = CGFloat(maxSize) / longest
147
190
  return CGSize(
148
191
  width: scaledDimension(renderSize.width, scale: scale),
149
192
  height: scaledDimension(renderSize.height, scale: scale)
@@ -32,7 +32,7 @@ Examples:
32
32
  ```
33
33
 
34
34
  ```json
35
- { "command": "recordStart", "outPath": "/tmp/demo.mp4", "fps": 30, "quality": 7 }
35
+ { "command": "recordStart", "outPath": "/tmp/demo.mp4", "fps": 30, "maxSize": 720 }
36
36
  ```
37
37
 
38
38
  ```json
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.17.5",
4
- "description": "Agent-native CLI for AI mobile testing and app automation across iOS, Android, tvOS, Android TV, macOS, and Linux.",
3
+ "version": "0.17.7",
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",
7
7
  "author": "Callstack",
8
8
  "homepage": "https://agent-device.dev/",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/callstackincubator/agent-device.git"
11
+ "url": "git+https://github.com/callstack/agent-device.git"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/callstackincubator/agent-device/issues"
14
+ "url": "https://github.com/callstack/agent-device/issues"
15
15
  },
16
16
  "type": "module",
17
17
  "packageManager": "pnpm@11.1.2",
@@ -134,6 +134,7 @@
134
134
  "test:integration:provider": "vitest run --project provider-integration",
135
135
  "test:integration:progress": "node --experimental-strip-types scripts/integration-progress.ts",
136
136
  "test:integration:progress:check": "node --experimental-strip-types scripts/integration-progress.ts --check",
137
+ "test:smoke:web": "pnpm build && node --test test/integration/smoke-web-platform.test.ts",
137
138
  "test:skillgym": "node test/skillgym/runner-environment.ts && pnpm build && skillgym run ./test/skillgym/suites/agent-device-smoke-suite.ts --config ./test/skillgym/skillgym.config.ts",
138
139
  "test:skillgym:case": "node test/skillgym/runner-environment.ts && pnpm build && skillgym run ./test/skillgym/suites/agent-device-smoke-suite.ts --config ./test/skillgym/skillgym.config.ts --case",
139
140
  "test:smoke": "node --test test/integration/smoke-*.test.ts",
@@ -178,6 +179,8 @@
178
179
  "android",
179
180
  "tvos",
180
181
  "macos",
182
+ "web",
183
+ "browser",
181
184
  "react-native",
182
185
  "observability",
183
186
  "diagnostics",
@@ -211,16 +214,17 @@
211
214
  "yaml": "^2.9.0"
212
215
  },
213
216
  "devDependencies": {
214
- "@microsoft/api-extractor": "^7.58.7",
215
- "@rslib/core": "0.20.1",
216
- "@types/node": "^22.0.0",
217
- "@vitest/coverage-v8": "4.1.2",
218
- "fallow": "^2.91.0",
217
+ "@microsoft/api-extractor": "^7.58.8",
218
+ "@rslib/core": "0.22.1",
219
+ "@types/node": "^22.19.21",
220
+ "@typescript/native-preview": "7.0.0-dev.20260613.1",
221
+ "@vitest/coverage-v8": "4.1.8",
222
+ "fallow": "^2.95.0",
219
223
  "oxfmt": "^0.42.0",
220
- "oxlint": "^1.57.0",
221
- "skillgym": "^0.8.0",
222
- "typescript": "^6.0.2",
223
- "vite": "^8.0.10",
224
- "vitest": "^4.1.2"
224
+ "oxlint": "^1.69.0",
225
+ "skillgym": "^0.9.1",
226
+ "typescript": "^6.0.3",
227
+ "vite": "^8.0.16",
228
+ "vitest": "^4.1.8"
225
229
  }
226
230
  }
package/server.json CHANGED
@@ -4,15 +4,15 @@
4
4
  "title": "agent-device",
5
5
  "description": "Let AI agents inspect, control, and debug real iOS, Android, desktop, and TV apps",
6
6
  "repository": {
7
- "url": "https://github.com/callstackincubator/agent-device",
7
+ "url": "https://github.com/callstack/agent-device",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.17.5",
10
+ "version": "0.17.7",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "agent-device",
15
- "version": "0.17.5",
15
+ "version": "0.17.7",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -1 +0,0 @@
1
- 45a85cddbdca10a23a6245bf8e6786342551bf61f9d02bd8fdbf419e785dc1f9 agent-device-android-multitouch-helper-0.17.5.apk
@@ -1 +0,0 @@
1
- 94e26dc456437cf7a2112450e8638b5fac62565b4b17e86dc3834f6eedcd3ced agent-device-android-snapshot-helper-0.17.5.apk
package/dist/src/1010.js DELETED
@@ -1 +0,0 @@
1
- import{AppError as e}from"./9152.js";let r=new Set(["1","true","yes","on"]),n=new Set(["0","false","no","off"]);function t(e){return`AGENT_DEVICE_${e.replace(/([A-Z])/g,"_$1").replace(/[^A-Za-z0-9_]/g,"_").toUpperCase()}`}function i(r,n,t,f){if(r.multiple)return(Array.isArray(n)?n:[n]).map(e=>i({...r,multiple:!1},e,t,f));if("boolean"===r.type){var a=n,u=t,l=f;if("boolean"==typeof a)return a;if("string"==typeof a){let e=o(a);if(void 0!==e)return e}throw new e("INVALID_ARGS",`Invalid value for "${l}" in ${u}. Expected boolean.`)}if("booleanOrString"===r.type){if("boolean"==typeof n)return n;if("string"==typeof n&&void 0!==o(n))return o(n);if("string"==typeof n&&n.trim().length>0)return n;throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected boolean or non-empty string.`)}if("string"===r.type){if("string"==typeof n&&n.trim().length>0)return n;throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected non-empty string.`)}if("enum"===r.type){if(void 0!==r.setValue){var p=r,m=n,s=t,I=f;let i=p.setValue;if(m===i)return i;if("string"==typeof m){let e=m.trim();if(""===e||"true"===e||"1"===e)return i;if("false"===e||"0"===e)return}if(!0===m)return i;if(!1!==m)throw new e("INVALID_ARGS",`Invalid value for "${I}" in ${s}. Expected boolean-like value for enum flag.`);return}if("string"!=typeof n||!r.enumValues?.includes(n))throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected one of: ${r.enumValues?.join(", ")}.`);return n}let v="number"==typeof n?n:"string"==typeof n?Number(n):NaN;if(!Number.isFinite(v)||!Number.isInteger(v))throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected integer.`);if("number"==typeof r.min&&v<r.min)throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Must be >= ${r.min}.`);if("number"==typeof r.max&&v>r.max)throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Must be <= ${r.max}.`);return v}function o(e){let t=e.trim().toLowerCase();return!!r.has(t)||!n.has(t)&&void 0}export{t as buildPrimaryEnvVarName,i as parseSourceValue};