agent-device 0.17.10 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.10.apk → agent-device-android-multitouch-helper-0.18.1.apk} +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.18.1.apk.sha256 +1 -0
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.10.manifest.json → agent-device-android-multitouch-helper-0.18.1.manifest.json} +4 -4
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.10.apk → agent-device-android-snapshot-helper-0.18.1.apk} +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.18.1.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.10.manifest.json → agent-device-android-snapshot-helper-0.18.1.manifest.json} +6 -6
- package/apple-runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +156 -0
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +10 -16
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +2 -0
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +3 -1
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +2 -0
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +2 -4
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +9 -36
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +2 -0
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +2 -0
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +2 -0
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +2 -0
- package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +0 -1
- package/dist/src/1986.js +1 -0
- package/dist/src/2748.js +1 -1
- package/dist/src/2948.js +21 -21
- package/dist/src/314.js +1 -1
- package/dist/src/3340.js +8 -7
- package/dist/src/432.js +4 -4
- package/dist/src/4418.js +1 -1
- package/dist/src/4428.js +3 -3
- package/dist/src/485.js +1 -0
- package/dist/src/495.js +5 -0
- package/dist/src/5484.js +1 -1
- package/dist/src/5643.js +1 -0
- package/dist/src/587.js +1 -1
- package/dist/src/5913.js +4 -0
- package/dist/src/6849.js +1 -0
- package/dist/src/6918.js +1 -1
- package/dist/src/6967.js +1 -0
- package/dist/src/760.js +1 -0
- package/dist/src/7871.js +1 -0
- package/dist/src/8225.js +1 -1
- package/dist/src/8391.js +1 -0
- package/dist/src/8404.js +2 -0
- package/dist/src/8414.js +2 -0
- package/dist/src/8875.js +1 -1
- package/dist/src/8936.js +1 -0
- package/dist/src/9414.js +1 -0
- package/dist/src/9581.js +5 -5
- package/dist/src/9624.js +1 -1
- package/dist/src/9722.js +37 -37
- package/dist/src/android-adb.d.ts +19 -54
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android.js +5 -5
- package/dist/src/apps.js +9 -11
- package/dist/src/audio-probe.js +1 -0
- package/dist/src/batch.d.ts +50 -25
- package/dist/src/batch.js +1 -1
- package/dist/src/cli-help.js +104 -37
- package/dist/src/cli.js +36 -27
- package/dist/src/contracts.d.ts +46 -122
- package/dist/src/contracts.js +1 -1
- package/dist/src/debug-symbols.js +3 -0
- package/dist/src/devices.js +1 -1
- package/dist/src/devices~1.js +1 -1
- package/dist/src/devices~2.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/finders.d.ts +2 -6
- package/dist/src/finders.js +1 -1
- package/dist/src/generic.js +1 -9
- package/dist/src/handler-utils.js +1 -0
- package/dist/src/index.d.ts +500 -182
- package/dist/src/index.js +1 -1
- package/dist/src/input-actions.js +1 -1
- package/dist/src/input-actions~1.js +1 -1
- package/dist/src/install-source.d.ts +0 -26
- package/dist/src/install-source.js +1 -1
- package/dist/src/interaction.js +1 -1
- package/dist/src/interactor.js +1 -0
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/companion-tunnel.js +1 -1
- package/dist/src/internal/png-worker.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/metro.d.ts +10 -134
- package/dist/src/metro.js +1 -1
- package/dist/src/notifications.js +1 -1
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-trace-recording.js +3 -3
- package/dist/src/record-trace.js +1 -1
- package/dist/src/remote-config.d.ts +28 -24
- package/dist/src/remote-config.js +1 -1
- package/dist/src/runner-sequence.js +1 -0
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selectors.d.ts +7 -3
- package/dist/src/selectors.js +1 -1
- package/dist/src/server.js +2 -2
- package/dist/src/session.js +11 -8
- package/dist/src/simulator.js +2 -0
- package/dist/src/snapshot.js +2 -2
- package/dist/src/web.js +1 -1
- package/macos-helper/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +297 -0
- package/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +30 -0
- package/package.json +15 -11
- package/server.json +2 -2
- package/android-multitouch-helper/README.md +0 -41
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.10.apk.sha256 +0 -1
- package/android-snapshot-helper/README.md +0 -88
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.17.10.apk.sha256 +0 -1
- package/dist/src/2133.js +0 -1
- package/dist/src/2577.js +0 -1
- package/dist/src/2954.js +0 -2
- package/dist/src/4291.js +0 -1
- package/dist/src/4723.js +0 -1
- package/dist/src/5628.js +0 -1
- package/dist/src/5910.js +0 -2
- package/dist/src/6024.js +0 -1
- package/dist/src/733.js +0 -1
- package/dist/src/7394.js +0 -6
- package/dist/src/8275.js +0 -2
- package/dist/src/8358.js +0 -4
- package/dist/src/9919.js +0 -6
- package/dist/src/android-snapshot-helper.d.ts +0 -359
- package/dist/src/android-snapshot-helper.js +0 -1
- package/dist/src/apple.js +0 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.swift +0 -17
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AccentColor.colorset/Contents.json +0 -11
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -36
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/logo.jpg +0 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Contents.json +0 -6
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Logo.imageset/Contents.json +0 -21
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Logo.imageset/logo.jpg +0 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/Contents.json +0 -21
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/powered-by.png +0 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner/ContentView.swift +0 -34
- package/ios-runner/README.md +0 -53
- package/ios-runner/RUNNER_PROTOCOL.md +0 -76
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
- /package/{ios-runner → apple-runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
- /package/dist/src/{4069.js → 4917.js} +0 -0
- /package/dist/src/{9836.js → 893.js} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
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"];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 C(d)){u.focusExisting&&await m({background:u.deviceHub,deviceHub:u.deviceHub});return}u.onColdBootStart?.(d);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 C(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 C(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,C as getSimulatorState,c as requireSimulatorDevice,S as shutdownSimulator};
|
package/dist/src/snapshot.js
CHANGED
|
@@ -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"./
|
|
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
|
|
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"./6849.js";import{normalizeError as o,AppError as r}from"./485.js";import{withDiagnosticTimer as i,emitDiagnostic as l}from"./432.js";import{pressAndroid as d,backAndroid as p}from"./input-actions.js";import{runAppleRunnerCommand as u,createDaemonRuntimeSessionStore as c,captureSnapshot as f,dispatchCommand as m,requireCommandSupported as h,runMacOsAlertAction as w,errorResponse as g,annotateScreenshotWithRefs as y,resolveSnapshotScope as _,snapshotAndroid as v,context_contextFromFlags as S}from"./9722.js";import{SETTINGS_INVALID_ARGS_MESSAGE as b,parseTimeout as I,getUnsupportedMacOsSettingMessage as A,successText as C,isMacOsSettingSupported as R,snapshotCaptureAnnotationsFrom as N}from"./2948.js";import{sleep as M}from"./5560.js";import{buildAppleRunnerRequestOptions as k}from"./8414.js";import{buildSnapshotSession as D,dispatchWaitViaRuntime as K,createDaemonRuntimePolicy as x,recordIfSession as P,resolveSessionDevice as E,withSessionlessRunnerCleanup as U}from"./selector-runtime.js";import{createAgentDevice as L}from"./7871.js";import{summarizeSnapshotDiagnostics as O}from"./5913.js";var B={};e.r(B),e.d(B,{handleSnapshotCommands:()=>eg});let H=new Set(["com.android.permissioncontroller","com.google.android.permissioncontroller","com.google.android.packageinstaller","com.android.packageinstaller"]),$=new Set(["android","com.android.systemui"]),q=/^android:id\/(?:alertTitle|message|button[123]|parentPanel|buttonPanel|contentPanel)$/i,F=/^android:id\/button[123]$/i,j=/(?:^|:)id\/permission_/i,T=/\b(?:is(?:n't| not) responding|keeps stopping|has stopped|close app|app info)\b/i,z=/^(?:ok|allow|allow all|while using the app|only this time|yes|continue|save|confirm|turn on|open settings)$/i,G=/^(?:cancel|deny|don.t allow|don’t allow|not now|no|dismiss|close|close app|later|skip)$/i;function V(e,t){let a=t[0];if(!a)return;let n=W(e,a.index);for(let a of t.slice(1)){let t=new Set(W(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 W(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 J(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 Q(e){let t=e.type??"",a=e.identifier??"";return!!(e.hittable||/\bbutton\b/i.test(t)||F.test(a)||/(?:^|:)id\/permission_(?:allow|deny)/i.test(a))}function X(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 Y(e,t,a={}){if("wait"===t)return await Z(e,a.timeoutMs??1e4);if("get"===t){var n;let t=await ea(e);return{kind:"alertStatus",platform:"android",action:"get",alert:n=t?.alert??null,...n?C("Alert visible"):C("No alert visible")}}return await ee(e,t)}async function Z(e,t){let a=Date.now(),n=await et(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,...C("Alert visible")}}async function ee(e,t){var a;let n,s,o=await et(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),en(t,o.alert,i.label);if("dismiss"===t)return await p(e),en(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 et(e,t){let a=Date.now();for(;Date.now()-a<t;){let t=await ea(e);if(t)return t;await M(300)}return null}async function ea(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??"",H.has(a)||j.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(X(t))});if(0===t.length)return[];let a=V(e,t);return void 0===a?t:J(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=>q.test(e.identifier??"")),r=s.length?[...s,...o]:(a=(t=o).some(e=>F.test(e.identifier??"")),n=t.some(e=>!F.test(e.identifier??"")),a&&n?t:[]);if(0===r.length)return[];let i=V(e,r);return void 0===i?r:J(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=X(n);if(!e||!n.rect||!Q(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||(z.test(t.trim())?"accept":G.test(t.trim())?"dismiss":"neutral")}(n,e)})}return a}(l),p=l.filter(e=>X(e)&&!Q(e)),u=X((t=p).find(e=>/(?:^|:)id\/(?:alertTitle|permission_message)$/i.test(e.identifier??"")))||X(t[0]),c=(a=p,n=u,(r=a.map(e=>X(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 v(e,{helperWaitForIdleTimeoutMs:0,includeHiddenContentHints:!1}),{backend:"android",purpose:"alert"})).nodes)}function en(e,t,a){return{kind:"alertHandled",platform:"android",action:e,handled:!0,alert:t,button:a,...C(`Alert ${e}ed`)}}async function es(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}:{},l=h("alert",o);if(l)return l;if("android"===o.platform){let t=I(a.positionals?.[1])??1e4;return el(e,await Y(o,r,{timeoutMs:t}))}if("macos"===o.platform){let t=async e=>await w(e,i);return await eo(e,r,t)}let d=k({req:a,logPath:n,traceLogPath:s?.trace?.outPath}),p=async e=>await u(o,{command:"alert",action:e,appBundleId:s?.appBundleId},d);return await eo(e,r,p)}async function eo(e,t,a){if("wait"===t)return await er(e,a);let n="accept"===t||"dismiss"===t?t:"get";return"accept"===n||"dismiss"===n?await ei(e,n,a):el(e,await a("get"))}async function er(e,t){let a=I(e.req.positionals?.[1])??1e4,n=Date.now();for(;Date.now()-n<a;){try{return el(e,await t("get"))}catch{}await M(300)}return g("COMMAND_FAILED","alert wait timed out")}async function ei(e,t,a){var n,s;let o,i,l=Date.now();for(;Date.now()-l<2e3;){try{return el(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 M(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 el(e,t){return P(e.sessionStore,e.session,e.req,t),{ok:!0,data:t}}async function ed(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,f=h("settings",o);if(f)return f;if("macos"===o.platform&&!R(i))return g("INVALID_ARGS",A(i));let w=d??s?.appBundleId;if("clear-app-state"===i&&!w)return g("INVALID_ARGS","settings clear-app-state requires an app id when no app is bound to the session");let y="clear-app-state"===i?[i,l,w??""]:"permission"===i?[i,l,p??"",t.positionals?.[3]??"",w??""]:"location"===i&&"set"===l?[i,l,u??"",c??"",w??""]:[i,l,w??""],_=await m(o,"settings",y,t.flags?.out,{...S(a,t.flags,w,s?.trace?.outPath)});return P(n,s,t,_??{setting:i,state:l}),{ok:!0,data:_??{setting:i,state:l}}}async function ep(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 eu(e)}}}}async function eu(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 m(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 ec(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 ec(e,t){if(!t?.snapshot)return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"unavailable",overlayRefCount:0};try{let a=await y({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 ef(e){return await eh({...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 em(e){return await eh({...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 eh(e){var t,a;let{req:n,sessionName:s,logPath:o,sessionStore:i}=e,{session:l,device:d}=await E(i,s,n.flags),p=h(e.command,d,{message:e.unsupportedMessage});if(p)return p;let u=_(n.flags?.snapshotScope,l);if(!u.ok)return u;let m=(t=e.command,a=l,"ios"!==d.platform||a?.appBundleId?null:g("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 U(l,d,async()=>{var t,a;let p,m,h=function(e){let{req:t,sessionName:a,logPath:n,sessionStore:s,session:o,device:i,snapshotScope:l}=e;return L({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 f({device:s,session:n,flags:t.flags,outPath:r?.outPath??t.flags?.out,logPath:a,snapshotScope:o}),l=O(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}),...x("snapshot"),sessions:c({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=D({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:u.scope});try{m=await e.execute({runtime:h,sessionName:s,req:n,snapshotScope:u.scope})}catch(a){let t=await ep({error:a,command:e.command,logPath:o,session:l,device:d});if(!t)throw a;return t}return(p=(t={req:n,sessionName:s,sessionStore:i,result:m.record}).sessionStore.get(t.sessionName))&&t.sessionStore.recordAction(p,{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 ew={snapshot:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>await ef({req:e,sessionName:t,logPath:a,sessionStore:n}),diff:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>e.positionals?.[0]!=="snapshot"?g("INVALID_ARGS","diff currently supports only: diff snapshot"):await em({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 E(n,t,e.flags);return await U(s,o,async()=>await es({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]}}:g("INVALID_ARGS",b));if(!i.ok)return i;let{session:l,device:d}=await E(n,t,e.flags);return await U(l,d,async()=>await ed({req:e,logPath:a,sessionStore:n,session:l,device:d,parsed:i.parsed}))}};async function eg(e){let t=ew[e.req.command];return t?await t(e):null}export{B as snapshot_namespaceObject};
|
package/dist/src/web.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AppError as e}from"./
|
|
1
|
+
import{AppError as e}from"./485.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};
|
|
@@ -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.
|
|
3
|
+
"version": "0.18.1",
|
|
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
|
-
"
|
|
153
|
-
"!
|
|
154
|
-
"!
|
|
155
|
-
"!
|
|
156
|
-
"!
|
|
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.
|
|
10
|
+
"version": "0.18.1",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "agent-device",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.18.1",
|
|
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`.
|
package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.10.apk.sha256
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
e3c1bb4b43e7e4bbcceaa4975ec7c00f606208a435d575209a9f0819a7adc5de agent-device-android-multitouch-helper-0.17.10.apk
|