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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
let e,t,r,
|
|
1
|
+
let e,t,r,a;import i from"node:fs";import o from"node:path";import{createHash as n}from"node:crypto";import s from"node:os";import{fileURLToPath as c}from"node:url";import{AppError as d}from"./485.js";import{withDiagnosticTimer as u,emitDiagnostic as l,runCmd as p}from"./8875.js";import{acquireProcessLock as m}from"./8391.js";import{readProcessStartTime as h}from"./9414.js";import{sleep as f}from"./5560.js";import{androidDeviceForSerial as w,runAndroidAdb as g,pullAndroidAdbFile as y}from"./3340.js";import{runXcrun as _,tryGetPlugin as v,SessionStore as P,IOS_RUNNER_CONTAINER_BUNDLE_IDS as S,requireCommandSupported as A,errorResponse as b,resolveImplicitSessionScope as M,resolvePublicSessionName as C,registerBuiltinPlatformPlugins as x,resolveTargetDevice as R,runAppleRunnerCommand as I}from"./9722.js";import{isIosFamily as D}from"./6849.js";import{buildAppleRunnerRequestOptions as N,ensureDeviceReady as E}from"./8414.js";import{defaultRecordingPath as $,appendRecordingExtensionWhenMissing as O}from"./5643.js";import{resolveWebProvider as k}from"./provider.js";import{resolveRecordingProvider as F}from"./recording-provider.js";import{recordingQualityInputToExportQuality as T,RECORDING_EXPORT_QUALITIES as L}from"./2948.js";import{recordSessionAction as U}from"./handler-utils.js";function z(e=process.env){let t=V(),r=o.join(t,"home"),a=o.join(t,"module-cache");return i.mkdirSync(r,{recursive:!0}),i.mkdirSync(a,{recursive:!0}),{...e,HOME:r,CLANG_MODULE_CACHE_PATH:a}}async function K(e){let t=i.statSync(e.sourcePath),r=i.readFileSync(e.sourcePath),a=W(e.cacheName??o.basename(e.sourcePath,o.extname(e.sourcePath))),n=B(["2",process.platform,process.arch,o.resolve(e.sourcePath),t.size,r]),s=o.join(V(),"bin",`${a}-${n}`);return await H({sourcePath:e.sourcePath,executablePath:s,timeoutMs:e.timeoutMs}),s}async function G(e){let t=W(e.cacheName),r=B(["2",process.platform,process.arch,e.source]),a=o.join(V(),"sources",`${t}-${r}.swift`),i=o.join(V(),"bin",`${t}-${r}`);return await H({sourcePath:a,executablePath:i,sourceText:e.source,timeoutMs:e.timeoutMs}),i}function V(){let e=process.env.AGENT_DEVICE_SWIFT_CACHE_DIR?.trim();return e?o.resolve(e):o.join(s.tmpdir(),"agent-device-swift-cache")}async function H(e){if(q(e.executablePath))return;let t=o.dirname(e.executablePath);i.mkdirSync(t,{recursive:!0});let r=`${e.executablePath}.lock`,a=await j(r,e.executablePath,e.timeoutMs??12e4);if(!a)return;let n=i.mkdtempSync(o.join(t,`.${o.basename(e.executablePath)}.${process.pid}.`)),s=o.join(n,o.basename(e.executablePath));try{if(q(e.executablePath))return;void 0===e.sourceText||i.existsSync(e.sourcePath)||(i.mkdirSync(o.dirname(e.sourcePath),{recursive:!0}),i.writeFileSync(e.sourcePath,e.sourceText)),await p("xcrun",["swiftc",e.sourcePath,"-o",s],{timeoutMs:e.timeoutMs??12e4,env:z()}),i.renameSync(s,e.executablePath)}finally{i.rmSync(n,{recursive:!0,force:!0}),await a()}}async function j(e,t,r){if(q(t))return null;try{return await m({lockDirPath:e,owner:{pid:process.pid,startTime:h(process.pid),acquiredAtMs:Date.now()},timeoutMs:r,pollMs:25,ownerGraceMs:r,description:`Swift cache lock: ${e} (${r}ms)`})}catch(t){if(t instanceof d&&t.message.startsWith("Timed out waiting for Swift cache lock:"))throw new d("COMMAND_FAILED",t.message,{...t.details,lockDir:e,timeoutMs:r,hint:`Another agent-device process may still be compiling this Swift helper. Retry shortly; if no agent-device process is active, remove "${e}" and retry.`});throw t}}function q(e){try{return i.accessSync(e,i.constants.X_OK),i.statSync(e).isFile()}catch{return!1}}function W(e){return e.replaceAll(/[^A-Za-z0-9._-]/g,"-").replaceAll(/^-+|-+$/g,"")||"swift-helper"}function B(e){let t=n("sha256");for(let r of e)t.update(Buffer.isBuffer(r)?r:String(r)),t.update("\0");return t.digest("hex").slice(0,16)}let Q=`
|
|
2
2
|
import Foundation
|
|
3
3
|
import AVFoundation
|
|
4
4
|
|
|
@@ -22,5 +22,5 @@ Task {
|
|
|
22
22
|
|
|
23
23
|
semaphore.wait()
|
|
24
24
|
exit(exitCode)
|
|
25
|
-
`.trim();async function
|
|
26
|
-
${t}`;return/\b(no such module ['"]AVFoundation['"]|unable to find utility ["']swiftc?["']|xcrun: error: unable to find utility ["']swiftc?["'])\b/i.test(r)}function Q(e){try{let t=a.statSync(e);if(!t.isFile()||t.size<=0)return!1}catch{return!1}let t=function(e){try{let t=a.openSync(e,"r");try{let e=a.fstatSync(t).size,r=0,i=[];for(;r+8<=e&&i.length<16;){let e=Buffer.alloc(8);if(8>a.readSync(t,e,0,8,r))break;let o=e.readUInt32BE(0),n=e.toString("latin1",4,8);if(i.push(n),1===o){let e=Buffer.alloc(8);if(8>a.readSync(t,e,0,8,r+8))break;o=Number(e.readBigUInt64BE(0))}if(!Number.isFinite(o)||o<=0)break;r+=o}return i}finally{a.closeSync(t)}}catch{return[]}}(e);return t.includes("ftyp")&&t.includes("moov")}function J(e){let t=o.parse(e);return o.join(t.dir,`${t.name}.gesture-telemetry.json`)}function Z(e){return[...e].sort((e,t)=>e.tMs-t.tMs)}function Y(e){var t,r,i;let o,n,{recording:s,trimStartMs:c}=e,d=(o=J((t={videoPath:s.outPath,events:s.gestureEvents,trimStartMs:c}).videoPath),n={version:1,generatedAt:new Date().toISOString(),events:(r=t.events,(i=t.trimStartMs??0)>0?Z(r.flatMap(e=>{let t=e.tMs-i,r="durationMs"in e?e.durationMs:void 0;return("number"==typeof r?t+r:t)<=0?[]:[{...e,tMs:Math.max(0,t)}]})):Z(r))},a.writeFileSync(o,JSON.stringify(n,null,2)),o);return s.telemetryPath=d,d}function ee(e){let t=o.dirname(c(import.meta.url)),r=[c(new URL(`./${e}`,import.meta.url)),o.resolve(t,`../../ios-runner/AgentDeviceRunner/RecordingScripts/${e}`),o.resolve(t,`../../../ios-runner/AgentDeviceRunner/RecordingScripts/${e}`),o.resolve(process.cwd(),`ios-runner/AgentDeviceRunner/RecordingScripts/${e}`)];for(let e of r)if(a.existsSync(e))return e;throw new d("COMMAND_FAILED",`Missing recording helper script: ${e}`,{hint:"Ensure ios-runner/AgentDeviceRunner/RecordingScripts is present in this checkout or bundled with the package.",scriptName:e,searchedPaths:r})}async function et(e){var t;let r,i,{videoPath:n,scriptPath:s,scriptArgs:c,commandDescription:l}=e;await j(n),await B(n);let m=(t=n,r=o.parse(t),i=`${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`,o.join(r.dir,`.${r.name}.agent-device-${i}${r.ext||".mp4"}`));try{let e=await F({sourcePath:s});await u(e,["--input",n,"--output",m,...c],{timeoutMs:12e4,env:k()}),await B(m),a.renameSync(m,n)}catch(t){let e=t instanceof d?t:new d("COMMAND_FAILED",String(t),void 0,t instanceof Error?t:void 0);throw new d("COMMAND_FAILED",l,{...e.details,videoPath:n,script:s},e)}finally{a.rmSync(m,{force:!0})}}async function er(e){let{videoPath:t,trimStartMs:i}=e;i>0&&await et({videoPath:t,scriptPath:r??=ee("recording-trim.swift"),scriptArgs:["--trim-start-ms",String(i)],commandDescription:"Failed to trim the start of the iOS recording"})}async function ei(e){let{videoPath:r,telemetryPath:i,exportQuality:a="medium",targetLabel:o="recording"}=e;await et({videoPath:r,scriptPath:t??=ee("recording-overlay.swift"),scriptArgs:["--events",i,"--quality",a],commandDescription:`Failed to add touch overlays to the ${o}`})}async function ea(e){let{videoPath:t,maxSize:r,exportQuality:a="medium",targetLabel:o="recording"}=e;await et({videoPath:t,scriptPath:i??=ee("recording-resize.swift"),scriptArgs:["--max-size",String(r),"--quality",a],commandDescription:`Failed to resize the ${o}`})}function eo(e){return e instanceof Error?e.message:String(e)}function en(e,t){return e.stderr.trim()||e.stdout.trim()||`${t} exited with code ${e.exitCode}`}function es(e,t,r=Date.now()){let i=Math.max(0,r-t.startedAt);return i>=1e3?{message:e,tooShort:!1}:{message:`${e}. Recording stopped after ${Math.round(i)}ms; wait at least 1000ms between record start and record stop so the recorder can finalize a playable MP4`,tooShort:!0}}async function ec(e){let{recording:t,deps:r,trimStartMs:i,targetLabel:a}=e,o=Y({recording:t,trimStartMs:i});if(!t.showTouches)return void f({level:"debug",phase:"record_stop_overlay_skipped",data:{reason:"hide_touches"}});if(0===t.gestureEvents.length)return void f({level:"debug",phase:"record_stop_overlay_skipped",data:{reason:"no_gesture_events"}});let n=function(e=process.platform){if("darwin"!==e)return"touch overlay burn-in is only available on macOS hosts; returning raw video plus gesture telemetry"}();if(n){t.overlayWarning??=n;return}try{await h("record_stop_overlay_export",()=>r.overlayRecordingTouches({videoPath:t.outPath,telemetryPath:o,exportQuality:t.exportQuality,targetLabel:a}),{targetLabel:a,gestureEventCount:t.gestureEvents.length})}catch(e){t.overlayWarning??=`failed to overlay recording touches: ${eo(e)}`}}function ed(e,t){if(1===t)return e;let r=o.parse(e),i=r.ext||".mp4";return o.join(r.dir,`${r.name}.part-${String(t).padStart(3,"0")}${i}`)}function eu(e){return e.chunks??=[{index:1,path:e.outPath,remotePath:e.remotePath}],e.chunks}async function el(e){let{recording:t,startNextChunk:r,finishCurrentChunk:i}=e;if(t.stopping)return;let a=await i();if(a)throw Error(a);if(t.stopping)return;let n=eu(t),s=n.length+1,c=await r(o.posix.dirname(t.remotePath));t.remotePath=c.remotePath,t.remotePid=c.remotePid,n.push({index:s,path:ed(t.outPath,s),remotePath:c.remotePath}),t.warning??="Android adb screenrecord is capped at 180s, so this recording was split into multiple MP4 chunks."}async function em(e){let{recording:t,deps:r}=e;eu(t).length<=1?await ec({recording:t,deps:r,targetLabel:"Android recording"}):(Y({recording:t}),t.showTouches&&t.gestureEvents.length>0&&(t.overlayWarning??="touch overlay burn-in is skipped for chunked Android recordings; returning raw chunks plus gesture telemetry"))}async function ep(e){for(let t of e.chunks){let r=await eh({deps:e.deps,deviceId:e.deviceId,remotePath:t.remotePath,outPath:t.path});if(r)return`failed to copy recording chunk ${t.index}: ${r}`}}async function eh(e){let t,{deps:r,deviceId:i,remotePath:o,outPath:n}=e;for(let e=0;e<2;e+=1){ef(n);let s=w(i),c=await y(o,n,{allowFailure:!0,device:s});if(0!==c.exitCode)t=en(c,"adb pull");else{await r.waitForStableFile(n,{pollMs:250,attempts:20});let t=await r.isPlayableVideo(n);if(f({level:"debug",phase:"record_stop_android_pull_validation",data:{deviceId:i,remotePath:o,outPath:n,attempt:e+1,fileSize:function(e){try{return a.statSync(e).size}catch{return 0}}(n),playable:t}}),t)return;f({level:"warn",phase:"record_stop_android_invalid_video_retry",data:{deviceId:i,remotePath:o,outPath:n,attempt:e+1}})}e<1&&await p(750)}return t?`failed to copy recording from device: ${t}`:(ef(n),"failed to copy recording from device: pulled file is not a playable MP4")}function ef(e){try{a.rmSync(e,{force:!0})}catch{}}let ew={medium:8e6,high:2e7};async function eg(e,t,r){return await g(w(e),t,r)}async function ey(e,t){let r=await eg(e,["shell","ps","-o","pid=","-p",t],{allowFailure:!0});return 0===r.exitCode&&r.stdout.split(/\s+/).map(e=>e.trim()).includes(t)}async function e_(e,t){for(let r=0;r<40;r+=1){if(!await ey(e,t))return!0;await p(250)}return!await ey(e,t)}async function ev(e,t){let r,i=0;for(let a=0;a<20;a+=1){let a=await eg(e,["shell","stat","-c","%s",t],{allowFailure:!0}),o=0===a.exitCode?a.stdout.trim():"";if(o.length>0&&o===r){if((i+=1)>=4)return}else i=0;r=o,await p(250)}}async function eP(e,t,r){for(let i=0;i<8;i+=1){let a=await eg(e,["shell","stat","-c","%s",t],{allowFailure:!0}),o=0===a.exitCode?Number(a.stdout.trim()):NaN;if(Number.isFinite(o)&&o>0)return!0;if(!await ey(e,r))break;if(i+1>=2)return!0;await p(250)}return!1}async function eS(e){let{deviceId:t,maxSize:r}=e;if(void 0===r)return;let i=await eg(t,["shell","wm","size"],{allowFailure:!0}),a=i.stdout.match(/Override size:\s*(\d+)x(\d+)/)??i.stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(0!==i.exitCode||!a)throw Error(`failed to resolve Android screen size for recording max-size: ${en(i,"adb shell wm size")}`);var o={width:Number(a[1]),height:Number(a[2]),maxSize:r};let n=Math.max(o.width,o.height);if(n<=o.maxSize)return{width:o.width,height:o.height};let s=o.maxSize/n;return{width:eA(o.width,s),height:eA(o.height,s)}}function eA(e,t){return Math.max(2,2*Math.round(e*t/2))}async function eb(e,t){await eg(e,["shell","rm","-f",t],{allowFailure:!0})}async function eM(e,t){let r=await eg(e,["shell","kill","-9",t],{allowFailure:!0});return f({level:"warn",phase:"record_stop_android_force_signal",data:{deviceId:e,remotePid:t,exitCode:r.exitCode,stdout:r.stdout.trim(),stderr:r.stderr.trim()}}),!(0!==r.exitCode&&await ey(e,t))&&await e_(e,t)}async function eC(e){var t;let r,i,{device:a,recordingSize:o,quality:n,preferredRemoteDir:s}=e,c="failed to start recording: Android screenrecord did not begin producing frames";for(let e of(t=Date.now(),r=`agent-device-recording-${t}.mp4`,i=["/sdcard","/data/local/tmp"],(s&&i.includes(s)?[s,...i.filter(e=>e!==s)]:i).map(e=>`${e}/${r}`))){let t=await eg(a.id,["shell",function(e,t,r){let i=["screenrecord"];return t&&i.push("--size",`${t.width}x${t.height}`),i.push("--bit-rate",String(ew[r])),i.push(e),`${i.join(" ")} >/dev/null 2>&1 & echo $!`}(e,o,n)],{allowFailure:!0});if(0!==t.exitCode){c=`failed to start recording: ${en(t,"adb shell screenrecord")}`;continue}let r=t.stdout.split(/\r?\n/).map(e=>e.trim()).filter(e=>/^\d+$/.test(e)).at(-1);if(!r){c="failed to start recording: adb did not return a valid Android screenrecord pid",await eb(a.id,e);continue}if(f({level:"debug",phase:"record_start_android_started",data:{deviceId:a.id,remotePath:e,remotePid:r}}),await eP(a.id,e,r))return{remotePath:e,remotePid:r,startedAt:Date.now()};c="failed to start recording: Android screenrecord did not begin producing frames",await eM(a.id,r),await eb(a.id,e)}return{error:S("COMMAND_FAILED",c)}}async function ex(e){let t,{device:r,recordingBase:i}=e;try{t=await eS({deviceId:r.id,maxSize:i.maxSize})}catch(e){return S("COMMAND_FAILED",e instanceof Error?e.message:String(e))}let a=i.exportQuality??"medium",o=await eC({device:r,recordingSize:t,quality:a});if("error"in o)return o.error;let n={platform:"android",remotePath:o.remotePath,remotePid:o.remotePid,chunks:[{index:1,path:i.outPath,remotePath:o.remotePath}],...i,startedAt:o.startedAt};return!function e(t){let{recording:r,startNextChunk:i,finishCurrentChunk:a}=t,o=setTimeout(()=>{r.rotationPromise=el({recording:r,startNextChunk:i,finishCurrentChunk:a}).catch(e=>{r.rotationFailedReason=e instanceof Error?e.message:String(e)}).finally(()=>{r.rotationPromise=void 0,r.stopping||r.rotationFailedReason||e({recording:r,startNextChunk:i,finishCurrentChunk:a})})},17e4);o.unref?.(),r.rotationTimer=o}({recording:n,finishCurrentChunk:async()=>await eI({device:r,recording:n,waitForRemoteFileStability:!1}),startNextChunk:async e=>{let i=await eC({device:r,recordingSize:t,quality:a,preferredRemoteDir:e});if("error"in i)throw Error(i.error.ok?"failed to start next Android recording chunk":i.error.error.message);return i}}),n}async function eI(e){let{device:t,recording:r,waitForRemoteFileStability:i=!0}=e;await ey(t.id,r.remotePid)||(r.warning??=function(e){if(!(Date.now()-e.startedAt<178e3))return"Android adb screenrecord stopped before record stop, likely after reaching the 180s platform limit. The MP4 may be truncated; final interactions after the limit are not in the video."}(r));let a=await eg(t.id,["shell","kill","-2",r.remotePid],{allowFailure:!0});if(f({level:"debug",phase:"record_stop_android_signal",data:{deviceId:t.id,remotePath:r.remotePath,remotePid:r.remotePid,exitCode:a.exitCode,stdout:a.stdout.trim(),stderr:a.stderr.trim()}}),0!==a.exitCode)return await eR(t.id,r.remotePid,a);let o=await eD(t.id,r.remotePid);if(o)return o;i&&await ev(t.id,r.remotePath)}async function eR(e,t,r){if(await ey(e,t)&&!await eM(e,t))return`failed to stop recording: ${en(r,"adb shell kill")}`}async function eD(e,t){if(!await e_(e,t)&&!await eM(e,t))return`failed to stop recording: Android screenrecord pid ${t} did not exit`}async function eN(e){let t,{deps:r,device:i,recording:a,stopRequestedAt:o}=e;f({level:"debug",phase:"record_stop_android_enter",data:{deviceId:i.id,remotePath:a.remotePath,remotePid:a.remotePid}}),a.stopping=!0,a.rotationTimer&&(clearTimeout(a.rotationTimer),a.rotationTimer=void 0),await a.rotationPromise;let n=await eI({device:i,recording:a});if(a.rotationFailedReason&&!n&&(a.warning??=`Android recording chunk rotation failed: ${a.rotationFailedReason}`),!n){let e=await ep({deps:r,deviceId:i.id,chunks:eu(a)});if(e)return await s(),S("COMMAND_FAILED",eE(e,a,o));await em({recording:a,deps:r})}if(await s(),n)return S("COMMAND_FAILED",eE(n,a,o));if(t)return S("COMMAND_FAILED",t);return null;async function s(){for(let e of eu(a)){let r=await eg(i.id,["shell","rm","-f",e.remotePath],{allowFailure:!0});f({level:"debug",phase:"record_stop_android_cleanup",data:{deviceId:i.id,remotePath:e.remotePath,exitCode:r.exitCode,stdout:r.stdout.trim(),stderr:r.stderr.trim()}}),0===r.exitCode||n||(t=`failed to clean up remote recording: ${en(r,"adb shell rm")}`)}}}function eE(e,t,r){return es(e,t,r).message}function eO(e){let t=e.appBundleId?.trim();return t&&t.length>0?t:void 0}function e$(e,t,r){return{verbose:e.flags?.verbose,logPath:t,traceLogPath:r.trace?.outPath,requestId:e.meta?.requestId,iosXctestrunFile:e.flags?.iosXctestrunFile,iosXctestDerivedDataPath:e.flags?.iosXctestDerivedDataPath,iosXctestEnvDir:e.flags?.iosXctestEnvDir}}async function ek(e){let{req:t,activeSession:r,device:i,logPath:a,deps:o}=e,n=eO(r);try{await o.runIosRunnerCommand(i,{command:"recordStop",appBundleId:n},e$(t,a,r))}catch(e){f({level:"warn",phase:"record_stop_runner_failed",data:{platform:i.platform,kind:i.kind,deviceId:i.id,session:r.name,error:eo(e)}})}}async function eF(e){let{req:t,activeSession:r,device:i,logPath:a,deps:o}=e,n=eO(r);if(n)try{let e=await o.runIosRunnerCommand(i,{command:"snapshot",appBundleId:n,interactiveOnly:!0,depth:1},e$(t,a,r)),s=Date.now();if("number"==typeof e.currentUptimeMs&&Number.isFinite(e.currentUptimeMs)&&e.currentUptimeMs>0)return f({level:"debug",phase:"record_start_gesture_clock_anchor",data:{source:"warm_snapshot"}}),{gestureClockOriginAtMs:s,gestureClockOriginUptimeMs:e.currentUptimeMs};return}catch(e){f({level:"warn",phase:"record_start_simulator_runner_warm_failed",data:{deviceId:i.id,session:r.name,appBundleId:n,error:eo(e)}});return}}async function eT(e){let t,r,{req:i,activeSession:a,sessionStore:o,device:n,logPath:s,deps:c,fpsFlag:d,recordingBase:u,appBundleId:l}=e,m=`agent-device-recording-${Date.now()}.mp4`,p=`tmp/${m}`,h=e$(i,s,a),w=async()=>c.runIosRunnerCommand(n,{command:"recordStart",outPath:m,fps:d,maxSize:u.maxSize,appBundleId:l},h);try{let e=await w();t="number"==typeof e.recorderStartUptimeMs?e.recorderStartUptimeMs:void 0,r="number"==typeof e.targetAppReadyUptimeMs?e.targetAppReadyUptimeMs:void 0}catch(i){var g,y;if(!eo(i).toLowerCase().includes("recording already in progress"))return S("COMMAND_FAILED",`failed to start recording: ${eo(i)}`);f({level:"warn",phase:"record_start_runner_desynced",data:{platform:n.platform,kind:n.kind,deviceId:n.id,session:a.name,error:eo(i)}});let e=(g=n.id,y=a.name,o.toArray().find(e=>e.name!==y&&"ios"===e.device.platform&&"device"===e.device.kind&&e.device.id===g&&e.recording?.platform==="ios-device-runner"));if(e)return S("COMMAND_FAILED",`failed to start recording: recording already in progress in session '${e.name}'`);try{await c.runIosRunnerCommand(n,{command:"recordStop",appBundleId:l},h)}catch{}try{let e=await w();t="number"==typeof e.recorderStartUptimeMs?e.recorderStartUptimeMs:void 0,r="number"==typeof e.targetAppReadyUptimeMs?e.targetAppReadyUptimeMs:void 0}catch(e){return S("COMMAND_FAILED",`failed to start recording: ${eo(e)}`)}}return{platform:"ios-device-runner",remotePath:p,runnerStartedAtUptimeMs:t,targetAppReadyUptimeMs:r,...u}}async function eL(e){let{req:t,activeSession:r,device:i,logPath:a,deps:o,fpsFlag:n,recordingBase:s,appBundleId:c}=e;try{await o.runIosRunnerCommand(i,{command:"recordStart",outPath:s.outPath,fps:n,maxSize:s.maxSize,appBundleId:c},e$(t,a,r))}catch(e){return S("COMMAND_FAILED",`failed to start recording: ${eo(e)}`)}return{platform:"macos-runner",...s}}async function eU(e){let{req:t,activeSession:r,device:i,logPath:a,deps:o,recording:n}=e;await ek({req:t,activeSession:r,device:i,logPath:a,deps:o});let s={stdout:"",stderr:"",exitCode:1};for(let e of x)if(0===(s=await o.runCmd("xcrun",["devicectl","device","copy","from","--device",i.id,"--source",n.remotePath,"--destination",n.outPath,"--domain-type","appDataContainer","--domain-identifier",e],{allowFailure:!0})).exitCode)break;if(0!==s.exitCode){let e=s.stderr.trim()||s.stdout.trim()||`devicectl exited with code ${s.exitCode}`;return S("COMMAND_FAILED",`failed to copy recording from device: ${e}`)}let c="number"!=typeof n.runnerStartedAtUptimeMs||"number"!=typeof n.targetAppReadyUptimeMs?0:Math.max(0,n.targetAppReadyUptimeMs-n.runnerStartedAtUptimeMs);return c>0&&await o.trimRecordingStart({videoPath:n.outPath,trimStartMs:c}),await ec({recording:n,deps:o,trimStartMs:c,targetLabel:"iOS recording"}),null}async function ez(e){let{req:t,activeSession:r,device:i,logPath:a,deps:o,recording:n}=e;return await ek({req:t,activeSession:r,device:i,logPath:a,deps:o}),await ec({recording:n,deps:o,targetLabel:"macOS recording"}),null}async function eK(e){let{deps:t,recording:r}=e;r.child.kill("SIGINT");let i=await eG(r.wait,5e3);return i||(await eV(t,r,"SIGINT"),(i=await eG(r.wait,2e3))||(r.child.kill("SIGTERM"),await eV(t,r,"SIGTERM"),i=await eG(r.wait,2e3)))?i:(r.child.kill("SIGKILL"),await eV(t,r,"SIGKILL"),await eG(r.wait,2e3))}async function eG(e,t){return await Promise.race([e,p(t).then(()=>null)])}async function eV(e,t,r){await ej(e,t,r)||await eH(e,t.outPath,r)}async function eH(e,t,r){let i,a=`simctl.*recordVideo.*${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}`;try{i=await e.runCmd("pgrep",["-f",a],{allowFailure:!0})}catch(e){f({level:"warn",phase:"record_stop_ios_simulator_pgrep_failed",data:{outPath:t,signal:r,error:eo(e)}});return}let o=eW(eX(i.stdout)),n=eB(o,r);f({level:n>0?"warn":"debug",phase:"record_stop_ios_simulator_signal_recorders",data:{outPath:t,signal:r,matchedPidCount:o.length,signaled:n,pgrepExitCode:i.exitCode}})}async function ej(e,t,r){let i=t.recorderPid??t.child.pid;if("number"!=typeof i||!Number.isInteger(i)||i<=0)return f({level:"debug",phase:"record_stop_ios_simulator_owned_recorder_unavailable",data:{outPath:t.outPath,signal:r,reason:"missing_recorder_pid"}}),!1;let a=await eq(e,i,t.outPath,r),o=eW([i,...a.pids]),n=eB(o,r);return f({level:n>0?"warn":"debug",phase:"record_stop_ios_simulator_signal_owned_recorder",data:{outPath:t.outPath,signal:r,recorderPid:i,childPidCount:a.pids.length,matchedPidCount:o.length,signaled:n,pgrepExitCode:a.exitCode}}),n>0}async function eq(e,t,r,i){let a;try{a=await e.runCmd("pgrep",["-P",String(t)],{allowFailure:!0})}catch(e){return f({level:"warn",phase:"record_stop_ios_simulator_owned_pgrep_failed",data:{outPath:r,signal:i,parentPid:t,error:eo(e)}}),{pids:[]}}return{pids:eX(a.stdout),exitCode:a.exitCode}}function eW(e){return Array.from(new Set(e)).filter(e=>Number.isInteger(e)&&e>0&&e!==process.pid)}function eB(e,t){let r=0;for(let i of e)try{process.kill(i,t),r+=1}catch{}return r}function eX(e){return e.split(/\s+/).map(e=>Number(e)).filter(e=>Number.isInteger(e)&&e>0)}async function eQ(e){let t,r,{req:i,activeSession:a,device:o,logPath:n,deps:s,recordingBase:c,resolvedOut:d}=e,u=c.showTouches?await eF({req:i,activeSession:a,device:o,logPath:n,deps:s}):void 0,{child:l,wait:m}=s.startIosSimulatorRecording({device:o,outPath:d}),p=await eZ(d);if(u)t=u.gestureClockOriginAtMs,r=u.gestureClockOriginUptimeMs;else if(c.showTouches)try{let e=Date.now(),c=await s.runIosRunnerCommand(o,{command:"uptime",appBundleId:eO(a)},e$(i,n,a)),d=Date.now();t=Math.round((e+d)/2),r="number"==typeof c.currentUptimeMs?c.currentUptimeMs:void 0}catch{}return{platform:"ios",child:l,wait:m,...c,recorderPid:l.pid,startedAt:p,gestureClockOriginAtMs:void 0===r?void 0:t,gestureClockOriginUptimeMs:r}}async function eJ(e){let{deps:t,recording:r,stopRequestedAt:i}=e;await h("record_stop_tail_settle",()=>t.waitForRecordingTail(r),{platform:r.platform,gestureEventCount:r.gestureEvents.length});let a=await h("record_stop_ios_simulator_process",()=>eK({deps:t,recording:r}),{outPath:r.outPath});if(!a)return eY("failed to stop recording: simctl recordVideo did not exit after 5000ms and forced cleanup",r,i);if(0!==a.exitCode)return eY(`failed to stop recording: ${en(a,"simctl recordVideo")}`,r,i);if(await h("record_stop_video_stable",()=>t.waitForStableFile(r.outPath,{pollMs:150,attempts:12}),{outPath:r.outPath}),!await h("record_stop_video_playable_check",()=>t.isPlayableVideo(r.outPath),{outPath:r.outPath}))return eY(`failed to stop recording: ${r.outPath} was not finalized into a playable video`,r,i);if(void 0!==r.maxSize)try{await h("record_stop_resize",()=>t.resizeRecording({videoPath:r.outPath,maxSize:r.maxSize,exportQuality:r.exportQuality,targetLabel:"iOS recording"}),{outPath:r.outPath,maxSize:r.maxSize})}catch(e){r.overlayWarning=`failed to resize recording: ${eo(e)}`}return await h("record_stop_finalize_overlay",()=>ec({recording:r,deps:t,targetLabel:"iOS recording"}),{outPath:r.outPath,showTouches:r.showTouches,gestureEventCount:r.gestureEvents.length}),null}async function eZ(e){for(let t=0;t<2;t+=1){try{if(a.statSync(e).size>0)return Date.now()}catch{}if(t+1>=2)break;await p(250)}return Date.now()}function eY(e,t,r){let i=es(e,t,r);return function(e){try{a.rmSync(e,{force:!0})}catch{}}(t.outPath),S("COMMAND_FAILED",i.message)}function e0(e){return"web"===e.platform?e2:"android"===e.platform?e8:"macos"===e.platform?e5:"ios"===e.platform&&"device"===e.kind?e4:"ios"===e.platform?e3:e7}function e1({req:e}){return e.positionals?.[1]??I(void 0)}let e2={validateStart:e=>(function(e){let t,r=(t=[],e.flags?.fps!==void 0&&t.push("--fps"),e.flags?.quality!==void 0&&t.push("--quality"),e.flags?.screenshotMaxSize!==void 0&&t.push("--max-size"),e.flags?.hideTouches!==void 0&&t.push("--hide-touches"),t);return r.length>0?S("INVALID_ARGS",`web recordings do not support ${r.join(", ")}; agent-browser records WebM directly`):null})(e),resolveOutputPath:function({req:e}){let t=e.positionals?.[1];return void 0===t?I("web"):R(t,".webm")},start:async({activeSession:e,recordingBase:t,resolvedOut:r})=>{var i;let a=(i=r,".webm"!==o.extname(i).toLowerCase()?S("INVALID_ARGS","web recordings must use a .webm output path"):null);if(a)return a;if(e.recordOnlySession)return S("INVALID_ARGS","record on web requires an active browser session; run open <url> --platform web first");let n=D();return n.startRecording?(await n.startRecording(r),{...t,outPath:r,startedAt:Date.now(),platform:"web",showTouches:!1}):S("UNSUPPORTED_OPERATION","record is not supported by this web provider")},stop:async({recording:e})=>await e6({recording:e}),cleanupRecordOnlySession:async()=>{try{await D().close()}catch{}}},e4={resolveOutputPath:e1,start:async({req:e,activeSession:t,sessionStore:r,device:i,logPath:a,deps:o,fpsFlag:n,recordingBase:s})=>{let c=eO(t);return c?await eT({req:e,activeSession:t,sessionStore:r,device:i,logPath:a,deps:o,fpsFlag:n,recordingBase:s,appBundleId:c}):S("INVALID_ARGS","record on physical iOS devices requires an active app session; run open <app> first")},stop:async({req:e,activeSession:t,device:r,logPath:i,deps:a,recording:o})=>await eU({req:e,activeSession:t,device:r,logPath:i,deps:a,recording:o})},e5={resolveOutputPath:e1,start:async({req:e,activeSession:t,device:r,logPath:i,deps:a,fpsFlag:o,recordingBase:n})=>{let s=eO(t);return s?await eL({req:e,activeSession:t,device:r,logPath:i,deps:a,fpsFlag:o,recordingBase:n,appBundleId:s}):S("INVALID_ARGS","record on macOS requires an active app session; run open <app> first")},stop:async({req:e,activeSession:t,device:r,logPath:i,deps:a,recording:o})=>await ez({req:e,activeSession:t,device:r,logPath:i,deps:a,recording:o})},e3={resolveOutputPath:e1,start:async({req:e,activeSession:t,device:r,logPath:i,deps:a,recordingBase:o,resolvedOut:n})=>await eQ({req:e,activeSession:t,device:r,logPath:i,deps:a,recordingBase:o,resolvedOut:n}),stop:async({deps:e,recording:t,stopRequestedAt:r})=>await eJ({deps:e,recording:t,stopRequestedAt:r})},e8={resolveOutputPath:e1,start:async({device:e,recordingBase:t})=>await ex({device:e,recordingBase:t}),stop:async({deps:e,device:t,recording:r,stopRequestedAt:i})=>await eN({deps:e,device:t,recording:r,stopRequestedAt:i})},e7={resolveOutputPath:e1,start:async()=>S("UNSUPPORTED_OPERATION","record is not supported on this device"),stop:async()=>S("UNSUPPORTED_OPERATION","record is not supported on this device")};async function e6(e){let{recording:t}=e,r=D();if(!r.stopRecording)return S("UNSUPPORTED_OPERATION","record is not supported by this web provider");if(await r.stopRecording(),!function(e){try{return a.statSync(e).size>0}catch{return!1}}(t.outPath)){var i=t.outPath;try{a.rmSync(i,{force:!0})}catch{}return S("COMMAND_FAILED",`failed to stop recording: ${t.outPath} was not finalized into a WebM video`)}return null}async function e9(e){"ios"!==e.platform||0!==e.gestureEvents.length&&await p(350)}async function te(e){let t,{req:r,sessionName:i,sessionStore:n,activeSession:s,device:c,logPath:d,deps:u}=e;if(s.recording)return S("INVALID_ARGS","recording already in progress");let l=r.flags?.fps,m=r.flags?.quality,p=r.flags?.screenshotMaxSize,h=e0(c),f=h.validateStart?.(r)??null;if(f)return f;if(void 0!==l&&(!Number.isInteger(l)||l<1||l>120))return S("INVALID_ARGS","fps must be an integer between 1 and 120");if(void 0!==m&&void 0===O(m))return S("INVALID_ARGS",`quality must be one of: ${$.join(", ")} (legacy numeric values 5-10 are accepted)`);if(void 0!==p&&(!Number.isInteger(p)||p<1))return S("INVALID_ARGS","max-size must be a positive integer");if(!M("record",c))return S("UNSUPPORTED_OPERATION","record is not supported on this device");let w=h.resolveOutputPath({req:r}),g=v.expandHome(w,r.meta?.cwd),y=(t=O(r.flags?.quality),{outPath:g,clientOutPath:r.meta?.clientArtifactPaths?.outPath,startedAt:Date.now(),maxSize:r.flags?.screenshotMaxSize,exportQuality:t??"medium",showTouches:r.flags?.hideTouches!==!0,gestureEvents:[]});a.mkdirSync(o.dirname(g),{recursive:!0}),a.rmSync(g,{force:!0});let _=await h.start({req:r,activeSession:s,sessionStore:n,device:c,logPath:d,deps:u,fpsFlag:l,recordingBase:y,resolvedOut:g});if("ok"in _)return _;s.recording=_,n.set(i,s);let P=n.ensureSessionDir(i);return n.recordAction(s,{command:r.command,positionals:r.positionals??[],flags:r.flags??{},result:{action:"start",showTouches:_.showTouches}}),{ok:!0,data:{recording:"started",outPath:_.clientOutPath??w,sessionStateDir:P,showTouches:_.showTouches}}}async function tt(e){var t;let r,i,{req:a,activeSession:n,device:s,logPath:c,deps:d}=e;if(!n.recording)return S("INVALID_ARGS","no active recording");let u=n.recording,l=Date.now(),m=u.invalidatedReason;n.recording=void 0;let p=function(e){switch(e.platform){case"android":return e8;case"ios":return e3;case"ios-device-runner":return e4;case"macos-runner":return e5;case"web":return e2}return e}(u),h=await p.stop({req:a,activeSession:n,device:s,logPath:c,deps:d,recording:u,stopRequestedAt:l});if(h)return h;if(m&&"ios"===u.platform&&u.showTouches)u.overlayWarning??=`overlay unavailable: ${m}`;else if(m)return S("COMMAND_FAILED",m);return r="android"===(t=u).platform?t.chunks:void 0,i=[{field:"outPath",path:t.outPath,localPath:t.clientOutPath,fileName:o.basename(t.clientOutPath??t.outPath)}],r&&r.length>1&&i.push(...r.slice(1).map(e=>({field:"chunkPath",path:e.path,localPath:tr(t,e.index),fileName:o.basename(tr(t,e.index)??e.path)}))),t.telemetryPath&&i.push({field:"telemetryPath",path:t.telemetryPath,localPath:function(e){if(e.clientOutPath)return J(e.clientOutPath)}(t),fileName:o.basename(t.telemetryPath)}),{ok:!0,data:{recording:"stopped",outPath:t.outPath,telemetryPath:t.telemetryPath,artifacts:i,showTouches:t.showTouches,warning:t.warning,overlayWarning:t.overlayWarning,chunks:r?.map(e=>({index:e.index,path:tr(t,e.index)??e.path}))}}}function tr(e,t){if("android"===e.platform&&e.clientOutPath)return ed(e.clientOutPath,t)}async function ti(e,t,r,i={}){if(!r.recordOnlySession)return;let a=e0(r.device);await a.cleanupRecordOnlySession?.(r),i.writeLog&&e.writeSessionLog(r),e.delete(t)}async function ta(e){let{req:t,sessionName:r,sessionStore:i,logPath:a}=e,o={runCmd:async(e,t,r)=>"xcrun"===e?await _(t,r):await u(e,t,r),startIosSimulatorRecording:e=>E().startIosSimulatorRecording(e),runIosRunnerCommand:P,waitForRecordingTail:e9,waitForStableFile:j,isPlayableVideo:q,trimRecordingStart:er,resizeRecording:ea,overlayRecordingTouches:ei},n=i.get(r),s=n?.device??await C(t.flags??{});n||await N(s);let c=n??{name:b(t),sessionScope:A(t),device:s,createdAt:Date.now(),recordOnlySession:!0,actions:[]},d=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(d))return S("INVALID_ARGS","record requires start|stop");if("start"===d)return te({req:t,sessionName:r,sessionStore:i,activeSession:c,device:s,logPath:a,deps:o});let l=await tt({req:t,activeSession:c,device:s,logPath:a,deps:o});return l.ok?(i.recordAction(c,{command:t.command,positionals:t.positionals??[],flags:t.flags??{},result:{action:"stop",outPath:l.data?.outPath,showTouches:l.data?.showTouches}}),await ti(i,r,c,{writeLog:!0})):await ti(i,r,c),l}export{ta as handleRecordCommand};
|
|
25
|
+
`.trim();async function J(e,t={}){let r,a=t.pollMs??150,o=t.attempts??12,n=0;for(let t=0;t<o;t+=1){let t=0;try{t=i.statSync(e).size}catch{t=0}if(t>0&&t===r){if((n+=1)>=2)return}else n=0;r=t,await f(a)}}async function X(e){try{let t=await Z(),r=await p(t,[e],{allowFailure:!0,timeoutMs:1e4,env:z()});if(0===r.exitCode)return!0;if(ee(r.stderr,r.stdout))return et(e);return!1}catch(r){var t;if((t=r)instanceof d&&("TOOL_MISSING"===t.code||ee(String(t.details?.stderr??""),String(t.details?.stdout??""))))return et(e);throw r}}async function Z(){e??=G({source:Q,cacheName:"video-validator",timeoutMs:3e4});try{return await e}catch(t){throw e=void 0,t}}async function Y(e,t={}){let r=t.pollMs??150,a=t.attempts??12;for(let t=0;t<a;t+=1){if(await X(e))return;await f(r)}}function ee(e,t){let r=`${e}
|
|
26
|
+
${t}`;return/\b(no such module ['"]AVFoundation['"]|unable to find utility ["']swiftc?["']|xcrun: error: unable to find utility ["']swiftc?["'])\b/i.test(r)}function et(e){try{let t=i.statSync(e);if(!t.isFile()||t.size<=0)return!1}catch{return!1}let t=function(e){try{let t=i.openSync(e,"r");try{let e=i.fstatSync(t).size,r=0,a=[];for(;r+8<=e&&a.length<16;){let e=Buffer.alloc(8);if(8>i.readSync(t,e,0,8,r))break;let o=e.readUInt32BE(0),n=e.toString("latin1",4,8);if(a.push(n),1===o){let e=Buffer.alloc(8);if(8>i.readSync(t,e,0,8,r+8))break;o=Number(e.readBigUInt64BE(0))}if(!Number.isFinite(o)||o<=0)break;r+=o}return a}finally{i.closeSync(t)}}catch{return[]}}(e);return t.includes("ftyp")&&t.includes("moov")}function er(e){let t=o.parse(e);return o.join(t.dir,`${t.name}.gesture-telemetry.json`)}function ea(e){return[...e].sort((e,t)=>e.tMs-t.tMs)}function ei(e){var t,r,a;let o,n,{recording:s,trimStartMs:c}=e,d=(o=er((t={videoPath:s.outPath,events:s.gestureEvents,trimStartMs:c}).videoPath),n={version:1,generatedAt:new Date().toISOString(),events:(r=t.events,(a=t.trimStartMs??0)>0?ea(r.flatMap(e=>{let t=e.tMs-a,r="durationMs"in e?e.durationMs:void 0;return("number"==typeof r?t+r:t)<=0?[]:[{...e,tMs:Math.max(0,t)}]})):ea(r))},i.writeFileSync(o,JSON.stringify(n,null,2)),o);return s.telemetryPath=d,d}function eo(e){let t=o.dirname(c(import.meta.url)),r=[c(new URL(`./${e}`,import.meta.url)),o.resolve(t,`../../apple-runner/AgentDeviceRunner/RecordingScripts/${e}`),o.resolve(t,`../../../apple-runner/AgentDeviceRunner/RecordingScripts/${e}`),o.resolve(process.cwd(),`apple-runner/AgentDeviceRunner/RecordingScripts/${e}`)];for(let e of r)if(i.existsSync(e))return e;throw new d("COMMAND_FAILED",`Missing recording helper script: ${e}`,{hint:"Ensure apple-runner/AgentDeviceRunner/RecordingScripts is present in this checkout or bundled with the package.",scriptName:e,searchedPaths:r})}async function en(e){var t;let r,a,{videoPath:n,scriptPath:s,scriptArgs:c,commandDescription:u}=e;await J(n),await Y(n);let l=(t=n,r=o.parse(t),a=`${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`,o.join(r.dir,`.${r.name}.agent-device-${a}${r.ext||".mp4"}`));try{let e=await K({sourcePath:s});await p(e,["--input",n,"--output",l,...c],{timeoutMs:12e4,env:z()}),await Y(l),i.renameSync(l,n)}catch(t){let e=t instanceof d?t:new d("COMMAND_FAILED",String(t),void 0,t instanceof Error?t:void 0);throw new d("COMMAND_FAILED",u,{...e.details,videoPath:n,script:s},e)}finally{i.rmSync(l,{force:!0})}}async function es(e){let{videoPath:t,trimStartMs:a}=e;a>0&&await en({videoPath:t,scriptPath:r??=eo("recording-trim.swift"),scriptArgs:["--trim-start-ms",String(a)],commandDescription:"Failed to trim the start of the iOS recording"})}async function ec(e){let{videoPath:r,telemetryPath:a,exportQuality:i="medium",targetLabel:o="recording"}=e;await en({videoPath:r,scriptPath:t??=eo("recording-overlay.swift"),scriptArgs:["--events",a,"--quality",i],commandDescription:`Failed to add touch overlays to the ${o}`})}async function ed(e){let{videoPath:t,maxSize:r,exportQuality:i="medium",targetLabel:o="recording"}=e;await en({videoPath:t,scriptPath:a??=eo("recording-resize.swift"),scriptArgs:["--max-size",String(r),"--quality",i],commandDescription:`Failed to resize the ${o}`})}function eu(e){return e instanceof Error?e.message:String(e)}function el(e,t){return e.stderr.trim()||e.stdout.trim()||`${t} exited with code ${e.exitCode}`}function ep(e,t,r=Date.now()){let a=Math.max(0,r-t.startedAt);return a>=1e3?{message:e,tooShort:!1}:{message:`${e}. Recording stopped after ${Math.round(a)}ms; wait at least 1000ms between record start and record stop so the recorder can finalize a playable MP4`,tooShort:!0}}async function em(e){let{recording:t,deps:r,trimStartMs:a,targetLabel:i}=e,o=ei({recording:t,trimStartMs:a});if(!t.showTouches)return void l({level:"debug",phase:"record_stop_overlay_skipped",data:{reason:"hide_touches"}});if(0===t.gestureEvents.length)return void l({level:"debug",phase:"record_stop_overlay_skipped",data:{reason:"no_gesture_events"}});let n=function(e=process.platform){if("darwin"!==e)return"touch overlay burn-in is only available on macOS hosts; returning raw video plus gesture telemetry"}();if(n){t.overlayWarning??=n;return}try{await u("record_stop_overlay_export",()=>r.overlayRecordingTouches({videoPath:t.outPath,telemetryPath:o,exportQuality:t.exportQuality,targetLabel:i}),{targetLabel:i,gestureEventCount:t.gestureEvents.length})}catch(e){t.overlayWarning??=`failed to overlay recording touches: ${eu(e)}`}}function eh(e,t){if(1===t)return e;let r=o.parse(e),a=r.ext||".mp4";return o.join(r.dir,`${r.name}.part-${String(t).padStart(3,"0")}${a}`)}function ef(e){return e.chunks??=[{index:1,path:e.outPath,remotePath:e.remotePath}],e.chunks}async function ew(e){let{recording:t,startNextChunk:r,finishCurrentChunk:a}=e;if(t.stopping)return;let i=await a();if(i)throw Error(i);if(t.stopping)return;let n=ef(t),s=n.length+1,c=await r(o.posix.dirname(t.remotePath));t.remotePath=c.remotePath,t.remotePid=c.remotePid,n.push({index:s,path:eh(t.outPath,s),remotePath:c.remotePath}),t.warning??="Android adb screenrecord is capped at 180s, so this recording was split into multiple MP4 chunks."}async function eg(e){let{recording:t,deps:r}=e;ef(t).length<=1?await em({recording:t,deps:r,targetLabel:"Android recording"}):(ei({recording:t}),t.showTouches&&t.gestureEvents.length>0&&(t.overlayWarning??="touch overlay burn-in is skipped for chunked Android recordings; returning raw chunks plus gesture telemetry"))}async function ey(e){for(let t of e.chunks){let r=await e_({deps:e.deps,deviceId:e.deviceId,remotePath:t.remotePath,outPath:t.path});if(r)return`failed to copy recording chunk ${t.index}: ${r}`}}async function e_(e){let t,{deps:r,deviceId:a,remotePath:o,outPath:n}=e;for(let e=0;e<2;e+=1){ev(n);let s=w(a),c=await y(o,n,{allowFailure:!0,device:s});if(0!==c.exitCode)t=el(c,"adb pull");else{await r.waitForStableFile(n,{pollMs:250,attempts:20});let t=await r.isPlayableVideo(n);if(l({level:"debug",phase:"record_stop_android_pull_validation",data:{deviceId:a,remotePath:o,outPath:n,attempt:e+1,fileSize:function(e){try{return i.statSync(e).size}catch{return 0}}(n),playable:t}}),t)return;l({level:"warn",phase:"record_stop_android_invalid_video_retry",data:{deviceId:a,remotePath:o,outPath:n,attempt:e+1}})}e<1&&await f(750)}return t?`failed to copy recording from device: ${t}`:(ev(n),"failed to copy recording from device: pulled file is not a playable MP4")}function ev(e){try{i.rmSync(e,{force:!0})}catch{}}let eP={medium:8e6,high:2e7};async function eS(e,t,r){return await g(w(e),t,r)}async function eA(e,t){let r=await eS(e,["shell","ps","-o","pid=","-p",t],{allowFailure:!0});return 0===r.exitCode&&r.stdout.split(/\s+/).map(e=>e.trim()).includes(t)}async function eb(e,t){for(let r=0;r<40;r+=1){if(!await eA(e,t))return!0;await f(250)}return!await eA(e,t)}async function eM(e,t){let r,a=0;for(let i=0;i<20;i+=1){let i=await eS(e,["shell","stat","-c","%s",t],{allowFailure:!0}),o=0===i.exitCode?i.stdout.trim():"";if(o.length>0&&o===r){if((a+=1)>=4)return}else a=0;r=o,await f(250)}}async function eC(e,t,r){for(let a=0;a<8;a+=1){let i=await eS(e,["shell","stat","-c","%s",t],{allowFailure:!0}),o=0===i.exitCode?Number(i.stdout.trim()):NaN;if(Number.isFinite(o)&&o>0)return!0;if(!await eA(e,r))break;if(a+1>=2)return!0;await f(250)}return!1}async function ex(e){let{deviceId:t,maxSize:r}=e;if(void 0===r)return;let a=await eS(t,["shell","wm","size"],{allowFailure:!0}),i=a.stdout.match(/Override size:\s*(\d+)x(\d+)/)??a.stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(0!==a.exitCode||!i)throw Error(`failed to resolve Android screen size for recording max-size: ${el(a,"adb shell wm size")}`);var o={width:Number(i[1]),height:Number(i[2]),maxSize:r};let n=Math.max(o.width,o.height);if(n<=o.maxSize)return{width:o.width,height:o.height};let s=o.maxSize/n;return{width:eR(o.width,s),height:eR(o.height,s)}}function eR(e,t){return Math.max(2,2*Math.round(e*t/2))}async function eI(e,t){await eS(e,["shell","rm","-f",t],{allowFailure:!0})}async function eD(e,t){let r=await eS(e,["shell","kill","-9",t],{allowFailure:!0});return l({level:"warn",phase:"record_stop_android_force_signal",data:{deviceId:e,remotePid:t,exitCode:r.exitCode,stdout:r.stdout.trim(),stderr:r.stderr.trim()}}),!(0!==r.exitCode&&await eA(e,t))&&await eb(e,t)}async function eN(e){var t;let r,a,{device:i,recordingSize:o,quality:n,preferredRemoteDir:s}=e,c="failed to start recording: Android screenrecord did not begin producing frames";for(let e of(t=Date.now(),r=`agent-device-recording-${t}.mp4`,a=["/sdcard","/data/local/tmp"],(s&&a.includes(s)?[s,...a.filter(e=>e!==s)]:a).map(e=>`${e}/${r}`))){let t=await eS(i.id,["shell",function(e,t,r){let a=["screenrecord"];return t&&a.push("--size",`${t.width}x${t.height}`),a.push("--bit-rate",String(eP[r])),a.push(e),`${a.join(" ")} >/dev/null 2>&1 & echo $!`}(e,o,n)],{allowFailure:!0});if(0!==t.exitCode){c=`failed to start recording: ${el(t,"adb shell screenrecord")}`;continue}let r=t.stdout.split(/\r?\n/).map(e=>e.trim()).filter(e=>/^\d+$/.test(e)).at(-1);if(!r){c="failed to start recording: adb did not return a valid Android screenrecord pid",await eI(i.id,e);continue}if(l({level:"debug",phase:"record_start_android_started",data:{deviceId:i.id,remotePath:e,remotePid:r}}),await eC(i.id,e,r))return{remotePath:e,remotePid:r,startedAt:Date.now()};c="failed to start recording: Android screenrecord did not begin producing frames",await eD(i.id,r),await eI(i.id,e)}return{error:b("COMMAND_FAILED",c)}}async function eE(e){let t,{device:r,recordingBase:a}=e;try{t=await ex({deviceId:r.id,maxSize:a.maxSize})}catch(e){return b("COMMAND_FAILED",e instanceof Error?e.message:String(e))}let i=a.exportQuality??"medium",o=await eN({device:r,recordingSize:t,quality:i});if("error"in o)return o.error;let n={platform:"android",remotePath:o.remotePath,remotePid:o.remotePid,chunks:[{index:1,path:a.outPath,remotePath:o.remotePath}],...a,startedAt:o.startedAt};return!function e(t){let{recording:r,startNextChunk:a,finishCurrentChunk:i}=t,o=setTimeout(()=>{r.rotationPromise=ew({recording:r,startNextChunk:a,finishCurrentChunk:i}).catch(e=>{r.rotationFailedReason=e instanceof Error?e.message:String(e)}).finally(()=>{r.rotationPromise=void 0,r.stopping||r.rotationFailedReason||e({recording:r,startNextChunk:a,finishCurrentChunk:i})})},17e4);o.unref?.(),r.rotationTimer=o}({recording:n,finishCurrentChunk:async()=>await e$({device:r,recording:n,waitForRemoteFileStability:!1}),startNextChunk:async e=>{let a=await eN({device:r,recordingSize:t,quality:i,preferredRemoteDir:e});if("error"in a)throw Error(a.error.ok?"failed to start next Android recording chunk":a.error.error.message);return a}}),n}async function e$(e){let{device:t,recording:r,waitForRemoteFileStability:a=!0}=e;await eA(t.id,r.remotePid)||(r.warning??=function(e){if(!(Date.now()-e.startedAt<178e3))return"Android adb screenrecord stopped before record stop, likely after reaching the 180s platform limit. The MP4 may be truncated; final interactions after the limit are not in the video."}(r));let i=await eS(t.id,["shell","kill","-2",r.remotePid],{allowFailure:!0});if(l({level:"debug",phase:"record_stop_android_signal",data:{deviceId:t.id,remotePath:r.remotePath,remotePid:r.remotePid,exitCode:i.exitCode,stdout:i.stdout.trim(),stderr:i.stderr.trim()}}),0!==i.exitCode)return await eO(t.id,r.remotePid,i);let o=await ek(t.id,r.remotePid);if(o)return o;a&&await eM(t.id,r.remotePath)}async function eO(e,t,r){if(await eA(e,t)&&!await eD(e,t))return`failed to stop recording: ${el(r,"adb shell kill")}`}async function ek(e,t){if(!await eb(e,t)&&!await eD(e,t))return`failed to stop recording: Android screenrecord pid ${t} did not exit`}async function eF(e){let t,{deps:r,device:a,recording:i,stopRequestedAt:o}=e;l({level:"debug",phase:"record_stop_android_enter",data:{deviceId:a.id,remotePath:i.remotePath,remotePid:i.remotePid}}),i.stopping=!0,i.rotationTimer&&(clearTimeout(i.rotationTimer),i.rotationTimer=void 0),await i.rotationPromise;let n=await e$({device:a,recording:i});if(i.rotationFailedReason&&!n&&(i.warning??=`Android recording chunk rotation failed: ${i.rotationFailedReason}`),!n){let e=await ey({deps:r,deviceId:a.id,chunks:ef(i)});if(e)return await s(),b("COMMAND_FAILED",eT(e,i,o));await eg({recording:i,deps:r})}if(await s(),n)return b("COMMAND_FAILED",eT(n,i,o));if(t)return b("COMMAND_FAILED",t);return null;async function s(){for(let e of ef(i)){let r=await eS(a.id,["shell","rm","-f",e.remotePath],{allowFailure:!0});l({level:"debug",phase:"record_stop_android_cleanup",data:{deviceId:a.id,remotePath:e.remotePath,exitCode:r.exitCode,stdout:r.stdout.trim(),stderr:r.stderr.trim()}}),0===r.exitCode||n||(t=`failed to clean up remote recording: ${el(r,"adb shell rm")}`)}}}function eT(e,t,r){return ep(e,t,r).message}function eL(e){let t=e.appBundleId?.trim();return t&&t.length>0?t:void 0}function eU(e,t,r){return N({req:e,logPath:t,traceLogPath:r.trace?.outPath})}async function ez(e){let{req:t,activeSession:r,device:a,logPath:i,deps:o}=e,n=eL(r);try{await o.runAppleRunnerCommand(a,{command:"recordStop",appBundleId:n},eU(t,i,r))}catch(e){l({level:"warn",phase:"record_stop_runner_failed",data:{platform:a.platform,kind:a.kind,deviceId:a.id,session:r.name,error:eu(e)}})}}async function eK(e){let{req:t,activeSession:r,device:a,logPath:i,deps:o}=e,n=eL(r);if(n)try{let e=await o.runAppleRunnerCommand(a,{command:"snapshot",appBundleId:n,interactiveOnly:!0,depth:1},eU(t,i,r)),s=Date.now();if("number"==typeof e.currentUptimeMs&&Number.isFinite(e.currentUptimeMs)&&e.currentUptimeMs>0)return l({level:"debug",phase:"record_start_gesture_clock_anchor",data:{source:"warm_snapshot"}}),{gestureClockOriginAtMs:s,gestureClockOriginUptimeMs:e.currentUptimeMs};return}catch(e){l({level:"warn",phase:"record_start_simulator_runner_warm_failed",data:{deviceId:a.id,session:r.name,appBundleId:n,error:eu(e)}});return}}async function eG(e){let t,r,{req:a,activeSession:i,sessionStore:o,device:n,logPath:s,deps:c,fpsFlag:d,recordingBase:u,appBundleId:p}=e,m=`agent-device-recording-${Date.now()}.mp4`,h=`tmp/${m}`,f=eU(a,s,i),w=async()=>c.runAppleRunnerCommand(n,{command:"recordStart",outPath:m,fps:d,maxSize:u.maxSize,appBundleId:p},f);try{let e=await w();t="number"==typeof e.recorderStartUptimeMs?e.recorderStartUptimeMs:void 0,r="number"==typeof e.targetAppReadyUptimeMs?e.targetAppReadyUptimeMs:void 0}catch(a){var g,y;if(!eu(a).toLowerCase().includes("recording already in progress"))return b("COMMAND_FAILED",`failed to start recording: ${eu(a)}`);l({level:"warn",phase:"record_start_runner_desynced",data:{platform:n.platform,kind:n.kind,deviceId:n.id,session:i.name,error:eu(a)}});let e=(g=n.id,y=i.name,o.toArray().find(e=>e.name!==y&&D(e.device)&&"device"===e.device.kind&&e.device.id===g&&e.recording?.platform==="ios-device-runner"));if(e)return b("COMMAND_FAILED",`failed to start recording: recording already in progress in session '${e.name}'`);try{await c.runAppleRunnerCommand(n,{command:"recordStop",appBundleId:p},f)}catch{}try{let e=await w();t="number"==typeof e.recorderStartUptimeMs?e.recorderStartUptimeMs:void 0,r="number"==typeof e.targetAppReadyUptimeMs?e.targetAppReadyUptimeMs:void 0}catch(e){return b("COMMAND_FAILED",`failed to start recording: ${eu(e)}`)}}return{platform:"ios-device-runner",remotePath:h,runnerStartedAtUptimeMs:t,targetAppReadyUptimeMs:r,...u}}async function eV(e){let{req:t,activeSession:r,device:a,logPath:i,deps:o,fpsFlag:n,recordingBase:s,appBundleId:c}=e;try{await o.runAppleRunnerCommand(a,{command:"recordStart",outPath:s.outPath,fps:n,maxSize:s.maxSize,appBundleId:c},eU(t,i,r))}catch(e){return b("COMMAND_FAILED",`failed to start recording: ${eu(e)}`)}return{platform:"macos-runner",...s}}async function eH(e){let{req:t,activeSession:r,device:a,logPath:i,deps:o,recording:n}=e;await ez({req:t,activeSession:r,device:a,logPath:i,deps:o});let s={stdout:"",stderr:"",exitCode:1};for(let e of S)if(0===(s=await o.runCmd("xcrun",["devicectl","device","copy","from","--device",a.id,"--source",n.remotePath,"--destination",n.outPath,"--domain-type","appDataContainer","--domain-identifier",e],{allowFailure:!0})).exitCode)break;if(0!==s.exitCode){let e=s.stderr.trim()||s.stdout.trim()||`devicectl exited with code ${s.exitCode}`;return b("COMMAND_FAILED",`failed to copy recording from device: ${e}`)}let c="number"!=typeof n.runnerStartedAtUptimeMs||"number"!=typeof n.targetAppReadyUptimeMs?0:Math.max(0,n.targetAppReadyUptimeMs-n.runnerStartedAtUptimeMs);return c>0&&await o.trimRecordingStart({videoPath:n.outPath,trimStartMs:c}),await em({recording:n,deps:o,trimStartMs:c,targetLabel:"iOS recording"}),null}async function ej(e){let{req:t,activeSession:r,device:a,logPath:i,deps:o,recording:n}=e;return await ez({req:t,activeSession:r,device:a,logPath:i,deps:o}),await em({recording:n,deps:o,targetLabel:"macOS recording"}),null}async function eq(e){let{deps:t,recording:r}=e;r.child.kill("SIGINT");let a=await eW(r.wait,5e3);return a||(await eB(t,r,"SIGINT"),(a=await eW(r.wait,2e3))||(r.child.kill("SIGTERM"),await eB(t,r,"SIGTERM"),a=await eW(r.wait,2e3)))?a:(r.child.kill("SIGKILL"),await eB(t,r,"SIGKILL"),await eW(r.wait,2e3))}async function eW(e,t){return await Promise.race([e,f(t).then(()=>null)])}async function eB(e,t,r){await eJ(e,t,r)||await eQ(e,t.outPath,r)}async function eQ(e,t,r){let a,i=`simctl.*recordVideo.*${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}`;try{a=await e.runCmd("pgrep",["-f",i],{allowFailure:!0})}catch(e){l({level:"warn",phase:"record_stop_ios_simulator_pgrep_failed",data:{outPath:t,signal:r,error:eu(e)}});return}let o=eZ(e0(a.stdout)),n=eY(o,r);l({level:n>0?"warn":"debug",phase:"record_stop_ios_simulator_signal_recorders",data:{outPath:t,signal:r,matchedPidCount:o.length,signaled:n,pgrepExitCode:a.exitCode}})}async function eJ(e,t,r){let a=t.recorderPid??t.child.pid;if("number"!=typeof a||!Number.isInteger(a)||a<=0)return l({level:"debug",phase:"record_stop_ios_simulator_owned_recorder_unavailable",data:{outPath:t.outPath,signal:r,reason:"missing_recorder_pid"}}),!1;let i=await eX(e,a,t.outPath,r),o=eZ([a,...i.pids]),n=eY(o,r);return l({level:n>0?"warn":"debug",phase:"record_stop_ios_simulator_signal_owned_recorder",data:{outPath:t.outPath,signal:r,recorderPid:a,childPidCount:i.pids.length,matchedPidCount:o.length,signaled:n,pgrepExitCode:i.exitCode}}),n>0}async function eX(e,t,r,a){let i;try{i=await e.runCmd("pgrep",["-P",String(t)],{allowFailure:!0})}catch(e){return l({level:"warn",phase:"record_stop_ios_simulator_owned_pgrep_failed",data:{outPath:r,signal:a,parentPid:t,error:eu(e)}}),{pids:[]}}return{pids:e0(i.stdout),exitCode:i.exitCode}}function eZ(e){return Array.from(new Set(e)).filter(e=>Number.isInteger(e)&&e>0&&e!==process.pid)}function eY(e,t){let r=0;for(let a of e)try{process.kill(a,t),r+=1}catch{}return r}function e0(e){return e.split(/\s+/).map(e=>Number(e)).filter(e=>Number.isInteger(e)&&e>0)}async function e1(e){let t,r,{req:a,activeSession:i,device:o,logPath:n,deps:s,recordingBase:c,resolvedOut:d}=e,u=c.showTouches?await eK({req:a,activeSession:i,device:o,logPath:n,deps:s}):void 0,{child:l,wait:p}=s.startIosSimulatorRecording({device:o,outPath:d}),m=await e4(d);if(u)t=u.gestureClockOriginAtMs,r=u.gestureClockOriginUptimeMs;else if(c.showTouches)try{let e=Date.now(),c=await s.runAppleRunnerCommand(o,{command:"uptime",appBundleId:eL(i)},eU(a,n,i)),d=Date.now();t=Math.round((e+d)/2),r="number"==typeof c.currentUptimeMs?c.currentUptimeMs:void 0}catch{}return{platform:"ios",child:l,wait:p,...c,recorderPid:l.pid,startedAt:m,gestureClockOriginAtMs:void 0===r?void 0:t,gestureClockOriginUptimeMs:r}}async function e2(e){let{deps:t,recording:r,stopRequestedAt:a}=e;await u("record_stop_tail_settle",()=>t.waitForRecordingTail(r),{platform:r.platform,gestureEventCount:r.gestureEvents.length});let i=await u("record_stop_ios_simulator_process",()=>eq({deps:t,recording:r}),{outPath:r.outPath});if(!i)return e5("failed to stop recording: simctl recordVideo did not exit after 5000ms and forced cleanup",r,a);if(0!==i.exitCode)return e5(`failed to stop recording: ${el(i,"simctl recordVideo")}`,r,a);if(await u("record_stop_video_stable",()=>t.waitForStableFile(r.outPath,{pollMs:150,attempts:12}),{outPath:r.outPath}),!await u("record_stop_video_playable_check",()=>t.isPlayableVideo(r.outPath),{outPath:r.outPath}))return e5(`failed to stop recording: ${r.outPath} was not finalized into a playable video`,r,a);if(void 0!==r.maxSize)try{await u("record_stop_resize",()=>t.resizeRecording({videoPath:r.outPath,maxSize:r.maxSize,exportQuality:r.exportQuality,targetLabel:"iOS recording"}),{outPath:r.outPath,maxSize:r.maxSize})}catch(e){r.overlayWarning=`failed to resize recording: ${eu(e)}`}return await u("record_stop_finalize_overlay",()=>em({recording:r,deps:t,targetLabel:"iOS recording"}),{outPath:r.outPath,showTouches:r.showTouches,gestureEventCount:r.gestureEvents.length}),null}async function e4(e){for(let t=0;t<2;t+=1){try{if(i.statSync(e).size>0)return Date.now()}catch{}if(t+1>=2)break;await f(250)}return Date.now()}function e5(e,t,r){let a=ep(e,t,r);return function(e){try{i.rmSync(e,{force:!0})}catch{}}(t.outPath),b("COMMAND_FAILED",a.message)}function e8(e){return tr[v(e.platform)?.recording?.resolveBackendTag(e)??"unsupported"]}function e3({req:e}){return e.positionals?.[1]??$(void 0)}x();let e6={validateStart:e=>(function(e){let t,r=(t=[],e.flags?.fps!==void 0&&t.push("--fps"),e.flags?.quality!==void 0&&t.push("--quality"),e.flags?.screenshotMaxSize!==void 0&&t.push("--max-size"),e.flags?.hideTouches!==void 0&&t.push("--hide-touches"),t);return r.length>0?b("INVALID_ARGS",`web recordings do not support ${r.join(", ")}; agent-browser records WebM directly`):null})(e),resolveOutputPath:function({req:e}){let t=e.positionals?.[1];return void 0===t?$("web"):O(t,".webm")},start:async({activeSession:e,recordingBase:t,resolvedOut:r})=>{var a;let i=(a=r,".webm"!==o.extname(a).toLowerCase()?b("INVALID_ARGS","web recordings must use a .webm output path"):null);if(i)return i;if(e.recordOnlySession)return b("INVALID_ARGS","record on web requires an active browser session; run open <url> --platform web first");let n=k();return n.startRecording?(await n.startRecording(r),{...t,outPath:r,startedAt:Date.now(),platform:"web",showTouches:!1}):b("UNSUPPORTED_OPERATION","record is not supported by this web provider")},stop:async({recording:e})=>await ta({recording:e}),cleanupRecordOnlySession:async()=>{try{await k().close()}catch{}}},e7={resolveOutputPath:e3,start:async({req:e,activeSession:t,sessionStore:r,device:a,logPath:i,deps:o,fpsFlag:n,recordingBase:s})=>{let c=eL(t);return c?await eG({req:e,activeSession:t,sessionStore:r,device:a,logPath:i,deps:o,fpsFlag:n,recordingBase:s,appBundleId:c}):b("INVALID_ARGS","record on physical iOS devices requires an active app session; run open <app> first")},stop:async({req:e,activeSession:t,device:r,logPath:a,deps:i,recording:o})=>await eH({req:e,activeSession:t,device:r,logPath:a,deps:i,recording:o})},e9={resolveOutputPath:e3,start:async({req:e,activeSession:t,device:r,logPath:a,deps:i,fpsFlag:o,recordingBase:n})=>{let s=eL(t);return s?await eV({req:e,activeSession:t,device:r,logPath:a,deps:i,fpsFlag:o,recordingBase:n,appBundleId:s}):b("INVALID_ARGS","record on macOS requires an active app session; run open <app> first")},stop:async({req:e,activeSession:t,device:r,logPath:a,deps:i,recording:o})=>await ej({req:e,activeSession:t,device:r,logPath:a,deps:i,recording:o})},te={resolveOutputPath:e3,start:async({req:e,activeSession:t,device:r,logPath:a,deps:i,recordingBase:o,resolvedOut:n})=>await e1({req:e,activeSession:t,device:r,logPath:a,deps:i,recordingBase:o,resolvedOut:n}),stop:async({deps:e,recording:t,stopRequestedAt:r})=>await e2({deps:e,recording:t,stopRequestedAt:r})},tt={resolveOutputPath:e3,start:async({device:e,recordingBase:t})=>await eE({device:e,recordingBase:t}),stop:async({deps:e,device:t,recording:r,stopRequestedAt:a})=>await eF({deps:e,device:t,recording:r,stopRequestedAt:a})},tr={web:e6,android:tt,macos:e9,"ios-device":e7,"ios-simulator":te,unsupported:{resolveOutputPath:e3,start:async()=>b("UNSUPPORTED_OPERATION","record is not supported on this device"),stop:async()=>b("UNSUPPORTED_OPERATION","record is not supported on this device")}};async function ta(e){let{recording:t}=e,r=k();if(!r.stopRecording)return b("UNSUPPORTED_OPERATION","record is not supported by this web provider");if(await r.stopRecording(),!function(e){try{return i.statSync(e).size>0}catch{return!1}}(t.outPath)){var a=t.outPath;try{i.rmSync(a,{force:!0})}catch{}return b("COMMAND_FAILED",`failed to stop recording: ${t.outPath} was not finalized into a WebM video`)}return null}async function ti(e){"ios"!==e.platform||0!==e.gestureEvents.length&&await f(350)}async function to(e){let t,{req:r,sessionName:a,sessionStore:n,activeSession:s,device:c,logPath:d,deps:u}=e;if(s.recording)return b("INVALID_ARGS","recording already in progress");let l=r.flags?.fps,p=r.flags?.quality,m=r.flags?.screenshotMaxSize,h=e8(c),f=h.validateStart?.(r)??null;if(f)return f;if(void 0!==l&&(!Number.isInteger(l)||l<1||l>120))return b("INVALID_ARGS","fps must be an integer between 1 and 120");if(void 0!==p&&void 0===T(p))return b("INVALID_ARGS",`quality must be one of: ${L.join(", ")} (legacy numeric values 5-10 are accepted)`);if(void 0!==m&&(!Number.isInteger(m)||m<1))return b("INVALID_ARGS","max-size must be a positive integer");let w=A("record",c);if(w)return w;let g=h.resolveOutputPath({req:r}),y=P.expandHome(g,r.meta?.cwd),_=(t=T(r.flags?.quality),{outPath:y,clientOutPath:r.meta?.clientArtifactPaths?.outPath,startedAt:Date.now(),maxSize:r.flags?.screenshotMaxSize,exportQuality:t??"medium",showTouches:r.flags?.hideTouches!==!0,gestureEvents:[]});i.mkdirSync(o.dirname(y),{recursive:!0}),i.rmSync(y,{force:!0});let v=await h.start({req:r,activeSession:s,sessionStore:n,device:c,logPath:d,deps:u,fpsFlag:l,recordingBase:_,resolvedOut:y});if("ok"in v)return v;s.recording=v,n.set(a,s);let S=n.ensureSessionDir(a);return U(n,s,r,r.command,{action:"start",showTouches:v.showTouches}),{ok:!0,data:{recording:"started",outPath:v.clientOutPath??g,sessionStateDir:S,showTouches:v.showTouches}}}async function tn(e){var t;let r,a,{req:i,activeSession:n,device:s,logPath:c,deps:d}=e;if(!n.recording)return b("INVALID_ARGS","no active recording");let u=n.recording,l=Date.now(),p=u.invalidatedReason;n.recording=void 0;let m=await function(e){let{recording:t}=e;switch(t.platform){case"android":return tt.stop({...e,recording:t});case"ios":return te.stop({...e,recording:t});case"ios-device-runner":return e7.stop({...e,recording:t});case"macos-runner":return e9.stop({...e,recording:t});case"web":return e6.stop({...e,recording:t})}return t}({req:i,activeSession:n,device:s,logPath:c,deps:d,recording:u,stopRequestedAt:l});if(m)return m;if(p&&"ios"===u.platform&&u.showTouches)u.overlayWarning??=`overlay unavailable: ${p}`;else if(p)return b("COMMAND_FAILED",p);return r="android"===(t=u).platform?t.chunks:void 0,a=[{field:"outPath",path:t.outPath,localPath:t.clientOutPath,fileName:o.basename(t.clientOutPath??t.outPath)}],r&&r.length>1&&a.push(...r.slice(1).map(e=>({field:"chunkPath",path:e.path,localPath:ts(t,e.index),fileName:o.basename(ts(t,e.index)??e.path)}))),t.telemetryPath&&a.push({field:"telemetryPath",path:t.telemetryPath,localPath:function(e){if(e.clientOutPath)return er(e.clientOutPath)}(t),fileName:o.basename(t.telemetryPath)}),{ok:!0,data:{recording:"stopped",outPath:t.outPath,telemetryPath:t.telemetryPath,artifacts:a,showTouches:t.showTouches,warning:t.warning,overlayWarning:t.overlayWarning,chunks:r?.map(e=>({index:e.index,path:ts(t,e.index)??e.path}))}}}function ts(e,t){if("android"===e.platform&&e.clientOutPath)return eh(e.clientOutPath,t)}async function tc(e,t,r,a={}){if(!r.recordOnlySession)return;let i=e8(r.device);await i.cleanupRecordOnlySession?.(r),a.writeLog&&e.writeSessionLog(r),e.delete(t)}async function td(e){let{req:t,sessionName:r,sessionStore:a,logPath:i}=e,o={runCmd:async(e,t,r)=>"xcrun"===e?await _(t,r):await p(e,t,r),startIosSimulatorRecording:e=>F().startIosSimulatorRecording(e),runAppleRunnerCommand:I,waitForRecordingTail:ti,waitForStableFile:J,isPlayableVideo:X,trimRecordingStart:es,resizeRecording:ed,overlayRecordingTouches:ec},n=a.get(r),s=n?.device??await R(t.flags??{});n||await E(s);let c=n??{name:C(t),sessionScope:M(t),device:s,createdAt:Date.now(),recordOnlySession:!0,actions:[]},d=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(d))return b("INVALID_ARGS","record requires start|stop");if("start"===d)return to({req:t,sessionName:r,sessionStore:a,activeSession:c,device:s,logPath:i,deps:o});let u=await tn({req:t,activeSession:c,device:s,logPath:i,deps:o});return u.ok?(U(a,c,t,t.command,{action:"stop",outPath:u.data?.outPath,showTouches:u.data?.showTouches}),await tc(a,r,c,{writeLog:!0})):await tc(a,r,c),u}export{td as handleRecordCommand};
|
package/dist/src/record-trace.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"node:fs";import e from"node:path";import{SessionStore as r,errorResponse as a}from"./9722.js";import{handleRecordCommand as o}from"./record-trace-recording.js";async function i(i){let{req:
|
|
1
|
+
import t from"node:fs";import e from"node:path";import{SessionStore as r,errorResponse as a}from"./9722.js";import{handleRecordCommand as o}from"./record-trace-recording.js";import{recordSessionAction as n}from"./handler-utils.js";async function i(i){let{req:s,sessionName:c,sessionStore:d,logPath:p}=i,m=s.command;if("record"===m)return o({req:s,sessionName:c,sessionStore:d,logPath:p});if("trace"===m){let o=(s.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(o))return a("INVALID_ARGS","trace requires start|stop");let i=d.get(c);if(!i)return a("SESSION_NOT_FOUND","No active session");if("start"===o){if(i.trace)return a("INVALID_ARGS","trace already in progress");let o=s.positionals?.[1]??d.defaultTracePath(i),c=r.expandHome(o);return t.mkdirSync(e.dirname(c),{recursive:!0}),t.appendFileSync(c,""),i.trace={outPath:c,startedAt:Date.now()},n(d,i,s,m,{action:"start",outPath:c}),{ok:!0,data:{trace:"started",outPath:c}}}if(!i.trace)return a("INVALID_ARGS","no active trace");let p=i.trace.outPath;if(s.positionals?.[1]){let a=r.expandHome(s.positionals[1]);t.mkdirSync(e.dirname(a),{recursive:!0}),t.existsSync(p)?t.renameSync(p,a):t.appendFileSync(a,""),p=a}return i.trace=void 0,n(d,i,s,m,{action:"stop",outPath:p}),{ok:!0,data:{trace:"stopped",outPath:p}}}return null}export{i as handleRecordTraceCommands};
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
declare type CloudProviderProfileFields = {
|
|
2
|
+
providerApp?: string;
|
|
3
|
+
providerOsVersion?: string;
|
|
4
|
+
providerProject?: string;
|
|
5
|
+
providerBuild?: string;
|
|
6
|
+
providerSessionName?: string;
|
|
7
|
+
awsProjectArn?: string;
|
|
8
|
+
awsDeviceArn?: string;
|
|
9
|
+
awsAppArn?: string;
|
|
10
|
+
awsRegion?: string;
|
|
11
|
+
awsInteractionMode?: 'INTERACTIVE' | 'NO_VIDEO' | 'VIDEO_ONLY';
|
|
12
|
+
};
|
|
13
|
+
|
|
1
14
|
declare const DAEMON_SERVER_MODES: readonly ['socket', 'http', 'dual'];
|
|
2
15
|
|
|
3
16
|
declare const DAEMON_TRANSPORT_PREFERENCES: readonly ['auto', 'socket', 'http'];
|
|
@@ -16,7 +29,7 @@ declare type LeaseBackend = (typeof LEASE_BACKENDS)[number];
|
|
|
16
29
|
|
|
17
30
|
declare type MetroPrepareKind = 'auto' | 'react-native' | 'expo';
|
|
18
31
|
|
|
19
|
-
declare const PLATFORM_SELECTORS: readonly ["
|
|
32
|
+
declare const PLATFORM_SELECTORS: readonly ["apple", "android", "linux", "web", "ios", "macos"];
|
|
20
33
|
|
|
21
34
|
declare type PlatformSelector = (typeof PLATFORM_SELECTORS)[number];
|
|
22
35
|
|
|
@@ -36,7 +49,7 @@ declare type RemoteConfigMetroOptions = {
|
|
|
36
49
|
metroNoInstallDeps?: boolean;
|
|
37
50
|
};
|
|
38
51
|
|
|
39
|
-
export declare type RemoteConfigProfile = RemoteConfigMetroOptions & RemoteConnectionProfileFields & {
|
|
52
|
+
export declare type RemoteConfigProfile = RemoteConfigMetroOptions & CloudProviderProfileFields & RemoteConnectionProfileFields & {
|
|
40
53
|
platform?: PlatformSelector;
|
|
41
54
|
target?: DeviceTarget;
|
|
42
55
|
device?: string;
|
|
@@ -47,12 +60,6 @@ export declare type RemoteConfigProfile = RemoteConfigMetroOptions & RemoteConne
|
|
|
47
60
|
session?: string;
|
|
48
61
|
};
|
|
49
62
|
|
|
50
|
-
export declare type RemoteConfigProfileOptions = {
|
|
51
|
-
configPath: string;
|
|
52
|
-
cwd: string;
|
|
53
|
-
env?: Record<string, string | undefined>;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
63
|
declare type RemoteConnectionProfileFields = {
|
|
57
64
|
stateDir?: string;
|
|
58
65
|
daemonBaseUrl?: string;
|
|
@@ -69,15 +76,6 @@ declare type RemoteConnectionProfileFields = {
|
|
|
69
76
|
clientId?: string;
|
|
70
77
|
};
|
|
71
78
|
|
|
72
|
-
export declare type ResolvedRemoteConfigProfile = {
|
|
73
|
-
resolvedPath: string;
|
|
74
|
-
profile: RemoteConfigProfile;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export declare function resolveRemoteConfigPath(options: RemoteConfigProfileOptions): string;
|
|
78
|
-
|
|
79
|
-
export declare function resolveRemoteConfigProfile(options: RemoteConfigProfileOptions): ResolvedRemoteConfigProfile;
|
|
80
|
-
|
|
81
79
|
declare const SESSION_ISOLATION_MODES: readonly ['none', 'tenant'];
|
|
82
80
|
|
|
83
81
|
declare type SessionIsolationMode = (typeof SESSION_ISOLATION_MODES)[number];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
1
|
+
export{};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{toAppErrorCode as e,AppError as t}from"./485.js";let n=["tap","doubleTap","longPress","drag"],r=20;function i(e,n,r){return new t("INVALID_ARGS",r,{stepIndex:e,kind:"string"==typeof n?n:void 0})}function s(e,t,n,r,s,o){if(!Number.isFinite(e)||e<t||e>n)throw i(r,s,`sequence step ${r} (${s}) ${o} must be between ${t} and ${n}`)}function o(e,o){if(!Array.isArray(e)||0===e.length)throw new t("INVALID_ARGS","sequence requires at least one step",{stepCount:Array.isArray(e)?e.length:0});if(e.length>r)throw new t("INVALID_ARGS",`sequence accepts at most ${r} steps, received ${e.length}`,{stepCount:e.length,maxSteps:r});return e.forEach((e,t)=>(function(e,t){var r;if(!("string"==typeof(r=e.kind)&&n.includes(r)))throw i(t,e.kind,`sequence step ${t} has unsupported kind "${String(e.kind)}"; allowed: ${n.join(", ")}`);if(!Number.isFinite(e.x)||!Number.isFinite(e.y))throw i(t,e.kind,`sequence step ${t} (${e.kind}) requires finite x and y`);if("drag"===e.kind&&(!Number.isFinite(e.x2)||!Number.isFinite(e.y2)))throw i(t,e.kind,`sequence step ${t} (drag) requires finite x2 and y2`);void 0!==e.durationMs&&s(e.durationMs,0,1e4,t,e.kind,"durationMs"),void 0!==e.pauseMs&&s(e.pauseMs,0,1e4,t,e.kind,"pauseMs")})(e,t)),{command:"sequence",steps:e,appBundleId:o}}function d(n){var r,i,s,o;let d=Array.isArray(r=n.sequenceResults)?r.map(e=>{let t=e??{};return{ok:!0===t.ok,kind:"string"==typeof t.kind?t.kind:"unknown",errorCode:"string"==typeof t.errorCode?t.errorCode:void 0,errorMessage:"string"==typeof t.errorMessage?t.errorMessage:void 0,gestureStartUptimeMs:"number"==typeof t.gestureStartUptimeMs?t.gestureStartUptimeMs:void 0,gestureEndUptimeMs:"number"==typeof t.gestureEndUptimeMs?t.gestureEndUptimeMs:void 0}}):[],u="number"==typeof n.completedSteps&&Number.isFinite(n.completedSteps)?n.completedSteps:d.filter(e=>e.ok).length,p="number"==typeof n.failedStepIndex&&Number.isFinite(n.failedStepIndex)?n.failedStepIndex:d.findIndex(e=>!e.ok)>=0?d.findIndex(e=>!e.ok):void 0;if(void 0!==p){let n,r,a;throw i=d,s=u,n=i[o=p],r=n?.kind??"step",a=n?.errorMessage??`sequence step ${o} (${r}) failed`,new t(e(n?.errorCode),a,{failedStepIndex:o,failedStepKind:r,completedSteps:s,sequenceResults:i,hint:"Run snapshot -i to inspect the current UI, then continue from the observed state."})}return{results:d,completedSteps:u,failedStepIndex:p}}export{r as MAX_RUNNER_SEQUENCE_STEPS,o as buildRunnerSequenceCommand,d as parseRunnerSequenceResult};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setSessionSnapshot as e,createDaemonRuntimeSessionStore as t,captureSnapshot as r,dispatchCommand as n,runIosRunnerCommand as s,isCommandSupportedOnDevice as o,errorResponse as i,stopIosRunnerSession as a,resolveRunnerAppBundleId as l,getActiveAndroidSnapshotFreshness as c,resolveTargetDevice as u,context_contextFromFlags as d}from"./9722.js";import{closeIosApp as f}from"./apps.js";import{emitDiagnostic as p}from"./432.js";import{ensureDeviceReady as m}from"./device-ready.js";import{asAppError as g,normalizeError as h,AppError as y}from"./733.js";import{evaluateIsPredicate as w,isSupportedPredicate as S,localCommandPolicy as _,createAgentDevice as v}from"./2577.js";import{getAndroidAppState as I}from"./3340.js";import{splitIsSelectorArgs as x,normalizeType as k,prefersValueForReadableText as P,findNodeByLabel as N,extractNodeReadText as A}from"./9836.js";import{resolveRectCenter as R,isSparseSnapshotQualityVerdict as b,parseWaitPositionals as q}from"./2948.js";import{buildSnapshotPresentationKey as D,snapshotPresentationOptionsFromFlags as E}from"./4291.js";import{isApplePlatform as C}from"./5910.js";import{parseFindArgs as M,tryParseSelectorChain as O}from"./587.js";async function $(e,t,r){let n=e.get(t),s=n?.device??await u(r??{});return n||await m(s),{session:n,device:s}}async function F(e,t,r){let n=!e&&"ios"===t.platform;try{return await r()}finally{n&&(await a(t.id),await K(t))}}async function K(e){let t=l();await f(e,t).catch(r=>{p({level:"debug",phase:"ios_sessionless_runner_host_close_failed",data:{deviceId:e.id,bundleId:t,error:r instanceof Error?r.message:String(r)}})})}function L(e,t,r,n){t&&e.recordAction(t,{command:r.command,positionals:r.positionals??[],flags:r.flags??{},result:n})}function U(e){let{session:t,sessionName:r,device:n,snapshot:s,appBundleId:o}=e;return t?{...t,snapshot:s,lastComparisonSafeSnapshot:s?.comparisonSafe===!0?s:t.lastComparisonSafeSnapshot}:{name:r,device:n,createdAt:Date.now(),appBundleId:o,snapshot:s,...s?.comparisonSafe===!0?{lastComparisonSafeSnapshot:s}:{},actions:[]}}function T(e,t={}){return{artifacts:function(e,t={}){let r=!0===t.plural?"do":"does";return{resolveInput:async()=>{throw new y("UNSUPPORTED_OPERATION",`${e} ${r} not resolve input artifacts`)},reserveOutput:async()=>{throw new y("UNSUPPORTED_OPERATION",`${e} ${r} not reserve output artifacts`)},createTempFile:async()=>{throw new y("UNSUPPORTED_OPERATION",`${e} ${r} not create temporary files`)}}}(e,t),policy:_()}}let G=[["snapshotDepth","--depth"],["snapshotScope","--scope"],["snapshotRaw","--raw"]];function V(e,t){let r=function(e){if(!e)return[];let t=[];for(let[r,n]of G)void 0!==e[r]&&t.push(n);return t}(t);return 0===r.length?null:i("INVALID_ARGS",`${e} @ref does not support ${r.join(", ")}.`)}async function B(e,t){let r=await H(e);if(r)throw new y("COMMAND_FAILED",`press ${t} left ${e.appBundleId} and foregrounded ${r.foregroundPackage}. The tap likely escaped the app.`,r)}async function H(e){var t;if("android"!==e.device.platform||!e.appBundleId)return null;let r=await I(e.device),n=r.package?.trim();return n&&n!==e.appBundleId&&("com.android.settings"===(t=n)||"com.android.systemui"===t||"com.google.android.permissioncontroller"===t||t.includes("launcher"))?{expectedPackage:e.appBundleId,foregroundPackage:n,activity:r.activity,hint:"com.google.android.permissioncontroller"===n?"Dismiss or allow the permission prompt, then retry the smoke assertion.":"Use screenshot as visual truth, then take a fresh snapshot -i before retrying."}:null}function X(e){return"COMMAND_FAILED"===e.code&&"string"==typeof e.details?.expectedPackage&&"string"==typeof e.details?.foregroundPackage}function j(e,t){let r=Array.isArray(e.selectorChain)?e.selectorChain:void 0,n=J(e),s=n?.kind==="ref"?Y(n.ref):void 0,o=n?.kind==="selector"?n.selector:void 0,i={...s?{ref:s}:{},...o?{selector:o}:{},...r?{selectorChain:r}:{}};if("attrs"===t)return i;let a="string"==typeof e.text?e.text:"";return{...i,text:a,refLabel:function(e){let t=e.trim();if(!(!t||t.length>80||/[\r\n]/.test(t)))return t}(a)}}function z(e){let t=J(e);return{...t?.kind==="ref"?{ref:Y(t.ref)}:{},...t?.kind==="selector"?{selector:t.selector}:{},..."string"==typeof e.text?{text:e.text}:{},...e.node&&"object"==typeof e.node?{node:e.node}:{}}}function Q(e){let{selectorChain:t,...r}=e;return r}function W(e,t,r,n){let s=e.get(t);s&&e.recordAction(s,{command:r.command,positionals:r.positionals??[],flags:r.flags??{},result:n})}function J(e){let t=e.target;return t&&"object"==typeof t?"ref"===t.kind&&"string"==typeof t.ref?{kind:"ref",ref:t.ref}:"selector"===t.kind&&"string"==typeof t.selector?{kind:"selector",selector:t.selector}:void 0:void 0}function Y(e){return e.startsWith("@")?e.slice(1):e}let Z=["application","window","tabbar","scrollbar","image"],ee=new Set(["tab bar"]);async function et(e,t){var r;if(t.ok||(r=t.error.message,!/^wait timed out for (?:selector|text): /i.test(r)))return t;let n=await er(e).catch(()=>null);return n?i(t.error.code,`${t.error.message}. Current surface: ${n.summary}.`,{...t.error.details??{},currentSurface:n.details}):t}async function er(e){let t=[...(await r({device:e.device,session:e.session,flags:{...e.req.flags,snapshotInteractiveOnly:!0},logPath:e.logPath??""})).snapshot.nodes].sort(es),n=en(t,6,{includeIdentifiers:!0});if(0===n.length)return null;let s=en(t.filter(e=>!ea(e)),4,{includeIdentifiers:!1}),o=en(t.filter(el),4,{includeIdentifiers:!0});return{summary:(s.length>0?s:n.slice(0,4)).join(", "),details:{labels:n,...o.length>0?{buttons:o}:{}}}}function en(e,t,r){let n=new Set,s=[];for(let o of e){let e=ei(o,r);if(!(!e||n.has(e))&&(n.add(e),s.push(e),s.length>=t))break}return s}function es(e,t){var r,n;return eo(e)-eo(t)||(r=e,n=t,r.rect&&n.rect?r.rect.y-n.rect.y||r.rect.x-n.rect.x:r.rect?-1:n.rect?1:(r.depth??0)-(n.depth??0)||r.index-n.index)}function eo(e){let t=!!ei(e,{includeIdentifiers:!1});return 2*!!ea(e)+ +!t}function ei(e,t){let r=(t.includeIdentifiers?[e.label,e.value,e.identifier]:[e.label,e.value]).map(e=>"string"==typeof e?e.trim():"").find(e=>e.length>0);return r?r.replace(/\s+/g," ").slice(0,80):""}function ea(e){let t=k(`${e.type??""} ${e.role??""} ${e.subrole??""}`),r=`${e.label??""} ${e.value??""}`.trim().toLowerCase();return Z.some(e=>t.includes(e))||ee.has(r)||r.endsWith(".fill")}function el(e){return k(`${e.type??""} ${e.role??""} ${e.subrole??""}`).includes("button")}function ec(e){var t;let{session:r,selectorExpression:n}=e;if(!r||"ios"!==r.device.platform||r.postGestureStabilization)return null;let s=O(n);if(!s||1!==s.selectors.length)return null;let o=s.selectors[0];if(!o||1!==o.terms.length)return null;let i=o.terms[0];return i&&"string"==typeof i.value&&("id"===(t=i.key)||"label"===t||"text"===t||"value"===t)?{key:i.key,value:i.value,raw:o.raw}:null}function eu(e,t={}){let r=g(e);if("ELEMENT_NOT_FOUND"===r.code)return!0===t.allowElementNotFound;if("COMMAND_FAILED"!==r.code)return!1;let n=r.message.toLowerCase();return n.includes("fetch failed")||n.includes("timed out")||n.includes("timeout")||n.includes("runner did not accept connection")||n.includes("invalid runner response")}async function ed(e){let{device:t,node:r,flags:s,appBundleId:o,traceOutPath:i,surface:a,contextFromFlags:l}=e,c=A(r),u=R(r.rect);if(!u||"ios"===t.platform&&c&&!P(r.type??""))return c;try{let e=await n(t,"read",[String(u.x),String(u.y)],void 0,{...l(s,o,i),surface:a}),d=e&&"object"==typeof e?e:void 0,f="string"==typeof d?.text?d.text:"";if(f.trim())return f;return p({level:"warn",phase:"interaction_read_fallback",data:{reason:"empty_backend_text",nodeRef:r.ref,surface:a,platform:t.platform}}),c}catch(e){return p({level:"warn",phase:"interaction_read_fallback",data:{reason:"backend_read_failed",nodeRef:r.ref,surface:a,platform:t.platform,error:e instanceof Error?e.message:String(e)}}),c}}function ef(t){let r,n,{session:s,sessionStore:o,sessionName:i}=t,a=0;return{capture:async l=>{var u,d;let f=Date.now(),p=(u=l,d=t.req.flags?.out,JSON.stringify({presentationKey:D(E(ew(u))),includeRects:!0===u.includeRects,outPath:u.outPath??d??null})),m=function(e){let{timestamp:t,lastSnapshotAt:r,lastSnapshotResult:n,lastSnapshotCacheKey:s,session:o,request:i,cacheKey:a}=e;if(!(i.cache?.forceFresh===!0||!n||s!==a||t-r>=750||c(o))&&!ey(o,i))return n}({timestamp:f,lastSnapshotAt:a,lastSnapshotResult:r,lastSnapshotCacheKey:n,session:s,request:l,cacheKey:p});if(m)return m;let g=function(e){var t,r;let{session:n,timestamp:s,request:o}=e,i=n?.snapshot;if(i&&(t=n,r=o,!(r.cache?.forceFresh===!0||r.cache?.useSessionSnapshot!==!0||c(t)||ey(t,r)))&&s-i.createdAt<750&&i.presentationKey===function(e){return D(E(ew(e)))}(o))return i}({session:s,timestamp:f,request:l});if(g)return a=g.createdAt,r={snapshot:g},n=p,r;let h=await ep({params:t,request:l}),y={snapshot:h};return function(t){let{session:r,sessionStore:n,sessionName:s,snapshot:o}=t;!r||b(o.snapshotQuality)||(e(r,o),n.set(s,r))}({session:s,sessionStore:o,sessionName:i,snapshot:h}),a=f,r=y,n=p,y}}}async function ep(e){var t;let{params:r,request:n}=e,s=await eh(r,n,n.snapshotScope);return n.recovery?.legacyIosSparse&&!(t=s).snapshotQuality&&"xctest"===t.backend&&1===t.nodes.length&&t.nodes[0]?.type==="Application"?await em({runtimeParams:r,request:n,policy:n.recovery.legacyIosSparse}):n.recovery?.sparseVerdictQueryScope?.shouldScope&&b(s.snapshotQuality)?await eg({runtimeParams:r,request:n,policy:n.recovery.sparseVerdictQueryScope,snapshot:s}):s}async function em(e){let{runtimeParams:t,request:r,policy:n}=e;try{return await eh(t,r,n.scope,!1)}catch(e){if(!n.shouldScope)throw e;return await eh(t,r,n.query,!1)}}async function eg(e){let{runtimeParams:t,request:r,policy:n,snapshot:s}=e;try{return await eh(t,r,n.query,!1)}catch{return s}}async function eh(e,t,n,s=t.flags?.snapshotInteractiveOnly){return(await r({device:e.device,session:e.session,flags:{...t.flags,snapshotInteractiveOnly:s},outPath:t.outPath??e.req.flags?.out,logPath:e.logPath??"",snapshotScope:n,includeRects:t.includeRects})).snapshot}function ey(e,t){return t.cache?.bypassForPostGestureStabilization===!0&&!!e?.postGestureStabilization}function ew(e){return void 0===e.snapshotScope?e.flags:{...e.flags,snapshotScope:e.snapshotScope}}function eS(r){return v({backend:function(e){let{req:t,session:r,device:n,logPath:s,sessionName:o,sessionStore:i}=e,a=ef({device:n,session:r,sessionStore:i,sessionName:o,req:t,logPath:s});return{platform:n.platform,captureSnapshot:async(e,r)=>{var s;let o,i={...t.flags,...(s=r,o={},s?.interactiveOnly!==void 0&&(o.snapshotInteractiveOnly=s.interactiveOnly),s?.scope!==void 0&&(o.snapshotScope=s.scope),s?.depth!==void 0&&(o.snapshotDepth=s.depth),s?.raw!==void 0&&(o.snapshotRaw=s.raw),o)},l=r?.includeRects===!0,c=r?.scope??t.flags?.snapshotScope,u="wait"===t.command||"find"===t.command||l&&"web"===n.platform;return await a.capture({flags:i,snapshotScope:c,includeRects:l,cache:{forceFresh:u,useSessionSnapshot:!0,bypassForPostGestureStabilization:!0}})},readText:async(o,i)=>({text:await ed({device:n,node:i,flags:t.flags,appBundleId:r?.appBundleId,traceOutPath:r?.trace?.outPath,surface:r?.surface,contextFromFlags:e.contextFromFlags??((e,t,r)=>d(s??"",e,t,r))})}),findText:async(t,r)=>({found:await ev(e,r)})}}(r),...T("selector commands",{plural:!0}),sessions:t({sessionName:r.sessionName,getSession:()=>r.session,recordOptions:{includeSnapshot:!0},setRecord:t=>{r.session&&t.snapshot&&(e(r.session,t.snapshot),r.sessionStore.set(r.sessionName,r.session))}})})}async function e_(e,t){let r=e.sessionStore.get(e.sessionName);if(!r&&t.requireSession)return{ok:!1,response:i("SESSION_NOT_FOUND","No active session. Run open first.")};let n=r?.device??await u(e.req.flags??{});return(r||await m(n),o(t.capability,n))?{ok:!0,runtime:eS({...e,session:r,device:n})}:{ok:!1,response:i("UNSUPPORTED_OPERATION",`${t.capability} is not supported on this device`)}}async function ev(e,t){let r=await eI(e,t);if(null!==r)return r;let n=await ex(e,t);return null!==n?n:await ek(e,t)}async function eI(e,t){return"macos"===e.device.platform&&e.session?.surface&&"app"!==e.session.surface?await ek(e,t):null}async function ex(e,t){var r,n,o;let i,a,l=C((r=e).device.platform)&&r.session?.appBundleId?{device:r.device,appBundleId:r.session.appBundleId,traceLogPath:r.session.trace?.outPath}:null;if(!l)return null;let c=await s(l.device,{command:"findText",text:t,appBundleId:l.appBundleId},(n=e,o=l,i=n.req.flags??{},a=n.req.meta??{},{verbose:i.verbose,logPath:n.logPath,traceLogPath:o.traceLogPath,requestId:a.requestId,iosXctestrunFile:i.iosXctestrunFile,iosXctestDerivedDataPath:i.iosXctestDerivedDataPath,iosXctestEnvDir:i.iosXctestEnvDir}));return c?.found===!0}async function ek(e,t){return!!N((await eP(e)).nodes,t)}async function eP(e){let t=ef({device:e.device,session:e.session,sessionStore:e.sessionStore,sessionName:e.sessionName,req:e.req,logPath:e.logPath}),{snapshot:r}=await t.capture({flags:{...e.req.flags,snapshotInteractiveOnly:!1},cache:{forceFresh:!0,bypassForPostGestureStabilization:!0}});return r}async function eN(e){var t;let{req:r}=e;if("find"!==r.command)return null;let n=r.positionals??[];if(0===n.length)return i("INVALID_ARGS","find requires a locator or text");let s=M(n);if(!s.query)return i("INVALID_ARGS","find requires a value");if(r.flags?.findFirst&&r.flags?.findLast)return i("INVALID_ARGS","find accepts only one of --first or --last");let o=s.action;if("exists"!==(t=o)&&"wait"!==t&&"get_text"!==t&&"get_attrs"!==t)return null;let a=await e_(e,{requireSession:!1,capability:"find"});return a.ok?await eF(async()=>{let t=await a.runtime.selectors.find({session:e.sessionName,requestId:r.meta?.requestId,locator:s.locator,query:s.query,action:o,timeoutMs:s.timeoutMs});return W(e.sessionStore,e.sessionName,r,function(e,t){if("exists"===t)return{found:!0};if("wait"===t)return{found:!0,waitedMs:e.waitedMs};let r="string"==typeof e.ref?e.ref:void 0;return"get_attrs"===t?{ref:r,action:"get attrs"}:{ref:r,action:"get text",text:"string"==typeof e.text?e.text:""}}(t,o)),"found"===t.kind?{found:!0,..."number"==typeof t.waitedMs?{waitedMs:t.waitedMs}:{}}:{..."string"==typeof t.ref?{ref:t.ref}:{},..."string"==typeof t.text?{text:t.text}:{},...t.node&&"object"==typeof t.node?{node:t.node}:{}}}):a.response}async function eA(e){let{req:t}=e;if("get"!==t.command)return null;let r=t.positionals?.[0];if("text"!==r&&"attrs"!==r)return i("INVALID_ARGS","get only supports text or attrs");let n=function(e){let t=e.positionals?.[1]??"";if(t.startsWith("@"))return{ok:!0,target:{kind:"ref",ref:t,fallbackLabel:e.positionals.length>2?e.positionals.slice(2).join(" ").trim():""}};let r=e.positionals?.slice(1).join(" ").trim()??"";return r?{ok:!0,target:{kind:"selector",selector:r}}:{ok:!1,response:i("INVALID_ARGS","get requires @ref or selector expression")}}(t);if(!n.ok)return n.response;if("ref"===n.target.kind){let e=V("get",t.flags);if(e)return e}if("selector"===n.target.kind){let t=await eq(e,r,n.target.selector);if(t)return t}let s=await e_(e,{requireSession:!0,capability:"get"});return s.ok?await eF(async()=>{let o=await s.runtime.selectors.get({session:e.sessionName,requestId:t.meta?.requestId,property:r,target:n.target});return W(e.sessionStore,e.sessionName,t,j(o,r)),z(o)}):s.response}async function eR(e){let{req:t}=e;if("is"!==t.command)return null;let r=(t.positionals?.[0]??"").toLowerCase();if(!S(r))return i("INVALID_ARGS","is requires predicate: visible|hidden|exists|editable|selected|text");let{split:n}=x(t.positionals??[]);if(!n)return i("INVALID_ARGS","is requires a selector expression");let s=n.rest.join(" ").trim();if("text"===r&&!s)return i("INVALID_ARGS","is text requires expected text value");if("text"!==r&&n.rest.length>0)return i("INVALID_ARGS",`is ${r} does not accept trailing values`);let o=await eD(e,r,n.selectorExpression,s);if(o)return o;let a=await e_(e,{requireSession:!0,capability:"is"});if(!a.ok)return a.response;let l=await eF(async()=>{let o=await a.runtime.selectors.is({session:e.sessionName,requestId:t.meta?.requestId,predicate:r,selector:n.selectorExpression,expectedText:s});return W(e.sessionStore,e.sessionName,t,o),Q(o)});return await eK(e,l,`is ${r}`)}async function eb(e){let{req:t,sessionName:r,sessionStore:n}=e,s=q(t.positionals??[]);if(!s)return i("INVALID_ARGS","wait requires a duration or text");let{session:a,device:l}=await $(n,r,t.flags);if("sleep"!==s.kind&&!o("wait",l))return i("UNSUPPORTED_OPERATION","wait is not supported on this device");if("selector"===s.kind){let t=await eE({...e,session:a,device:l,selectorExpression:s.selectorExpression,timeoutMs:s.timeoutMs});if(t)return t}let c=async()=>{let o=eS({...e,session:a,device:l}),i=await eF(async()=>{let e=await o.selectors.wait({session:r,requestId:t.meta?.requestId,target:function(e,t){if("sleep"===e.kind)return{kind:"sleep",durationMs:e.durationMs};if("selector"===e.kind)return{kind:"selector",selector:e.selectorExpression,timeoutMs:e.timeoutMs};if("ref"===e.kind){if(!t?.snapshot)throw new y("INVALID_ARGS","Ref wait requires an existing snapshot in session.");return{kind:"ref",ref:e.rawRef,timeoutMs:e.timeoutMs}}if(!e.text)throw new y("INVALID_ARGS","wait requires text");return{kind:"text",text:e.text,timeoutMs:e.timeoutMs}}(s,a)});return W(n,r,t,e),{waitedMs:e.waitedMs,..."string"==typeof e.text?{text:e.text}:{},..."string"==typeof e.selector?{selector:e.selector}:{}}}),c=await et({req:t,logPath:e.logPath,session:a,device:l},i);return await eK(e,c,"wait")};return"sleep"===s.kind?await c():await F(a,l,c)}async function eq(e,t,r){let n=e.sessionStore.get(e.sessionName),s=ec({session:n,selectorExpression:r});if(!n||!s||"text"===t&&"id"!==s.key)return null;let o=await eO(e,n,s);if(e$(o))return o.response;if(!o)return null;let i=function(e,t,r){if(!r.found||!r.node)return null;let n={target:{kind:"selector",selector:t},node:r.node,selectorChain:[t]};return"attrs"===e?{kind:"attrs",...n}:"string"!=typeof r.text?null:{kind:"text",...n,text:r.text}}(t,s.raw,o);return i?(W(e.sessionStore,e.sessionName,e.req,j(i,t)),{ok:!0,data:z(i)}):null}async function eD(e,t,r,n){var s,o,i,a,l;let c;if("hidden"===t)return null;let u=await eC(e,r);if(e$(u))return u.response;if(!u?.result.found||!u.result.node)return null;let d="exists"===t?{predicate:t,pass:!0,selector:u.selector.raw,matches:1,selectorChain:[u.selector.raw]}:(s=t,o=n,i=u.selector.raw,a=u.session,c=w({predicate:s,node:l=u.result.node,nodes:[l],expectedText:o,platform:a.device.platform}),{predicate:s,pass:c.pass,selector:i,..."text"===s?{text:c.actualText}:{},selectorChain:[i]});return d?(W(e.sessionStore,e.sessionName,e.req,d),{ok:!0,data:Q(d)}):null}async function eE(e){let t=ec({session:e.session,selectorExpression:e.selectorExpression});if(!e.session||!t)return null;let r=Date.now(),n=await eO(e,e.session,t);if(e$(n))return n.response;if(!n?.found)return null;let s={kind:"selector",selector:t.raw,waitedMs:Date.now()-r,selectorChain:[t.raw]};return W(e.sessionStore,e.sessionName,e.req,s),await et({req:e.req,logPath:e.logPath,session:e.session,device:e.device},{ok:!0,data:s})}async function eC(e,t){let r=e.sessionStore.get(e.sessionName),n=ec({session:r,selectorExpression:t});if(!r||!n)return null;let s=await eO(e,r,n);return e$(s)?s:s?{session:r,selector:n,result:s}:null}async function eM(e,t,r){let n=await s(t.device,{command:"querySelector",selectorKey:r.key,selectorValue:r.value,appBundleId:t.appBundleId},{verbose:!!e.req.flags?.verbose,logPath:e.logPath,traceLogPath:t.trace?.outPath,requestId:e.req.meta?.requestId,iosXctestrunFile:e.req.flags?.iosXctestrunFile,iosXctestDerivedDataPath:e.req.flags?.iosXctestDerivedDataPath,iosXctestEnvDir:e.req.flags?.iosXctestEnvDir}),o=!0===n.found,i=function(e){let t=e.nodes;if(!Array.isArray(t))return;let r=t[0];if(r&&"object"==typeof r)return r}(n);return{found:o,..."string"==typeof n.text?{text:n.text}:{},...i?{node:i}:{}}}async function eO(e,t,r){try{return await eM(e,t,r)}catch(e){if(eu(e,{allowElementNotFound:!0}))return null;return{kind:"error",response:{ok:!1,error:h(e)}}}}function e$(e){return null!==e&&"kind"in e&&"error"===e.kind}async function eF(e){try{return{ok:!0,data:await e()}}catch(t){let e=g(t);return i(e.code,e.message,e.details)}}async function eK(e,t,r){var n;let s;if(t.ok)return t;let o=e.sessionStore.get(e.sessionName);if(!o)return t;try{s=await H(o)}catch{return t}return s?i(t.error.code,`${r} failed because ${"com.google.android.permissioncontroller"===(n=s).foregroundPackage?`Android permission dialog is blocking ${n.expectedPackage}`:`${n.foregroundPackage} is foreground instead of ${n.expectedPackage}`}.`,{...t.error.details??{},...s,blockedBy:"android_foreground_surface",originalMessage:t.error.message}):t}export{B as assertAndroidPressStayedInApp,U as buildSnapshotSession,T as createDaemonRuntimePolicy,ef as createSelectorCaptureRuntime,eN as dispatchFindReadOnlyViaRuntime,eA as dispatchGetViaRuntime,eR as dispatchIsViaRuntime,eb as dispatchWaitViaRuntime,X as isAndroidEscapeError,eu as isDirectIosSelectorFallbackError,ec as readSimpleIosSelectorTarget,ed as readTextForNode,L as recordIfSession,V as refSnapshotFlagGuardResponse,$ as resolveSessionDevice,F as withSessionlessRunnerCleanup};
|
|
1
|
+
import{snapshotPresentationOptionsFromFlags as e,isMacOs as t,isApplePlatform as r,publicPlatformString as n,isIosFamily as s,buildSnapshotPresentationKey as o}from"./6849.js";import{setSessionSnapshot as i,createDaemonRuntimeSessionStore as a,context_contextFromFlags as l,captureSnapshot as c,dispatchCommand as u,requireCommandSupported as d,stopIosRunnerSession as f,errorResponse as p,resolveRunnerAppBundleId as m,getActiveAndroidSnapshotFreshness as g,noActiveSessionError as h,resolveTargetDevice as y,runAppleRunnerCommand as w}from"./9722.js";import{closeIosApp as S}from"./apps.js";import{emitDiagnostic as _}from"./8875.js";import{ensureDeviceReady as v,buildAppleRunnerRequestOptions as x}from"./8414.js";import{asAppError as k,normalizeError as I,AppError as A}from"./485.js";import{evaluateIsPredicate as P,isSupportedPredicate as N,localCommandPolicy as R,createAgentDevice as b}from"./7871.js";import{getAndroidAppState as q}from"./3340.js";import{splitIsSelectorArgs as C,normalizeType as M,prefersValueForReadableText as E,findNodeByLabel as D,extractNodeReadText as $}from"./893.js";import{resolveRectCenter as L,isSparseSnapshotQualityVerdict as O,parseWaitPositionals as K}from"./2948.js";import{parseFindArgs as F,tryParseSelectorChain as G}from"./587.js";async function V(e,t,r){let n=e.get(t),s=n?.device??await y(r??{});return n||await v(s),{session:n,device:s}}async function U(e,t,r){let n=!e&&s(t);try{return await r()}finally{n&&(await f(t.id),await T(t))}}async function T(e){let t=m();await S(e,t).catch(r=>{_({level:"debug",phase:"ios_sessionless_runner_host_close_failed",data:{deviceId:e.id,bundleId:t,error:r instanceof Error?r.message:String(r)}})})}function B(e,t,r,n){t&&e.recordAction(t,{command:r.command,positionals:r.positionals??[],flags:r.flags??{},result:n})}function H(e){let{session:t,sessionName:r,device:n,snapshot:s,appBundleId:o}=e;return t?{...t,snapshot:s,lastComparisonSafeSnapshot:s?.comparisonSafe===!0?s:t.lastComparisonSafeSnapshot}:{name:r,device:n,createdAt:Date.now(),appBundleId:o,snapshot:s,...s?.comparisonSafe===!0?{lastComparisonSafeSnapshot:s}:{},actions:[]}}function j(e,t={}){return{artifacts:function(e,t={}){let r=!0===t.plural?"do":"does";return{resolveInput:async()=>{throw new A("UNSUPPORTED_OPERATION",`${e} ${r} not resolve input artifacts`)},reserveOutput:async()=>{throw new A("UNSUPPORTED_OPERATION",`${e} ${r} not reserve output artifacts`)},createTempFile:async()=>{throw new A("UNSUPPORTED_OPERATION",`${e} ${r} not create temporary files`)}}}(e,t),policy:R()}}let z=[["snapshotDepth","--depth"],["snapshotScope","--scope"],["snapshotRaw","--raw"]];function Q(e,t){let r=function(e){if(!e)return[];let t=[];for(let[r,n]of z)void 0!==e[r]&&t.push(n);return t}(t);return 0===r.length?null:p("INVALID_ARGS",`${e} @ref does not support ${r.join(", ")}.`)}async function W(e,t){let r=await J(e);if(r)throw new A("COMMAND_FAILED",`press ${t} left ${e.appBundleId} and foregrounded ${r.foregroundPackage}. The tap likely escaped the app.`,r)}async function J(e){var t;if("android"!==e.device.platform||!e.appBundleId)return null;let r=await q(e.device),n=r.package?.trim();return n&&n!==e.appBundleId&&("com.android.settings"===(t=n)||"com.android.systemui"===t||"com.google.android.permissioncontroller"===t||t.includes("launcher"))?{expectedPackage:e.appBundleId,foregroundPackage:n,activity:r.activity,hint:"com.google.android.permissioncontroller"===n?"Dismiss or allow the permission prompt, then retry the smoke assertion.":"Use screenshot as visual truth, then take a fresh snapshot -i before retrying."}:null}function X(e){return"COMMAND_FAILED"===e.code&&"string"==typeof e.details?.expectedPackage&&"string"==typeof e.details?.foregroundPackage}function Y(e,t){let r=Array.isArray(e.selectorChain)?e.selectorChain:void 0,n=er(e),s=n?.kind==="ref"?en(n.ref):void 0,o=n?.kind==="selector"?n.selector:void 0,i={...s?{ref:s}:{},...o?{selector:o}:{},...r?{selectorChain:r}:{}};if("attrs"===t)return i;let a="string"==typeof e.text?e.text:"";return{...i,text:a,refLabel:function(e){let t=e.trim();if(!(!t||t.length>80||/[\r\n]/.test(t)))return t}(a)}}function Z(e){let t=er(e);return{...t?.kind==="ref"?{ref:en(t.ref)}:{},...t?.kind==="selector"?{selector:t.selector}:{},..."string"==typeof e.text?{text:e.text}:{},...e.node&&"object"==typeof e.node?{node:e.node}:{}}}function ee(e){let{selectorChain:t,...r}=e;return r}function et(e,t,r,n){let s=e.get(t);s&&e.recordAction(s,{command:r.command,positionals:r.positionals??[],flags:r.flags??{},result:n})}function er(e){let t=e.target;return t&&"object"==typeof t?"ref"===t.kind&&"string"==typeof t.ref?{kind:"ref",ref:t.ref}:"selector"===t.kind&&"string"==typeof t.selector?{kind:"selector",selector:t.selector}:void 0:void 0}function en(e){return e.startsWith("@")?e.slice(1):e}let es=["application","window","tabbar","scrollbar","image"],eo=new Set(["tab bar"]);async function ei(e,t){var r;if(t.ok||(r=t.error.message,!/^wait timed out for (?:selector|text): /i.test(r)))return t;let n=await ea(e).catch(()=>null);return n?p(t.error.code,`${t.error.message}. Current surface: ${n.summary}.`,{...t.error.details??{},currentSurface:n.details}):t}async function ea(e){let t=[...(await c({device:e.device,session:e.session,flags:{...e.req.flags,snapshotInteractiveOnly:!0},logPath:e.logPath??""})).snapshot.nodes].sort(ec),r=el(t,6,{includeIdentifiers:!0});if(0===r.length)return null;let n=el(t.filter(e=>!ef(e)),4,{includeIdentifiers:!1}),s=el(t.filter(ep),4,{includeIdentifiers:!0});return{summary:(n.length>0?n:r.slice(0,4)).join(", "),details:{labels:r,...s.length>0?{buttons:s}:{}}}}function el(e,t,r){let n=new Set,s=[];for(let o of e){let e=ed(o,r);if(!(!e||n.has(e))&&(n.add(e),s.push(e),s.length>=t))break}return s}function ec(e,t){var r,n;return eu(e)-eu(t)||(r=e,n=t,r.rect&&n.rect?r.rect.y-n.rect.y||r.rect.x-n.rect.x:r.rect?-1:n.rect?1:(r.depth??0)-(n.depth??0)||r.index-n.index)}function eu(e){let t=!!ed(e,{includeIdentifiers:!1});return 2*!!ef(e)+ +!t}function ed(e,t){let r=(t.includeIdentifiers?[e.label,e.value,e.identifier]:[e.label,e.value]).map(e=>"string"==typeof e?e.trim():"").find(e=>e.length>0);return r?r.replace(/\s+/g," ").slice(0,80):""}function ef(e){let t=M(`${e.type??""} ${e.role??""} ${e.subrole??""}`),r=`${e.label??""} ${e.value??""}`.trim().toLowerCase();return es.some(e=>t.includes(e))||eo.has(r)||r.endsWith(".fill")}function ep(e){return M(`${e.type??""} ${e.role??""} ${e.subrole??""}`).includes("button")}function em(e){var t;let{session:r,selectorExpression:n}=e;if(!r||!s(r.device)||r.postGestureStabilization)return null;let o=G(n);if(!o||1!==o.selectors.length)return null;let i=o.selectors[0];if(!i||1!==i.terms.length)return null;let a=i.terms[0];return a&&"string"==typeof a.value&&("id"===(t=a.key)||"label"===t||"text"===t||"value"===t)?{key:a.key,value:a.value,raw:i.raw}:null}function eg(e,t={}){let r=k(e);if("ELEMENT_NOT_FOUND"===r.code)return!0===t.allowElementNotFound;if("COMMAND_FAILED"!==r.code)return!1;let n=r.message.toLowerCase();return n.includes("fetch failed")||n.includes("timed out")||n.includes("timeout")||n.includes("runner did not accept connection")||n.includes("invalid runner response")}async function eh(e){let{device:t,node:r,flags:n,appBundleId:o,traceOutPath:i,surface:a,contextFromFlags:l}=e,c=$(r),d=L(r.rect);if(!d||s(t)&&c&&!E(r.type??""))return c;try{let e=await u(t,"read",[String(d.x),String(d.y)],void 0,{...l(n,o,i),surface:a}),s=e&&"object"==typeof e?e:void 0,f="string"==typeof s?.text?s.text:"";if(f.trim())return f;return _({level:"warn",phase:"interaction_read_fallback",data:{reason:"empty_backend_text",nodeRef:r.ref,surface:a,platform:t.platform}}),c}catch(e){return _({level:"warn",phase:"interaction_read_fallback",data:{reason:"backend_read_failed",nodeRef:r.ref,surface:a,platform:t.platform,error:e instanceof Error?e.message:String(e)}}),c}}function ey(t){let r,n,{session:s,sessionStore:a,sessionName:l}=t,c=0;return{capture:async u=>{var d,f;let p=Date.now(),m=(d=u,f=t.req.flags?.out,JSON.stringify({presentationKey:o(e(ek(d))),includeRects:!0===d.includeRects,outPath:d.outPath??f??null})),h=function(e){let{timestamp:t,lastSnapshotAt:r,lastSnapshotResult:n,lastSnapshotCacheKey:s,session:o,request:i,cacheKey:a}=e;if(!(i.cache?.forceFresh===!0||!n||s!==a||t-r>=750||g(o))&&!ex(o,i))return n}({timestamp:p,lastSnapshotAt:c,lastSnapshotResult:r,lastSnapshotCacheKey:n,session:s,request:u,cacheKey:m});if(h)return h;let y=function(t){var r,n;let{session:s,timestamp:i,request:a}=t,l=s?.snapshot;if(l&&(r=s,n=a,!(n.cache?.forceFresh===!0||n.cache?.useSessionSnapshot!==!0||g(r)||ex(r,n)))&&i-l.createdAt<750&&l.presentationKey===function(t){return o(e(ek(t)))}(a))return l}({session:s,timestamp:p,request:u});if(y)return c=y.createdAt,r={snapshot:y},n=m,r;let w=await ew({params:t,request:u}),S={snapshot:w};return function(e){let{session:t,sessionStore:r,sessionName:n,snapshot:s}=e;!t||O(s.snapshotQuality)||(i(t,s),r.set(n,t))}({session:s,sessionStore:a,sessionName:l,snapshot:w}),c=p,r=S,n=m,S}}}async function ew(e){var t;let{params:r,request:n}=e,s=await ev(r,n,n.snapshotScope);return n.recovery?.legacyIosSparse&&!(t=s).snapshotQuality&&"xctest"===t.backend&&1===t.nodes.length&&t.nodes[0]?.type==="Application"?await eS({runtimeParams:r,request:n,policy:n.recovery.legacyIosSparse}):n.recovery?.sparseVerdictQueryScope?.shouldScope&&O(s.snapshotQuality)?await e_({runtimeParams:r,request:n,policy:n.recovery.sparseVerdictQueryScope,snapshot:s}):s}async function eS(e){let{runtimeParams:t,request:r,policy:n}=e;try{return await ev(t,r,n.scope,!1)}catch(e){if(!n.shouldScope)throw e;return await ev(t,r,n.query,!1)}}async function e_(e){let{runtimeParams:t,request:r,policy:n,snapshot:s}=e;try{return await ev(t,r,n.query,!1)}catch{return s}}async function ev(e,t,r,n=t.flags?.snapshotInteractiveOnly){return(await c({device:e.device,session:e.session,flags:{...t.flags,snapshotInteractiveOnly:n},outPath:t.outPath??e.req.flags?.out,logPath:e.logPath??"",snapshotScope:r,includeRects:t.includeRects})).snapshot}function ex(e,t){return t.cache?.bypassForPostGestureStabilization===!0&&!!e?.postGestureStabilization}function ek(e){return void 0===e.snapshotScope?e.flags:{...e.flags,snapshotScope:e.snapshotScope}}function eI(e){return b({backend:function(e){let{req:t,session:r,device:s,logPath:o,sessionName:i,sessionStore:a}=e,c=ey({device:s,session:r,sessionStore:a,sessionName:i,req:t,logPath:o});return{platform:n(s),captureSnapshot:async(e,r)=>{var n;let o,i={...t.flags,...(n=r,o={},n?.interactiveOnly!==void 0&&(o.snapshotInteractiveOnly=n.interactiveOnly),n?.scope!==void 0&&(o.snapshotScope=n.scope),n?.depth!==void 0&&(o.snapshotDepth=n.depth),n?.raw!==void 0&&(o.snapshotRaw=n.raw),o)},a=r?.includeRects===!0,l=r?.scope??t.flags?.snapshotScope,u="wait"===t.command||"find"===t.command||a&&"web"===s.platform;return await c.capture({flags:i,snapshotScope:l,includeRects:a,cache:{forceFresh:u,useSessionSnapshot:!0,bypassForPostGestureStabilization:!0}})},readText:async(n,i)=>({text:await eh({device:s,node:i,flags:t.flags,appBundleId:r?.appBundleId,traceOutPath:r?.trace?.outPath,surface:r?.surface,contextFromFlags:e.contextFromFlags??((e,t,r)=>l(o??"",e,t,r))})}),findText:async(t,r)=>({found:await eP(e,r)})}}(e),...j("selector commands",{plural:!0}),sessions:a({sessionName:e.sessionName,getSession:()=>e.session,recordOptions:{includeSnapshot:!0},setRecord:t=>{e.session&&t.snapshot&&(i(e.session,t.snapshot),e.sessionStore.set(e.sessionName,e.session))}})})}async function eA(e,t){let r=e.sessionStore.get(e.sessionName);if(!r&&t.requireSession)return{ok:!1,response:h()};let n=r?.device??await y(e.req.flags??{});r||await v(n);let s=d(t.capability,n);return s?{ok:!1,response:s}:{ok:!0,runtime:eI({...e,session:r,device:n})}}async function eP(e,t){let r=await eN(e,t);if(null!==r)return r;let n=await eR(e,t);return null!==n?n:await eb(e,t)}async function eN(e,r){return t(e.device)&&e.session?.surface&&"app"!==e.session.surface?await eb(e,r):null}async function eR(e,t){var n,s,o;let i=r((n=e).device.platform)&&n.session?.appBundleId?{device:n.device,appBundleId:n.session.appBundleId,traceLogPath:n.session.trace?.outPath}:null;if(!i)return null;let a=await w(i.device,{command:"findText",text:t,appBundleId:i.appBundleId},(s=e,o=i,x({req:s.req,logPath:s.logPath,traceLogPath:o.traceLogPath})));return a?.found===!0}async function eb(e,t){return!!D((await eq(e)).nodes,t)}async function eq(e){let t=ey({device:e.device,session:e.session,sessionStore:e.sessionStore,sessionName:e.sessionName,req:e.req,logPath:e.logPath}),{snapshot:r}=await t.capture({flags:{...e.req.flags,snapshotInteractiveOnly:!1},cache:{forceFresh:!0,bypassForPostGestureStabilization:!0}});return r}async function eC(e){var t;let{req:r}=e;if("find"!==r.command)return null;let n=r.positionals??[];if(0===n.length)return p("INVALID_ARGS","find requires a locator or text");let s=F(n);if(!s.query)return p("INVALID_ARGS","find requires a value");if(r.flags?.findFirst&&r.flags?.findLast)return p("INVALID_ARGS","find accepts only one of --first or --last");let o=s.action;if("exists"!==(t=o)&&"wait"!==t&&"get_text"!==t&&"get_attrs"!==t)return null;let i=await eA(e,{requireSession:!1,capability:"find"});return i.ok?await eU(async()=>{let t=await i.runtime.selectors.find({session:e.sessionName,requestId:r.meta?.requestId,locator:s.locator,query:s.query,action:o,timeoutMs:s.timeoutMs});return et(e.sessionStore,e.sessionName,r,function(e,t){if("exists"===t)return{found:!0};if("wait"===t)return{found:!0,waitedMs:e.waitedMs};let r="string"==typeof e.ref?e.ref:void 0;return"get_attrs"===t?{ref:r,action:"get attrs"}:{ref:r,action:"get text",text:"string"==typeof e.text?e.text:""}}(t,o)),"found"===t.kind?{found:!0,..."number"==typeof t.waitedMs?{waitedMs:t.waitedMs}:{}}:{..."string"==typeof t.ref?{ref:t.ref}:{},..."string"==typeof t.text?{text:t.text}:{},...t.node&&"object"==typeof t.node?{node:t.node}:{}}}):i.response}async function eM(e){let{req:t}=e;if("get"!==t.command)return null;let r=t.positionals?.[0];if("text"!==r&&"attrs"!==r)return p("INVALID_ARGS","get only supports text or attrs");let n=function(e){let t=e.positionals?.[1]??"";if(t.startsWith("@"))return{ok:!0,target:{kind:"ref",ref:t,fallbackLabel:e.positionals.length>2?e.positionals.slice(2).join(" ").trim():""}};let r=e.positionals?.slice(1).join(" ").trim()??"";return r?{ok:!0,target:{kind:"selector",selector:r}}:{ok:!1,response:p("INVALID_ARGS","get requires @ref or selector expression")}}(t);if(!n.ok)return n.response;if("ref"===n.target.kind){let e=Q("get",t.flags);if(e)return e}if("selector"===n.target.kind){let t=await e$(e,r,n.target.selector);if(t)return t}let s=await eA(e,{requireSession:!0,capability:"get"});return s.ok?await eU(async()=>{let o=await s.runtime.selectors.get({session:e.sessionName,requestId:t.meta?.requestId,property:r,target:n.target});return et(e.sessionStore,e.sessionName,t,Y(o,r)),Z(o)}):s.response}async function eE(e){let{req:t}=e;if("is"!==t.command)return null;let r=(t.positionals?.[0]??"").toLowerCase();if(!N(r))return p("INVALID_ARGS","is requires predicate: visible|hidden|exists|editable|selected|text");let{split:n}=C(t.positionals??[]);if(!n)return p("INVALID_ARGS","is requires a selector expression");let s=n.rest.join(" ").trim();if("text"===r&&!s)return p("INVALID_ARGS","is text requires expected text value");if("text"!==r&&n.rest.length>0)return p("INVALID_ARGS",`is ${r} does not accept trailing values`);let o=await eL(e,r,n.selectorExpression,s);if(o)return o;let i=await eA(e,{requireSession:!0,capability:"is"});if(!i.ok)return i.response;let a=await eU(async()=>{let o=await i.runtime.selectors.is({session:e.sessionName,requestId:t.meta?.requestId,predicate:r,selector:n.selectorExpression,expectedText:s});return et(e.sessionStore,e.sessionName,t,o),ee(o)});return await eT(e,a,`is ${r}`)}async function eD(e){let{req:t,sessionName:r,sessionStore:n}=e,s=K(t.positionals??[]);if(!s)return p("INVALID_ARGS","wait requires a duration or text");let{session:o,device:i}=await V(n,r,t.flags);if("sleep"!==s.kind){let e=d("wait",i);if(e)return e}if("selector"===s.kind){let t=await eO({...e,session:o,device:i,selectorExpression:s.selectorExpression,timeoutMs:s.timeoutMs});if(t)return t}let a=async()=>{let a=eI({...e,session:o,device:i}),l=await eU(async()=>{let e=await a.selectors.wait({session:r,requestId:t.meta?.requestId,target:function(e,t){if("sleep"===e.kind)return{kind:"sleep",durationMs:e.durationMs};if("selector"===e.kind)return{kind:"selector",selector:e.selectorExpression,timeoutMs:e.timeoutMs};if("ref"===e.kind){if(!t?.snapshot)throw new A("INVALID_ARGS","Ref wait requires an existing snapshot in session.");return{kind:"ref",ref:e.rawRef,timeoutMs:e.timeoutMs}}if(!e.text)throw new A("INVALID_ARGS","wait requires text");return{kind:"text",text:e.text,timeoutMs:e.timeoutMs}}(s,o)});return et(n,r,t,e),{waitedMs:e.waitedMs,..."string"==typeof e.text?{text:e.text}:{},..."string"==typeof e.selector?{selector:e.selector}:{}}}),c=await ei({req:t,logPath:e.logPath,session:o,device:i},l);return await eT(e,c,"wait")};return"sleep"===s.kind?await a():await U(o,i,a)}async function e$(e,t,r){let n=e.sessionStore.get(e.sessionName),s=em({session:n,selectorExpression:r});if(!n||!s||"text"===t&&"id"!==s.key)return null;let o=await eG(e,n,s);if(eV(o))return o.response;if(!o)return null;let i=function(e,t,r){if(!r.found||!r.node)return null;let n={target:{kind:"selector",selector:t},node:r.node,selectorChain:[t]};return"attrs"===e?{kind:"attrs",...n}:"string"!=typeof r.text?null:{kind:"text",...n,text:r.text}}(t,s.raw,o);return i?(et(e.sessionStore,e.sessionName,e.req,Y(i,t)),{ok:!0,data:Z(i)}):null}async function eL(e,t,r,n){var s,o,i,a,l;let c;if("hidden"===t)return null;let u=await eK(e,r);if(eV(u))return u.response;if(!u?.result.found||!u.result.node)return null;let d="exists"===t?{predicate:t,pass:!0,selector:u.selector.raw,matches:1,selectorChain:[u.selector.raw]}:(s=t,o=n,i=u.selector.raw,a=u.session,c=P({predicate:s,node:l=u.result.node,nodes:[l],expectedText:o,platform:a.device.platform}),{predicate:s,pass:c.pass,selector:i,..."text"===s?{text:c.actualText}:{},selectorChain:[i]});return d?(et(e.sessionStore,e.sessionName,e.req,d),{ok:!0,data:ee(d)}):null}async function eO(e){let t=em({session:e.session,selectorExpression:e.selectorExpression});if(!e.session||!t)return null;let r=Date.now(),n=await eG(e,e.session,t);if(eV(n))return n.response;if(!n?.found)return null;let s={kind:"selector",selector:t.raw,waitedMs:Date.now()-r,selectorChain:[t.raw]};return et(e.sessionStore,e.sessionName,e.req,s),await ei({req:e.req,logPath:e.logPath,session:e.session,device:e.device},{ok:!0,data:s})}async function eK(e,t){let r=e.sessionStore.get(e.sessionName),n=em({session:r,selectorExpression:t});if(!r||!n)return null;let s=await eG(e,r,n);return eV(s)?s:s?{session:r,selector:n,result:s}:null}async function eF(e,t,r){let n=await w(t.device,{command:"querySelector",selectorKey:r.key,selectorValue:r.value,appBundleId:t.appBundleId},x({req:e.req,logPath:e.logPath,traceLogPath:t.trace?.outPath})),s=!0===n.found,o=function(e){let t=e.nodes;if(!Array.isArray(t))return;let r=t[0];if(r&&"object"==typeof r)return r}(n);return{found:s,..."string"==typeof n.text?{text:n.text}:{},...o?{node:o}:{}}}async function eG(e,t,r){try{return await eF(e,t,r)}catch(e){if(eg(e,{allowElementNotFound:!0}))return null;return{kind:"error",response:{ok:!1,error:I(e)}}}}function eV(e){return null!==e&&"kind"in e&&"error"===e.kind}async function eU(e){try{return{ok:!0,data:await e()}}catch(t){let e=k(t);return p(e.code,e.message,e.details)}}async function eT(e,t,r){var n;let s;if(t.ok)return t;let o=e.sessionStore.get(e.sessionName);if(!o)return t;try{s=await J(o)}catch{return t}return s?p(t.error.code,`${r} failed because ${"com.google.android.permissioncontroller"===(n=s).foregroundPackage?`Android permission dialog is blocking ${n.expectedPackage}`:`${n.foregroundPackage} is foreground instead of ${n.expectedPackage}`}.`,{...t.error.details??{},...s,blockedBy:"android_foreground_surface",originalMessage:t.error.message}):t}export{W as assertAndroidPressStayedInApp,H as buildSnapshotSession,j as createDaemonRuntimePolicy,ey as createSelectorCaptureRuntime,eC as dispatchFindReadOnlyViaRuntime,eM as dispatchGetViaRuntime,eE as dispatchIsViaRuntime,eD as dispatchWaitViaRuntime,X as isAndroidEscapeError,eg as isDirectIosSelectorFallbackError,em as readSimpleIosSelectorTarget,eh as readTextForNode,B as recordIfSession,Q as refSnapshotFlagGuardResponse,V as resolveSessionDevice,U as withSessionlessRunnerCleanup};
|
package/dist/src/selectors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function findSelectorChainMatch(nodes: SnapshotState['nodes'], chain: SelectorChain, options: {
|
|
2
|
-
platform: Platform;
|
|
2
|
+
platform: Platform | PublicPlatform;
|
|
3
3
|
requireRect?: boolean;
|
|
4
4
|
}): {
|
|
5
5
|
selectorIndex: number;
|
|
@@ -12,7 +12,7 @@ export declare function formatSelectorFailure(chain: SelectorChain, diagnostics:
|
|
|
12
12
|
unique?: boolean;
|
|
13
13
|
}): string;
|
|
14
14
|
|
|
15
|
-
export declare function isNodeEditable(node: SnapshotNode, platform: Platform): boolean;
|
|
15
|
+
export declare function isNodeEditable(node: SnapshotNode, platform: Platform | PublicPlatform): boolean;
|
|
16
16
|
|
|
17
17
|
export declare function isNodeVisible(node: SnapshotNode): boolean;
|
|
18
18
|
|
|
@@ -22,7 +22,11 @@ export declare function parseSelectorChain(expression: string): SelectorChain;
|
|
|
22
22
|
|
|
23
23
|
declare type Platform = (typeof PLATFORMS)[number];
|
|
24
24
|
|
|
25
|
-
declare const PLATFORMS: readonly ['
|
|
25
|
+
declare const PLATFORMS: readonly ['apple', 'android', 'linux', 'web'];
|
|
26
|
+
|
|
27
|
+
declare const PUBLIC_PLATFORMS: readonly ['ios', 'macos', 'android', 'linux', 'web'];
|
|
28
|
+
|
|
29
|
+
declare type PublicPlatform = (typeof PUBLIC_PLATFORMS)[number];
|
|
26
30
|
|
|
27
31
|
declare type RawSnapshotNode = {
|
|
28
32
|
index: number;
|
|
@@ -59,13 +63,13 @@ declare type Rect = {
|
|
|
59
63
|
};
|
|
60
64
|
|
|
61
65
|
export declare function resolveSelectorChain(nodes: SnapshotState['nodes'], chain: SelectorChain, options: {
|
|
62
|
-
platform: Platform;
|
|
66
|
+
platform: Platform | PublicPlatform;
|
|
63
67
|
requireRect?: boolean;
|
|
64
68
|
requireUnique?: boolean;
|
|
65
69
|
disambiguateAmbiguous?: boolean;
|
|
66
70
|
}): SelectorResolution | null;
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
declare type Selector = {
|
|
69
73
|
raw: string;
|
|
70
74
|
terms: SelectorTerm[];
|
|
71
75
|
};
|
|
@@ -82,7 +86,7 @@ export declare type SelectorDiagnostics = {
|
|
|
82
86
|
|
|
83
87
|
declare type SelectorKey = 'id' | 'role' | 'text' | 'label' | 'value' | 'appname' | 'windowtitle' | 'visible' | 'hidden' | 'editable' | 'selected' | 'enabled' | 'hittable';
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
declare type SelectorResolution = {
|
|
86
90
|
node: SnapshotNode;
|
|
87
91
|
selector: Selector;
|
|
88
92
|
selectorIndex: number;
|
|
@@ -97,13 +101,17 @@ declare type SelectorTerm = {
|
|
|
97
101
|
|
|
98
102
|
declare type SnapshotBackend = 'xctest' | 'android' | 'macos-helper' | 'linux-atspi' | 'web';
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
declare type SnapshotNode = RawSnapshotNode & {
|
|
101
105
|
ref: string;
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
/**
|
|
105
109
|
* Structured quality verdict computed once by the iOS runner's snapshot capture plan.
|
|
106
110
|
* The daemon renders it; it never re-derives degradation from node shapes.
|
|
111
|
+
*
|
|
112
|
+
* Defined here (the foundational snapshot type module) rather than in
|
|
113
|
+
* snapshot-quality.ts so SnapshotNode can reference it without a cyclic import;
|
|
114
|
+
* snapshot-quality.ts (the validation logic) re-exports it for existing callers.
|
|
107
115
|
*/
|
|
108
116
|
declare type SnapshotQualityVerdict = {
|
|
109
117
|
state: 'healthy' | 'recovered' | 'sparse';
|
package/dist/src/selectors.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{findSelectorChainMatch,formatSelectorFailure,isNodeEditable,isNodeVisible,resolveSelectorChain}from"./
|
|
1
|
+
export{findSelectorChainMatch,formatSelectorFailure,isNodeEditable,isNodeVisible,resolveSelectorChain}from"./893.js";export{isSelectorToken,parseSelectorChain,tryParseSelectorChain}from"./587.js";
|
package/dist/src/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)}export{
|
|
1
|
+
import{SESSION_SURFACES as e,isCommandName as t,stringSchema as r,DEVICE_ROTATIONS as n,listMcpCommandMetadata as o,booleanSchema as i,BACK_MODES as s,looseObjectSchema as a}from"./2948.js";import{DEVICE_TARGETS as c,RESPONSE_LEVELS as u,jsonRpcRequestSchema as d,PLATFORMS as p}from"./6849.js";import{formatCliOutput as l}from"./cli-output.js";import{readVersion as f}from"./1404.js";function m(e){return{type:"number",...e?{description:e}:{}}}function h(e,t){return{type:"string",enum:e,...t?{description:t}:{}}}function g(e){return{type:"string",const:e}}function y(e,t=[],r){return{type:"object",...r?{description:r}:{},properties:e,...t.length>0?{required:t}:{}}}let b={type:"array",items:{type:"string"}},w=y({x:m(),y:m(),width:m(),height:m()},["x","y","width","height"]),v=y({x:m(),y:m()},["x","y"]),x=y({index:m(),ref:r("Stable snapshot ref such as e12."),type:r(),role:r(),subrole:r(),label:r(),value:r(),identifier:r(),rect:w,enabled:i(),selected:i(),focused:i(),visibleToUser:i(),hittable:i(),depth:m(),parentIndex:m(),pid:m(),bundleId:r(),appName:r(),windowTitle:r(),surface:r(),hiddenContentAbove:i(),hiddenContentBelow:i(),interactionBlocked:h(["covered"]),presentationHints:b},["index","ref"],"Resolved snapshot node for the matched element."),k=y({kind:g("ref"),ref:r()},["kind","ref"]),C=y({kind:g("selector"),selector:r()},["kind","selector"]);function _(e={}){let t=e.properties??{},n=e.required??[];return{type:"object",oneOf:[y({kind:g("point"),point:v,...t},["kind","point",...n]),y({kind:g("ref"),point:v,target:k,node:x,selectorChain:b,refLabel:r(),...t},["kind","target",...n]),y({kind:g("selector"),point:v,target:C,node:x,selectorChain:b,refLabel:r(),...t},["kind","point","target","node","selectorChain",...n])]}}let S=a("Raw backend result passthrough."),E={platform:h(p),target:h(c),device:r("Human-readable device name."),id:r("Stable device id."),kind:h(["simulator","emulator","device"])},A=["platform","target","device","id","kind"],j=y({success:i(),exitCode:m(),stdout:r(),stderr:r(),error:a("Normalized error detail when shutdown failed.")},["success","exitCode","stdout","stderr"]),O={press:_({properties:{backendResult:S,message:r()}}),fill:_({properties:{text:r("Text submitted to the field."),warning:r(),backendResult:S,message:r()},required:["text"]}),longpress:_({properties:{durationMs:m(),backendResult:S,message:r()}}),boot:y({...E,booted:{type:"boolean",const:!0}},[...A,"booted"]),shutdown:y({...E,shutdown:j},[...A,"shutdown"]),viewport:y({width:m(),height:m(),message:r()},["width","height","message"]),home:y({action:g("home"),message:r()},["action","message"]),back:y({action:g("back"),mode:h(s),message:r()},["action","mode","message"]),rotate:y({action:g("rotate"),orientation:h(n),message:r()},["action","orientation","message"]),"app-switcher":y({action:g("app-switcher"),message:r()},["action","message"]),clipboard:{type:"object",oneOf:[y({action:g("read"),text:r()},["action","text"]),y({action:g("write"),textLength:m(),message:r()},["action","textLength","message"])]},appstate:{type:"object",oneOf:[y({platform:h(["ios","macos"]),appName:r(),appBundleId:r(),source:g("session"),surface:h(e),device_udid:r("iOS only — the session device UDID."),ios_simulator_device_set:{type:["string","null"],description:"iOS only — the simulator set path, or null when unknown."}},["platform","appName","source","surface"]),y({platform:g("android"),package:r(),activity:r()},["platform","package","activity"])]},keyboard:y({platform:h(["android","ios"]),action:h(["status","dismiss","enter"]),visible:i(),wasVisible:i(),dismissed:i(),attempts:m(),inputType:r(),type:h(["text","number","email","phone","password","datetime","unknown"]),inputMethodPackage:r(),focusedPackage:r(),focusedResourceId:r(),inputOwner:h(["app","ime","unknown"]),message:r()},["platform","action"])},R=function(e={}){return{execute:async(r,n)=>{var o;if(!t(r))throw Error(`Unknown command tool: ${r}`);let i=!(o=n)||"object"!=typeof o||Array.isArray(o)?{client:{},outputFormat:"optimized"}:{client:function(e){let t=e.stateDir,r=e.includeCost,n=e.responseLevel,o={};if(void 0!==t&&("string"!=typeof t||0===t.length))throw Error("Expected stateDir to be a non-empty string.");if("string"==typeof t&&(o.stateDir=t),void 0!==r&&"boolean"!=typeof r)throw Error("Expected includeCost to be a boolean.");!0===r&&(o.cost=!0);let i=function(e){if(void 0!==e){if("string"!=typeof e||!u.includes(e))throw Error("Expected responseLevel to be one of 'digest', 'default', or 'full'.");return e}}(n);return void 0!==i&&(o.responseLevel=i),o}(o),outputFormat:function(e){if(void 0===e)return"optimized";if("optimized"!==e&&"json"!==e)throw Error('Expected mcpOutputFormat to be "optimized" or "json".');return e}(o.mcpOutputFormat)},s=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return e;let{stateDir:t,mcpOutputFormat:r,includeCost:n,responseLevel:o,...i}=e;return i}(n),a=await N(e,i.client),c=await (e.runCommand??L)(a,r,s);return{isError:!1,structuredContent:c,content:[{type:"text",text:function(e){if("json"===e.outputFormat||void 0!==e.responseLevel&&"default"!==e.responseLevel)return P(e.result);let t=l({name:e.name,input:e.input,result:e.result});return"string"==typeof t?.text?t.text:P(t?.data??e.result)}({name:r,input:s,result:c,outputFormat:i.outputFormat,responseLevel:i.client.responseLevel})}]}}}}();async function N(e,t){if(e.createClient)return await e.createClient(t);let{createAgentDeviceClient:r}=await import("./495.js");return r(t)}async function L(e,t,r){let n=await import("./command-surface.js");return await n.runCommand(e,t,r)}function P(e){return"string"==typeof e?e:JSON.stringify(e,null,2)}async function M(e){if("2.0"!==e.jsonrpc||"string"!=typeof e.method)return U(e.id??null,-32600,"Invalid JSON-RPC request.");if(void 0===e.id)return null;try{var t,r;return t=e.id,r=await K(e.method,e.params),{jsonrpc:"2.0",id:t,result:r}}catch(t){if(t instanceof F)return U(e.id,-32601,t.message);return U(e.id,-32602,t instanceof Error?t.message:String(t))}}async function K(e,t){switch(e){case"initialize":return{protocolVersion:"2025-11-25",capabilities:{tools:{}},serverInfo:{name:"agent-device",version:f()}};case"ping":return{};case"tools/list":return{tools:o().map(e=>{var t;let r=e.name in O?O[e.name]:void 0;return{name:e.name,description:e.description,inputSchema:{...t=e.inputSchema,properties:{...t.properties,stateDir:{type:"string",description:"Agent-device state directory."},mcpOutputFormat:{type:"string",enum:["optimized","json"],description:"MCP text content format. Defaults to optimized agent-friendly text; use json for JSON text. Structured content is always returned separately."},includeCost:{type:"boolean",description:"Include per-command agent-cost (cost.wallClockMs, …) in structuredContent. Defaults to off; the default response shape is unchanged."},responseLevel:{type:"string",enum:["digest","default","full"],description:"Response verbosity: token-cheap digest / default (today) / full. Defaults to default; the default response shape is unchanged."}}},...r?{outputSchema:r}:{}}})};case"tools/call":return await D(t);default:throw new F(`Unsupported MCP method: ${e}`)}}async function D(e){let t=function(e){if(!e||"object"!=typeof e||Array.isArray(e))throw Error("Expected object parameters.");return e}(e),r=function(e,t){let r=e[t];if("string"!=typeof r||0===r.length)throw Error(`Expected ${t} to be a non-empty string.`);return r}(t,"name");try{return await R.execute(r,t.arguments)}catch(e){return function(e,t=!1){return{isError:t,content:[{type:"text",text:e}]}}(e instanceof Error?e.message:String(e),!0)}}function U(e,t,r){return{jsonrpc:"2.0",id:e,error:{code:t,message:r}}}class F extends Error{}async function I(){let e=function(e={}){let t=e.handlePayload??z,r=e.write??$,n=Promise.resolve();return{push:e=>{var o;let i=Array.isArray(o=e)?1===o.length?q(o[0]):null:q(o);n=n.then(async()=>{let n=await t(e);n&&r(n)}).catch(e=>{r({jsonrpc:"2.0",id:i,error:{code:-32603,message:e instanceof Error?e.message:String(e)}})})},idle:async()=>{await n}}}(),t=new T(t=>{e.push(t)});process.stdin.setEncoding("utf8"),process.stdin.on("data",e=>{try{t.push(e)}catch(e){$({jsonrpc:"2.0",id:null,error:{code:-32700,message:e instanceof Error?e.message:String(e)}})}}),await new Promise(e=>{process.stdin.on("end",e),process.stdin.on("close",e),process.stdin.resume()}),await e.idle()}function z(e){return Array.isArray(e)?H(e):J(e)}async function H(e){let t=[];for(let n of e){var r;t.push(...(r=await J(n))?[r]:[])}return t.length>0?t:null}async function J(e){let t;try{t=d.parse(e)}catch{return{jsonrpc:"2.0",id:q(e),error:{code:-32600,message:"Invalid JSON-RPC request."}}}return M(t)}function q(e){if(e&&"object"==typeof e&&!Array.isArray(e)){let t=e.id;if("string"==typeof t||"number"==typeof t||null===t)return t}return null}class T{buffer="";sink;constructor(e){this.sink=e}push(e){for(this.buffer+=e;;){let e=this.tryReadLineMessage();if(void 0!==e){this.emit(e);continue}break}}tryReadLineMessage(){let e=this.buffer.indexOf("\n");if(-1===e)return;let t=this.buffer.slice(0,e).trim();return this.buffer=this.buffer.slice(e+1),t.length>0?t:void 0}emit(e){this.sink(JSON.parse(e))}}function $(e){process.stdout.write(`${JSON.stringify(e)}
|
|
2
|
+
`)}export{I as runAgentDeviceMcpServer};
|