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