agent-device 0.3.3 → 0.3.4

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.
@@ -1,17 +1,17 @@
1
- let e,t;import i from"node:crypto";import{isCancel as r,select as a}from"@clack/prompts";import{node_path as n,runCmdStreaming as o,promises as s,asAppError as l,fileURLToPath as c,runCmdBackground as d,node_fs as u,node_os as f,errors_AppError as p,runCmd as m,node_net as h,whichCmd as w,readVersion as g}from"./274.js";async function v(e,t){let i=e,n=e=>e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim();if(t.platform&&(i=i.filter(e=>e.platform===t.platform)),t.udid){let e=i.find(e=>e.id===t.udid&&"ios"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No iOS device with UDID ${t.udid}`);return e}if(t.serial){let e=i.find(e=>e.id===t.serial&&"android"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No Android device with serial ${t.serial}`);return e}if(t.deviceName){let e=n(t.deviceName),r=i.find(t=>n(t.name)===e);if(!r)throw new p("DEVICE_NOT_FOUND",`No device named ${t.deviceName}`);return r}if(1===i.length)return i[0];if(0===i.length)throw new p("DEVICE_NOT_FOUND","No devices found",{selector:t});let o=i.filter(e=>e.booted);if(1===o.length)return o[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await a({message:"Multiple devices available. Choose a device to continue:",options:(o.length>0?o:i).map(e=>({label:`${e.name} (${e.platform}${e.kind?`, ${e.kind}`:""}${e.booted?", booted":""})`,value:e.id}))});if(r(e))throw new p("INVALID_ARGS","Device selection cancelled");if(e){let t=i.find(t=>t.id===e);if(t)return t}}return o[0]??i[0]}function A(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function y(e){return["1","true","yes","on"].includes((e??"").toLowerCase())}let I=2e4,b=12e4,N=1e4;class S{static fromTimeoutMs(e,t=Date.now()){return new S(t,e)}remainingMs(e=Date.now()){return Math.max(0,this.expiresAtMs-e)}elapsedMs(e=Date.now()){return Math.max(0,e-this.startedAtMs)}isExpired(e=Date.now()){return 0>=this.remainingMs(e)}constructor(e,t){A(this,"startedAtMs",void 0),A(this,"expiresAtMs",void 0),this.startedAtMs=e,this.expiresAtMs=e+Math.max(0,t)}}async function D(e,t={},i={}){let r,a={maxAttempts:t.maxAttempts??3,baseDelayMs:t.baseDelayMs??200,maxDelayMs:t.maxDelayMs??2e3,jitter:t.jitter??.2,shouldRetry:t.shouldRetry};for(let t=1;t<=a.maxAttempts&&(!i.deadline?.isExpired()||!(t>1));t+=1)try{let r=await e({attempt:t,maxAttempts:a.maxAttempts,deadline:i.deadline});return i.onEvent?.({phase:i.phase,event:"succeeded",attempt:t,maxAttempts:a.maxAttempts,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs()}),r}catch(s){r=s;let e=i.classifyReason?.(s);if(i.onEvent?.({phase:i.phase,event:"attempt_failed",attempt:t,maxAttempts:a.maxAttempts,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs(),reason:e}),t>=a.maxAttempts||a.shouldRetry&&!a.shouldRetry(s,t))break;let n=function(e,t,i,r){let a=Math.min(t,e*2**(r-1));return Math.max(0,a+a*i*(2*Math.random()-1))}(a.baseDelayMs,a.maxDelayMs,a.jitter,t),o=i.deadline?Math.min(n,i.deadline.remainingMs()):n;if(o<=0)break;i.onEvent?.({phase:i.phase,event:"retry_scheduled",attempt:t,maxAttempts:a.maxAttempts,delayMs:o,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs(),reason:e}),await function(e){return new Promise(t=>setTimeout(t,e))}(o)}if(i.onEvent?.({phase:i.phase,event:"exhausted",attempt:a.maxAttempts,maxAttempts:a.maxAttempts,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs(),reason:i.classifyReason?.(r)}),r)throw r;throw new p("COMMAND_FAILED","retry failed")}async function x(e,t={}){return D(()=>e(),{maxAttempts:t.attempts,baseDelayMs:t.baseDelayMs,maxDelayMs:t.maxDelayMs,jitter:t.jitter,shouldRetry:t.shouldRetry})}function k(e){let t=e.error?l(e.error):null,i=e.context?.platform,r=e.context?.phase;if(t?.code==="TOOL_MISSING")return"android"===i?"ADB_TRANSPORT_UNAVAILABLE":"IOS_TOOL_MISSING";let a=t?.details??{},n="string"==typeof a.message?a.message:void 0,o="string"==typeof a.stdout?a.stdout:void 0,s="string"==typeof a.stderr?a.stderr:void 0,c=a.boot&&"object"==typeof a.boot?a.boot:null,d=a.bootstatus&&"object"==typeof a.bootstatus?a.bootstatus:null,u=[e.message,t?.message,e.stdout,e.stderr,n,o,s,"string"==typeof c?.stdout?c.stdout:void 0,"string"==typeof c?.stderr?c.stderr:void 0,"string"==typeof d?.stdout?d.stdout:void 0,"string"==typeof d?.stderr?d.stderr:void 0].filter(Boolean).join("\n").toLowerCase();return"ios"===i&&(u.includes("runner did not accept connection")||"connect"===r&&(u.includes("timed out")||u.includes("timeout")||u.includes("econnrefused")||u.includes("connection refused")||u.includes("fetch failed")||u.includes("socket hang up")))?"IOS_RUNNER_CONNECT_TIMEOUT":"ios"===i&&"boot"===r&&(u.includes("timed out")||u.includes("timeout"))?"IOS_BOOT_TIMEOUT":"android"===i&&"boot"===r&&(u.includes("timed out")||u.includes("timeout"))?"ANDROID_BOOT_TIMEOUT":u.includes("resource temporarily unavailable")||u.includes("killed: 9")||u.includes("cannot allocate memory")||u.includes("system is low on memory")?"CI_RESOURCE_STARVATION_SUSPECTED":"android"===i&&(u.includes("device not found")||u.includes("no devices")||u.includes("device offline")||u.includes("offline")||u.includes("unauthorized")||u.includes("not authorized")||u.includes("unable to locate device")||u.includes("invalid device"))?"ADB_TRANSPORT_UNAVAILABLE":t?.code==="COMMAND_FAILED"||u.length>0?"BOOT_COMMAND_FAILED":"UNKNOWN"}function O(e){switch(e){case"IOS_BOOT_TIMEOUT":return"Retry simulator boot and inspect simctl bootstatus logs; in CI consider increasing AGENT_DEVICE_IOS_BOOT_TIMEOUT_MS.";case"IOS_RUNNER_CONNECT_TIMEOUT":return"Retry runner startup, inspect xcodebuild logs, and verify simulator responsiveness before command execution.";case"ANDROID_BOOT_TIMEOUT":return"Retry emulator startup and verify sys.boot_completed reaches 1; consider increasing startup budget in CI.";case"ADB_TRANSPORT_UNAVAILABLE":return"Check adb server/device transport (adb devices -l), restart adb, and ensure the target device is online and authorized.";case"CI_RESOURCE_STARVATION_SUSPECTED":return"CI machine may be resource constrained; reduce parallel jobs or use a larger runner.";case"IOS_TOOL_MISSING":return"Xcode command-line tools are missing or not in PATH; run xcode-select --install and verify xcrun works.";case"BOOT_COMMAND_FAILED":return"Inspect command stderr/stdout for the failing boot phase and retry after environment validation.";default:return"Retry once and inspect verbose logs for the failing phase."}}let _=y(process.env.AGENT_DEVICE_RETRY_LOGS);function M(e){return e.startsWith("emulator-")}async function L(e,t=N){return m("adb",["-s",e,"shell","getprop","sys.boot_completed"],{allowFailure:!0,timeoutMs:t})}async function R(e,t){let i=t.replace(/_/g," ").trim();if(!M(e))return i||e;let r=await m("adb",["-s",e,"emu","avd","name"],{allowFailure:!0,timeoutMs:N}),a=r.stdout.trim();return 0===r.exitCode&&a?a.replace(/_/g," "):i||e}async function E(){if(!await w("adb"))throw new p("TOOL_MISSING","adb not found in PATH");let e=(await m("adb",["devices","-l"],{timeoutMs:N})).stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("List of devices")).map(e=>e.split(/\s+/)).filter(e=>"device"===e[1]).map(e=>({serial:e[0],rawModel:(e.find(e=>e.startsWith("model:"))??"").replace("model:","")}));return await Promise.all(e.map(async({serial:e,rawModel:t})=>{let[i,r]=await Promise.all([R(e,t),C(e)]);return{platform:"android",id:e,name:i,kind:M(e)?"emulator":"device",booted:r}}))}async function C(e){try{let t=await L(e);return"1"===t.stdout.trim()}catch{return!1}}async function T(e,t=6e4){let i,r=S.fromTimeoutMs(t),a=Math.max(1,Math.ceil(t/1e3)),n=!1;try{await D(async({deadline:a})=>{if(a?.isExpired())throw n=!0,new p("COMMAND_FAILED","Android boot deadline exceeded",{serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),message:"timeout"});let o=Math.max(1e3,a?.remainingMs()??t),s=await L(e,Math.min(o,N));if(i=s,"1"!==s.stdout.trim())throw new p("COMMAND_FAILED","Android device is still booting",{serial:e,stdout:s.stdout,stderr:s.stderr,exitCode:s.exitCode})},{maxAttempts:a,baseDelayMs:1e3,maxDelayMs:1e3,jitter:0,shouldRetry:e=>{let t=k({error:e,stdout:i?.stdout,stderr:i?.stderr,context:{platform:"android",phase:"boot"}});return"ADB_TRANSPORT_UNAVAILABLE"!==t&&"ANDROID_BOOT_TIMEOUT"!==t}},{deadline:r,phase:"boot",classifyReason:e=>k({error:e,stdout:i?.stdout,stderr:i?.stderr,context:{platform:"android",phase:"boot"}}),onEvent:e=>{_&&process.stderr.write(`[agent-device][retry] ${JSON.stringify(e)}
2
- `)}})}catch(f){let a=l(f),o=i?.stdout,s=i?.stderr,c=i?.exitCode,d=k({error:f,stdout:o,stderr:s,context:{platform:"android",phase:"boot"}});"BOOT_COMMAND_FAILED"===d&&"Android device is still booting"===a.message&&(d="ANDROID_BOOT_TIMEOUT");let u={serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),reason:d,hint:O(d),stdout:o,stderr:s,exitCode:c};if(n||"ANDROID_BOOT_TIMEOUT"===d)throw new p("COMMAND_FAILED","Android device did not finish booting in time",u);if("TOOL_MISSING"===a.code)throw new p("TOOL_MISSING",a.message,{...u,...a.details??{}});if("ADB_TRANSPORT_UNAVAILABLE"===d)throw new p("COMMAND_FAILED",a.message,{...u,...a.details??{}});throw new p(a.code,a.message,{...u,...a.details??{}},a.cause)}}function P(e){let t=$(e),i=e=>{let i=F(t,e);if(null!==i)return"true"===i};return{text:F(t,"text"),desc:F(t,"content-desc"),resourceId:F(t,"resource-id"),className:F(t,"class"),bounds:F(t,"bounds"),clickable:i("clickable"),enabled:i("enabled"),focusable:i("focusable"),focused:i("focused")}}function $(e){let t=new Map,i=e.indexOf(" "),r=e.lastIndexOf(">");if(i<0||r<=i)return t;let a=/([^\s=/>]+)\s*=\s*(["'])([\s\S]*?)\2/y,n=i;for(;n<r;){for(;n<r;){let t=e[n];if(" "!==t&&"\n"!==t&&"\r"!==t&&" "!==t)break;n+=1}if(n>=r)break;let i=e[n];if("/"===i||">"===i)break;a.lastIndex=n;let o=a.exec(e);if(!o)break;t.set(o[1],o[3]),n=a.lastIndex}return t}function F(e,t){return e.get(t)??null}function B(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let i=Number(t[1]),r=Number(t[2]);return{x:i,y:r,width:Math.max(0,Number(t[3])-i),height:Math.max(0,Number(t[4])-r)}}function V(e){return e?e.toLowerCase():""}function U(e){let t=e.trim();return!!t&&/^[\w.]+:id\/[\w.-]+$/i.test(t)}let G={settings:{type:"intent",value:"android.settings.SETTINGS"}};function j(e,t){return["-s",e.id,...t]}async function q(e,t){let i=t.trim();if(i.includes("."))return{type:"package",value:i};let r=G[i.toLowerCase()];if(r)return r;let a=(await m("adb",j(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(i.toLowerCase()));if(1===a.length)return{type:"package",value:a[0]};if(a.length>1)throw new p("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:a});throw new p("APP_NOT_INSTALLED",`No package found matching "${t}"`)}async function W(e,t="launchable"){if("launchable"===t){let t=await m("adb",j(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c","android.intent.category.LAUNCHER"]),{allowFailure:!0});if(0===t.exitCode&&t.stdout.trim().length>0){let e=new Set;for(let i of t.stdout.split("\n")){let t=i.trim();if(!t)continue;let r=t.split(/\s+/)[0],a=r.includes("/")?r.split("/")[0]:r;a&&e.add(a)}if(e.size>0)return Array.from(e)}}return(await m("adb",j(e,"user-installed"===t?["shell","pm","list","packages","-3"]:["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function J(e,t="launchable"){let i=await W(e,t),r=new Set("launchable"===t?i:await W(e,"launchable"));return i.map(e=>({package:e,launchable:r.has(e)}))}async function H(e){let t=await z(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let i=await z(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return i||{}}async function z(e,t){for(let i of t){let t=function(e){for(let t of[/mCurrentFocus=Window\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mFocusedApp=AppWindowToken\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/,/ResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/]){let i=t.exec(e);if(i)return{package:i[1],activity:i[2]}}return null}((await m("adb",j(e,i),{allowFailure:!0})).stdout??"");if(t)return t}return null}async function X(e,t,i){e.booted||await T(e.id);let r=await q(e,t);if("intent"===r.type){if(i)throw new p("INVALID_ARGS","Activity override requires a package name, not an intent");await m("adb",j(e,["shell","am","start","-a",r.value]));return}if(i){let t=i.includes("/")?i:`${r.value}/${i.startsWith(".")?i:`.${i}`}`;await m("adb",j(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-n",t]));return}try{await m("adb",j(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-p",r.value]));return}catch(i){let t=await K(e,r.value);if(!t)throw i;await m("adb",j(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-n",t]))}}async function K(e,t){let i=await m("adb",j(e,["shell","cmd","package","resolve-activity","--brief",t]),{allowFailure:!0});return 0!==i.exitCode?null:function(e){let t=e.split("\n").map(e=>e.trim()).filter(Boolean);for(let e=t.length-1;e>=0;e-=1){let i=t[e];if(i.includes("/"))return i.split(/\s+/)[0]}return null}(i.stdout)}async function Y(e){e.booted||await T(e.id)}async function Z(e,t){if("settings"===t.trim().toLowerCase())return void await m("adb",j(e,["shell","am","force-stop","com.android.settings"]));let i=await q(e,t);if("intent"===i.type)throw new p("INVALID_ARGS","Close requires a package name, not an intent");await m("adb",j(e,["shell","am","force-stop",i.value]))}async function Q(e,t){let i=await q(e,t);if("intent"===i.type)throw new p("INVALID_ARGS","reinstall requires a package name, not an intent");let r=await m("adb",j(e,["uninstall",i.value]),{allowFailure:!0});if(0!==r.exitCode){let e=`${r.stdout}
3
- ${r.stderr}`.toLowerCase();if(!e.includes("unknown package")&&!e.includes("not installed"))throw new p("COMMAND_FAILED",`adb uninstall failed for ${i.value}`,{stdout:r.stdout,stderr:r.stderr,exitCode:r.exitCode})}return{package:i.value}}async function ee(e,t){await m("adb",j(e,["install",t]))}async function et(e,t,i){e.booted||await T(e.id);let{package:r}=await Q(e,t);return await ee(e,i),{package:r}}async function ei(e,t,i){await m("adb",j(e,["shell","input","tap",String(t),String(i)]))}async function er(e){await m("adb",j(e,["shell","input","keyevent","4"]))}async function ea(e){await m("adb",j(e,["shell","input","keyevent","3"]))}async function en(e){await m("adb",j(e,["shell","input","keyevent","187"]))}async function eo(e,t,i,r=800){await m("adb",j(e,["shell","input","swipe",String(t),String(i),String(t),String(i),String(r)]))}async function es(e,t){let i=t.replace(/ /g,"%s");await m("adb",j(e,["shell","input","text",i]))}async function el(e,t,i){await ei(e,t,i)}async function ec(e,t,i,r){await el(e,t,i);let a=null;for(let s of[{clearPadding:12,minClear:8,maxClear:48,chunkSize:4,delayMs:0},{clearPadding:24,minClear:16,maxClear:96,chunkSize:1,delayMs:15}]){var n,o;let l=(n=r.length+s.clearPadding,o=s.minClear,Math.max(o,Math.min(s.maxClear,n)));if(await eb(e,l),await eI(e,r,s.chunkSize,s.delayMs),(a=await eN(e,t,i))===r)return}throw new p("COMMAND_FAILED","Android fill verification failed",{expected:r,actual:a??null})}async function ed(e,t,i=.6){let{width:r,height:a}=await ew(e),n=Math.floor(r*i),o=Math.floor(a*i),s=Math.floor(r/2),l=Math.floor(a/2),c=s,d=l,u=s,f=l;switch(t){case"up":d=l-Math.floor(o/2),f=l+Math.floor(o/2);break;case"down":d=l+Math.floor(o/2),f=l-Math.floor(o/2);break;case"left":c=s-Math.floor(n/2),u=s+Math.floor(n/2);break;case"right":c=s+Math.floor(n/2),u=s-Math.floor(n/2);break;default:throw new p("INVALID_ARGS",`Unknown direction: ${t}`)}await m("adb",j(e,["shell","input","swipe",String(c),String(d),String(u),String(f),"300"]))}async function eu(e,t){for(let i=0;i<8;i+=1){let i="";try{i=await eg(e)}catch(t){let e=t instanceof Error?t.message:String(t);throw new p("UNSUPPORTED_OPERATION",`uiautomator dump failed: ${e}`)}if(function(e,t){let i=t.toLowerCase(),r=/<node[^>]+>/g,a=r.exec(e);for(;a;){let t=$(a[0]),n=(F(t,"text")??"").toLowerCase(),o=(F(t,"content-desc")??"").toLowerCase();if(n.includes(i)||o.includes(i)){let e=B(F(t,"bounds"));if(e)return{x:Math.floor(e.x+e.width/2),y:Math.floor(e.y+e.height/2)};return{x:0,y:0}}a=r.exec(e)}return null}(i,t))return;await ed(e,"down",.5)}throw new p("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function ef(e,t){let i=await m("adb",j(e,["exec-out","screencap","-p"]),{binaryStdout:!0});if(!i.stdoutBuffer)throw new p("COMMAND_FAILED","Failed to capture screenshot");await s.writeFile(t,i.stdoutBuffer)}async function ep(e,t,i){let r=t.toLowerCase(),a=function(e){let t=e.toLowerCase();if("on"===t||"true"===t||"1"===t)return!0;if("off"===t||"false"===t||"0"===t)return!1;throw new p("INVALID_ARGS",`Invalid setting state: ${e}`)}(i);switch(r){case"wifi":return void await m("adb",j(e,["shell","svc","wifi",a?"enable":"disable"]));case"airplane":await m("adb",j(e,["shell","settings","put","global","airplane_mode_on",a?"1":"0"])),await m("adb",j(e,["shell","am","broadcast","-a","android.intent.action.AIRPLANE_MODE","--ez","state",a?"true":"false"]));return;case"location":return void await m("adb",j(e,["shell","settings","put","secure","location_mode",a?"3":"0"]));default:throw new p("INVALID_ARGS",`Unsupported setting: ${t}`)}}async function em(e,t={}){return function(e,t,i){let r=function(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},i=[t],r=/<node\b[^>]*>|<\/node>/g,a=r.exec(e);for(;a;){let t=a[0];if(t.startsWith("</node")){i.length>1&&i.pop(),a=r.exec(e);continue}let n=P(t),o=B(n.bounds),s=i[i.length-1],l={type:n.className,label:n.text||n.desc,value:n.text,identifier:n.resourceId,rect:o,enabled:n.enabled,hittable:n.clickable??n.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||i.push(l),a=r.exec(e)}return t}(e),a=[],n=!1,o=i.depth??1/0,s=i.scope?function(e,t){let i=t.toLowerCase(),r=[...e.children];for(;r.length>0;){let e=r.shift(),t=e.label?.toLowerCase()??"",a=e.value?.toLowerCase()??"",n=e.identifier?.toLowerCase()??"";if(t.includes(i)||a.includes(i)||n.includes(i))return e;r.push(...e.children)}return null}(r,i.scope):null,l=s?[s]:r.children,c=new Map,d=e=>{let t=c.get(e);if(void 0!==t)return t;for(let t of e.children)if(t.hittable||d(t))return c.set(e,!0),!0;return c.set(e,!1),!1},u=(e,t,r,s=!1,l=!1)=>{var c,f,p,m,h,w;let g,v,A,y,I,b,N,S;if(a.length>=800){n=!0;return}if(t>o)return;let D=!!i.raw||(c=e,f=i,p=s,m=d(e),h=l,v=V(c.type),A=!!(c.label&&c.label.trim().length>0),y=!!(c.identifier&&c.identifier.trim().length>0),I=A&&!U(c.label??""),b=y&&!U(c.identifier??""),N=(g=(w=v).split(".").pop()??w).includes("layout")||"viewgroup"===g||"view"===g,S="imageview"===v||"imagebutton"===v,f.interactiveOnly?!!c.hittable||!!(I||b)&&!S&&(!N||!!h)&&(p||m||h):f.compact?I||b||!!c.hittable:!N&&!S||!!c.hittable||!!I||!!b&&!!m||m),x=r;D&&(x=a.length,a.push({index:x,type:e.type??void 0,label:e.label??void 0,value:e.value??void 0,identifier:e.identifier??void 0,rect:e.rect,enabled:e.enabled,hittable:e.hittable,depth:t,parentIndex:r}));let k=s||!!e.hittable,O=l||function(e){if(!e)return!1;let t=V(e);return t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")}(e.type);for(let i of e.children)if(u(i,t+1,x,k,O),n)return};for(let e of l)if(u(e,0,void 0,!1,!1),n)break;return n?{nodes:a,truncated:n}:{nodes:a}}(await eg(e),0,t)}async function eh(){if(!await w("adb"))throw new p("TOOL_MISSING","adb not found in PATH")}async function ew(e){let t=(await m("adb",j(e,["shell","wm","size"]))).stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(!t)throw new p("COMMAND_FAILED","Unable to read screen size");return{width:Number(t[1]),height:Number(t[2])}}async function eg(e){return x(()=>ev(e),{shouldRetry:ey})}async function ev(e){var t,i,r;let a,n,o=await m("adb",j(e,["exec-out","uiautomator","dump","/dev/tty"]),{allowFailure:!0});if(0===o.exitCode){let e=eA(o.stdout,o.stderr);if(e)return e}let s="/sdcard/window_dump.xml",l=await m("adb",j(e,["shell","uiautomator","dump",s])),c=(t=s,i=l.stdout,r=l.stderr,a=`${i}
4
- ${r}`,n=/dumped to:\s*(\S+)/i.exec(a),n?.[1]??t),d=await m("adb",j(e,["shell","cat",c])),u=eA(d.stdout,d.stderr);if(!u)throw new p("COMMAND_FAILED","uiautomator dump did not return XML",{stdout:d.stdout,stderr:d.stderr});return u}function eA(e,t){let i=`${e}
5
- ${t}`,r=i.indexOf("<?xml"),a=r>=0?r:i.indexOf("<hierarchy");if(a<0)return null;let n=i.lastIndexOf("</hierarchy>");if(n<0||n<a)return null;let o=i.slice(a,n+12).trim();return o.length>0?o:null}function ey(e){if(!(e instanceof p)||"COMMAND_FAILED"!==e.code)return!1;let t=`${e.details?.stderr??""}`.toLowerCase();return!!(t.includes("device offline")||t.includes("device not found")||t.includes("transport error")||t.includes("connection reset")||t.includes("broken pipe")||t.includes("timed out")||t.includes("no such file or directory"))}async function eI(e,t,i,r){let a=Math.max(1,Math.floor(i));for(let i=0;i<t.length;i+=a){let n=t.slice(i,i+a);await es(e,n),r>0&&i+a<t.length&&await eD(r)}}async function eb(e,t){let i=Math.max(0,t);await m("adb",j(e,["shell","input","keyevent","KEYCODE_MOVE_END"]),{allowFailure:!0});for(let t=0;t<i;t+=24){let r=Math.min(24,i-t);await m("adb",j(e,["shell","input","keyevent",...Array(r).fill("KEYCODE_DEL")]),{allowFailure:!0})}}async function eN(e,t,i){let r,a=await eg(e),n=/<node\b[^>]*>/g,o=null,s=null,l=null;for(;null!==(r=n.exec(a));){let e=P(r[0]),a=B(e.bounds);if(!a)continue;let n=e.className??"",c=(e.text??"").replace(/&quot;/g,'"').replace(/&apos;/g,"'").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&"),d=e.focused??!1;if(!c)continue;let u=Math.max(1,a.width*a.height),f=t>=a.x&&t<=a.x+a.width&&i>=a.y&&i<=a.y+a.height;if(d&&eS(n)){(!o||u<=o.area)&&(o={text:c,area:u});continue}if(f&&eS(n)){(!s||u<=s.area)&&(s={text:c,area:u});continue}f&&(!l||u<=l.area)&&(l={text:c,area:u})}return o?.text??s?.text??l?.text??null}function eS(e){let t=e.toLowerCase();return t.includes("edittext")||t.includes("textfield")}async function eD(e){await new Promise(t=>setTimeout(t,e))}async function ex(){if("darwin"!==process.platform)throw new p("UNSUPPORTED_PLATFORM","iOS tools are only available on macOS");if(!await w("xcrun"))throw new p("TOOL_MISSING","xcrun not found in PATH");let e=[],t=await m("xcrun",["simctl","list","devices","-j"]);try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices))for(let i of t)i.isAvailable&&e.push({platform:"ios",id:i.udid,name:i.name,kind:"simulator",booted:"Booted"===i.state})}catch(e){throw new p("COMMAND_FAILED","Failed to parse simctl devices JSON",void 0,e)}if(await w("xcrun"))try{let t=await m("xcrun",["devicectl","list","devices","--json"]);for(let i of JSON.parse(t.stdout).devices??[])i.platform?.toLowerCase().includes("ios")&&e.push({platform:"ios",id:i.identifier,name:i.name,kind:"device",booted:!0})}catch{}return e}let ek={settings:"com.apple.Preferences"},eO=function(e,t,i){if(!e)return t;let r=Number(e);return Number.isFinite(r)?Math.max(5e3,Math.floor(r)):t}(process.env.AGENT_DEVICE_IOS_BOOT_TIMEOUT_MS,b,5e3),e_=y(process.env.AGENT_DEVICE_RETRY_LOGS);async function eM(e,t){let i=t.trim();if(i.includes("."))return i;let r=ek[i.toLowerCase()];if(r)return r;if("simulator"===e.kind){let r=(await eV(e)).filter(e=>e.name.toLowerCase()===i.toLowerCase());if(1===r.length)return r[0].bundleId;if(r.length>1)throw new p("INVALID_ARGS",`Multiple apps matched "${t}"`,{matches:r})}throw new p("APP_NOT_INSTALLED",`No app found matching "${t}"`)}async function eL(e,t){let i=await eM(e,t);if("simulator"===e.kind){await eU(e),await m("open",["-a","Simulator"],{allowFailure:!0}),await m("xcrun",["simctl","launch",e.id,i]);return}await m("xcrun",["devicectl","device","process","launch","--device",e.id,i])}async function eR(e){"simulator"!==e.kind||"Booted"!==await eG(e.id)&&(await eU(e),await m("open",["-a","Simulator"],{allowFailure:!0}))}async function eE(e,t){let i=await eM(e,t);if("simulator"===e.kind){await eU(e);let t=await m("xcrun",["simctl","terminate",e.id,i],{allowFailure:!0});if(0!==t.exitCode){if(t.stderr.toLowerCase().includes("found nothing to terminate"))return;throw new p("COMMAND_FAILED",`xcrun exited with code ${t.exitCode}`,{cmd:"xcrun",args:["simctl","terminate",e.id,i],stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode})}return}await m("xcrun",["devicectl","device","process","terminate","--device",e.id,i])}async function eC(e,t){eB(e,"reinstall");let i=await eM(e,t);await eU(e);let r=await m("xcrun",["simctl","uninstall",e.id,i],{allowFailure:!0});if(0!==r.exitCode){let e=`${r.stdout}
6
- ${r.stderr}`.toLowerCase();if(!e.includes("not installed")&&!e.includes("not found")&&!e.includes("no such file"))throw new p("COMMAND_FAILED",`simctl uninstall failed for ${i}`,{stdout:r.stdout,stderr:r.stderr,exitCode:r.exitCode})}return{bundleId:i}}async function eT(e,t){eB(e,"reinstall"),await eU(e),await m("xcrun",["simctl","install",e.id,t])}async function eP(e,t,i){let{bundleId:r}=await eC(e,t);return await eT(e,i),{bundleId:r}}async function e$(e,t){if("simulator"===e.kind){await eU(e),await m("xcrun",["simctl","io",e.id,"screenshot",t]);return}await m("xcrun",["devicectl","device","screenshot","--device",e.id,t])}async function eF(e,t,i,r){eB(e,"settings"),await eU(e);let a=t.toLowerCase(),n=function(e){let t=e.toLowerCase();if("on"===t||"true"===t||"1"===t)return!0;if("off"===t||"false"===t||"0"===t)return!1;throw new p("INVALID_ARGS",`Invalid setting state: ${e}`)}(i);switch(a){case"wifi":return void await m("xcrun",["simctl","status_bar",e.id,"override","--wifiMode",n?"active":"failed"]);case"airplane":n?await m("xcrun",["simctl","status_bar",e.id,"override","--dataNetwork","hide","--wifiMode","failed","--wifiBars","0","--cellularMode","failed","--cellularBars","0","--operatorName",""]):await m("xcrun",["simctl","status_bar",e.id,"clear"]);return;case"location":if(!r)throw new p("INVALID_ARGS","location setting requires an active app in session");await m("xcrun",["simctl","privacy",e.id,n?"grant":"revoke","location",r]);return;default:throw new p("INVALID_ARGS",`Unsupported setting: ${t}`)}}function eB(e,t){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)}async function eV(e){let t=(await m("xcrun",["simctl","listapps",e.id],{allowFailure:!0})).stdout.trim();if(!t)return[];let i=null;if(t.startsWith("{"))try{i=JSON.parse(t)}catch{i=null}if(!i&&t.startsWith("{"))try{let e=await m("plutil",["-convert","json","-o","-","-"],{allowFailure:!0,stdin:t});0===e.exitCode&&e.stdout.trim().startsWith("{")&&(i=JSON.parse(e.stdout))}catch{i=null}return i?Object.entries(i).map(([e,t])=>({bundleId:e,name:t.CFBundleDisplayName??t.CFBundleName??e})):[]}async function eU(e){let t,i;if("simulator"!==e.kind||"Booted"===await eG(e.id))return;let r=S.fromTimeoutMs(eO);try{await D(async({deadline:r})=>{if(r?.isExpired())throw new p("COMMAND_FAILED","iOS simulator boot deadline exceeded",{timeoutMs:eO});let a=Math.max(1e3,r?.remainingMs()??eO);t=await m("xcrun",["simctl","boot",e.id],{allowFailure:!0,timeoutMs:a});let n=`${t.stdout}
7
- ${t.stderr}`.toLowerCase(),o=n.includes("already booted")||n.includes("current state: booted");if(0!==t.exitCode&&!o)throw new p("COMMAND_FAILED","simctl boot failed",{stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode});if(i=await m("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0,timeoutMs:a}),0!==i.exitCode)throw new p("COMMAND_FAILED","simctl bootstatus failed",{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode});let s=await eG(e.id);if("Booted"!==s)throw new p("COMMAND_FAILED","Simulator is still booting",{state:s})},{maxAttempts:3,baseDelayMs:500,maxDelayMs:2e3,jitter:.2,shouldRetry:e=>{let r=k({error:e,stdout:i?.stdout??t?.stdout,stderr:i?.stderr??t?.stderr,context:{platform:"ios",phase:"boot"}});return"IOS_BOOT_TIMEOUT"!==r&&"CI_RESOURCE_STARVATION_SUSPECTED"!==r}},{deadline:r,phase:"boot",classifyReason:e=>k({error:e,stdout:i?.stdout??t?.stdout,stderr:i?.stderr??t?.stderr,context:{platform:"ios",phase:"boot"}}),onEvent:e=>{e_&&process.stderr.write(`[agent-device][retry] ${JSON.stringify(e)}
8
- `)}})}catch(u){let a=t?.stdout,n=t?.stderr,o=t?.exitCode,s=i?.stdout,l=i?.stderr,c=i?.exitCode,d=k({error:u,stdout:s??a,stderr:l??n,context:{platform:"ios",phase:"boot"}});throw new p("COMMAND_FAILED","iOS simulator failed to boot",{platform:"ios",deviceId:e.id,timeoutMs:eO,elapsedMs:r.elapsedMs(),reason:d,hint:O(d),boot:t?{exitCode:o,stdout:a,stderr:n}:void 0,bootstatus:i?{exitCode:c,stdout:s,stderr:l}:void 0})}}async function eG(e){let t=await m("xcrun",["simctl","list","devices","-j"],{allowFailure:!0,timeoutMs:I});if(0!==t.exitCode)return null;try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices??{})){let i=t.find(t=>t.udid===e);if(i)return i.state}}catch{}return null}let ej=new Map,eq=eJ(process.env.AGENT_DEVICE_RUNNER_STARTUP_TIMEOUT_MS,12e4,5e3),eW=eJ(process.env.AGENT_DEVICE_RUNNER_COMMAND_TIMEOUT_MS,15e3,1e3);function eJ(e,t,i){if(!e)return t;let r=Number(e);return Number.isFinite(r)?Math.max(i,Math.floor(r)):t}async function eH(e,t,i={}){var r;return"snapshot"===(r=t.command)||"findText"===r||"listTappables"===r||"alert"===r?x(()=>ez(e,t,i),{shouldRetry:e4}):ez(e,t,i)}async function ez(e,t,i={}){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let r=await eQ(e,i),a=r.ready?eW:eq;return await eX(e,r,t,i.logPath,a)}catch(a){let r=a instanceof p?a:new p("COMMAND_FAILED",String(a));if("COMMAND_FAILED"===r.code&&"string"==typeof r.message&&r.message.includes("Runner did not accept connection")){await eY(e.id);let r=await eQ(e,i),a=await e5(r.device,r.port,t,i.logPath,eq);return await eK(a,r,i.logPath)}throw a}}async function eX(e,t,i,r,a){let n=await e5(e,t.port,i,r,a);return await eK(n,t,r)}async function eK(e,t,i){let r=await e.text(),a={};try{a=JSON.parse(r)}catch{throw new p("COMMAND_FAILED","Invalid runner response",{text:r})}if(!a.ok)throw new p("COMMAND_FAILED",a.error?.message??"Runner error",{runner:a,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:i});return t.ready=!0,a.data??{}}async function eY(e){let t=ej.get(e);if(t){try{await e5(t.device,t.port,{command:"shutdown"},void 0,15e3)}catch{await e0(t.child.pid,"SIGTERM")}try{await Promise.race([t.testPromise,new Promise(e=>setTimeout(e,1e4))])}catch{}await e0(t.child.pid,"SIGKILL"),e9(t.xctestrunPath),e9(t.jsonPath),ej.delete(e)}}async function eZ(e){await m("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0,timeoutMs:eq})}async function eQ(e,t){let i=ej.get(e.id);if(i)return i;await eZ(e.id);let r=await e1(e.id,t),a=await e7(),{xctestrunPath:n,jsonPath:o}=await e6(r,{AGENT_DEVICE_RUNNER_PORT:String(a)},`session-${e.id}-${a}`),{child:s,wait:l}=d("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-test-timeouts-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",n,"-destination",`platform=iOS Simulator,id=${e.id}`],{allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(a)}});s.stdout?.on("data",e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)}),s.stderr?.on("data",e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)});let c={device:e,deviceId:e.id,port:a,xctestrunPath:n,jsonPath:o,testPromise:l,child:s,ready:!1};return ej.set(e.id,c),c}async function e0(e,t){if(!e||e<=0)return;try{process.kill(e,t)}catch{}let i="SIGTERM"===t?"TERM":"KILL";try{await m("pkill",[`-${i}`,"-P",String(e)],{allowFailure:!0})}catch{}}async function e1(e,t){let i,r=n.join(f.homedir(),".agent-device","ios-runner"),a=n.join(r,"derived");if((i=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(i.toLowerCase()))try{u.rmSync(a,{recursive:!0,force:!0})}catch{}let s=e2(a);if(s)return s;let l=function(){let e=n.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=n.join(t,"package.json");if(u.existsSync(e))return t;t=n.dirname(t)}return e}(),d=n.join(l,"ios-runner","AgentDeviceRunner","AgentDeviceRunner.xcodeproj");if(!u.existsSync(d))throw new p("COMMAND_FAILED","iOS runner project not found",{projectPath:d});try{await o("xcodebuild",["build-for-testing","-project",d,"-scheme","AgentDeviceRunner","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-destination",`platform=iOS Simulator,id=${e}`,"-derivedDataPath",a],{onStdoutChunk:e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)}})}catch(i){let e=i instanceof p?i:new p("COMMAND_FAILED",String(i));throw new p("COMMAND_FAILED","xcodebuild build-for-testing failed",{error:e.message,details:e.details,logPath:t.logPath})}let m=e2(a);if(!m)throw new p("COMMAND_FAILED","Failed to locate .xctestrun after build");return m}function e2(e){if(!u.existsSync(e))return null;let t=[],i=[e];for(;i.length>0;){let e=i.pop();for(let r of u.readdirSync(e,{withFileTypes:!0})){let a=n.join(e,r.name);if(r.isDirectory()){i.push(a);continue}if(r.isFile()&&r.name.endsWith(".xctestrun"))try{let e=u.statSync(a);t.push({path:a,mtimeMs:e.mtimeMs})}catch{}}}return 0===t.length?null:(t.sort((e,t)=>t.mtimeMs-e.mtimeMs),t[0]?.path??null)}function e3(e,t,i,r){t&&u.appendFileSync(t,e),i&&u.appendFileSync(i,e),r&&process.stderr.write(e)}function e4(e){if(!(e instanceof p)||"COMMAND_FAILED"!==e.code)return!1;let t=`${e.message??""}`.toLowerCase();return!!(t.includes("runner did not accept connection")||t.includes("fetch failed")||t.includes("econnrefused")||t.includes("socket hang up"))}async function e5(e,t,i,r,a=eq){let n=Date.now(),o=null;for(;Date.now()-n<a;)try{return await fetch(`http://127.0.0.1:${t}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}catch(e){o=e,await new Promise(e=>setTimeout(e,100))}if("simulator"===e.kind){let r=await e8(e.id,t,i);return new Response(r.body,{status:r.status})}throw new p("COMMAND_FAILED","Runner did not accept connection",{port:t,logPath:r,lastError:o?String(o):void 0,reason:k({error:o,message:"Runner did not accept connection",context:{platform:"ios",phase:"connect"}}),hint:O("IOS_RUNNER_CONNECT_TIMEOUT")})}async function e8(e,t,i){let r=JSON.stringify(i),a=await m("xcrun",["simctl","spawn",e,"/usr/bin/curl","-s","-X","POST","-H","Content-Type: application/json","--data",r,`http://127.0.0.1:${t}/command`],{allowFailure:!0}),n=a.stdout;if(0!==a.exitCode){let e=k({message:"Runner did not accept connection (simctl spawn)",stdout:a.stdout,stderr:a.stderr,context:{platform:"ios",phase:"connect"}});throw new p("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:a.stdout,stderr:a.stderr,exitCode:a.exitCode,reason:e,hint:O(e)})}return{status:200,body:n}}async function e7(){return await new Promise((e,t)=>{let i=h.createServer();i.listen(0,"127.0.0.1",()=>{let r=i.address();i.close(),"object"==typeof r&&r?.port?e(r.port):t(new p("COMMAND_FAILED","Failed to allocate port"))}),i.on("error",t)})}async function e6(e,t,i){let r,a=n.dirname(e),o=i.replace(/[^a-zA-Z0-9._-]/g,"_"),s=n.join(a,`AgentDeviceRunner.env.${o}.json`),l=n.join(a,`AgentDeviceRunner.env.${o}.xctestrun`),c=await m("plutil",["-convert","json","-o","-",e],{allowFailure:!0});if(0!==c.exitCode||!c.stdout.trim())throw new p("COMMAND_FAILED","Failed to read xctestrun plist",{xctestrunPath:e,stderr:c.stderr});try{r=JSON.parse(c.stdout)}catch(t){throw new p("COMMAND_FAILED","Failed to parse xctestrun JSON",{xctestrunPath:e,error:String(t)})}let d=e=>{e.EnvironmentVariables={...e.EnvironmentVariables??{},...t},e.UITestEnvironmentVariables={...e.UITestEnvironmentVariables??{},...t},e.UITargetAppEnvironmentVariables={...e.UITargetAppEnvironmentVariables??{},...t},e.TestingEnvironmentVariables={...e.TestingEnvironmentVariables??{},...t}},f=r.TestConfigurations;if(Array.isArray(f))for(let e of f){if(!e||"object"!=typeof e)continue;let t=e.TestTargets;if(Array.isArray(t))for(let e of t)e&&"object"==typeof e&&d(e)}for(let[e,t]of Object.entries(r))t&&"object"==typeof t&&t.TestBundlePath&&(d(t),r[e]=t);u.writeFileSync(s,JSON.stringify(r,null,2));let h=await m("plutil",["-convert","xml1","-o",l,s],{allowFailure:!0});if(0!==h.exitCode)throw new p("COMMAND_FAILED","Failed to write xctestrun plist",{tmpXctestrunPath:l,stderr:h.stderr});return{xctestrunPath:l,jsonPath:s}}function e9(e){try{u.existsSync(e)&&u.unlinkSync(e)}catch{}}async function te(e,t={}){let i,r;if("ios"!==e.platform||"simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","AX snapshot is only supported on iOS simulators");let a=await tt(),n=await x(async()=>{var e,i;let r,n,o,s=await m(a,[],{allowFailure:!0});if(t.traceLogPath&&(e=t.traceLogPath,r=((i=s).stdout??"").toString(),n=(i.stderr??"").toString(),o=`
1
+ let e,t;import i from"node:crypto";import{isCancel as r,select as a}from"@clack/prompts";import{node_path as n,runCmdStreaming as o,promises as s,asAppError as l,fileURLToPath as c,runCmdBackground as u,node_fs as d,node_os as f,errors_AppError as p,runCmd as m,node_net as h,whichCmd as w,readVersion as g}from"./274.js";async function v(e,t){let i=e,n=e=>e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim();if(t.platform&&(i=i.filter(e=>e.platform===t.platform)),t.udid){let e=i.find(e=>e.id===t.udid&&"ios"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No iOS device with UDID ${t.udid}`);return e}if(t.serial){let e=i.find(e=>e.id===t.serial&&"android"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No Android device with serial ${t.serial}`);return e}if(t.deviceName){let e=n(t.deviceName),r=i.find(t=>n(t.name)===e);if(!r)throw new p("DEVICE_NOT_FOUND",`No device named ${t.deviceName}`);return r}if(1===i.length)return i[0];if(0===i.length)throw new p("DEVICE_NOT_FOUND","No devices found",{selector:t});let o=i.filter(e=>e.booted);if(1===o.length)return o[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await a({message:"Multiple devices available. Choose a device to continue:",options:(o.length>0?o:i).map(e=>({label:`${e.name} (${e.platform}${e.kind?`, ${e.kind}`:""}${e.booted?", booted":""})`,value:e.id}))});if(r(e))throw new p("INVALID_ARGS","Device selection cancelled");if(e){let t=i.find(t=>t.id===e);if(t)return t}}return o[0]??i[0]}function y(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function A(e){return["1","true","yes","on"].includes((e??"").toLowerCase())}let I=2e4,b=12e4,N=1e4;class S{static fromTimeoutMs(e,t=Date.now()){return new S(t,e)}remainingMs(e=Date.now()){return Math.max(0,this.expiresAtMs-e)}elapsedMs(e=Date.now()){return Math.max(0,e-this.startedAtMs)}isExpired(e=Date.now()){return 0>=this.remainingMs(e)}constructor(e,t){y(this,"startedAtMs",void 0),y(this,"expiresAtMs",void 0),this.startedAtMs=e,this.expiresAtMs=e+Math.max(0,t)}}async function x(e,t={},i={}){let r,a={maxAttempts:t.maxAttempts??3,baseDelayMs:t.baseDelayMs??200,maxDelayMs:t.maxDelayMs??2e3,jitter:t.jitter??.2,shouldRetry:t.shouldRetry};for(let t=1;t<=a.maxAttempts&&(!i.deadline?.isExpired()||!(t>1));t+=1)try{let r=await e({attempt:t,maxAttempts:a.maxAttempts,deadline:i.deadline});return i.onEvent?.({phase:i.phase,event:"succeeded",attempt:t,maxAttempts:a.maxAttempts,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs()}),r}catch(s){r=s;let e=i.classifyReason?.(s);if(i.onEvent?.({phase:i.phase,event:"attempt_failed",attempt:t,maxAttempts:a.maxAttempts,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs(),reason:e}),t>=a.maxAttempts||a.shouldRetry&&!a.shouldRetry(s,t))break;let n=function(e,t,i,r){let a=Math.min(t,e*2**(r-1));return Math.max(0,a+a*i*(2*Math.random()-1))}(a.baseDelayMs,a.maxDelayMs,a.jitter,t),o=i.deadline?Math.min(n,i.deadline.remainingMs()):n;if(o<=0)break;i.onEvent?.({phase:i.phase,event:"retry_scheduled",attempt:t,maxAttempts:a.maxAttempts,delayMs:o,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs(),reason:e}),await function(e){return new Promise(t=>setTimeout(t,e))}(o)}if(i.onEvent?.({phase:i.phase,event:"exhausted",attempt:a.maxAttempts,maxAttempts:a.maxAttempts,elapsedMs:i.deadline?.elapsedMs(),remainingMs:i.deadline?.remainingMs(),reason:i.classifyReason?.(r)}),r)throw r;throw new p("COMMAND_FAILED","retry failed")}async function k(e,t={}){return x(()=>e(),{maxAttempts:t.attempts,baseDelayMs:t.baseDelayMs,maxDelayMs:t.maxDelayMs,jitter:t.jitter,shouldRetry:t.shouldRetry})}function D(e){let t=e.error?l(e.error):null,i=e.context?.platform,r=e.context?.phase;if(t?.code==="TOOL_MISSING")return"android"===i?"ADB_TRANSPORT_UNAVAILABLE":"IOS_TOOL_MISSING";let a=t?.details??{},n="string"==typeof a.message?a.message:void 0,o="string"==typeof a.stdout?a.stdout:void 0,s="string"==typeof a.stderr?a.stderr:void 0,c=a.boot&&"object"==typeof a.boot?a.boot:null,u=a.bootstatus&&"object"==typeof a.bootstatus?a.bootstatus:null,d=[e.message,t?.message,e.stdout,e.stderr,n,o,s,"string"==typeof c?.stdout?c.stdout:void 0,"string"==typeof c?.stderr?c.stderr:void 0,"string"==typeof u?.stdout?u.stdout:void 0,"string"==typeof u?.stderr?u.stderr:void 0].filter(Boolean).join("\n").toLowerCase();return"ios"===i&&(d.includes("runner did not accept connection")||"connect"===r&&(d.includes("timed out")||d.includes("timeout")||d.includes("econnrefused")||d.includes("connection refused")||d.includes("fetch failed")||d.includes("socket hang up")))?"IOS_RUNNER_CONNECT_TIMEOUT":"ios"===i&&"boot"===r&&(d.includes("timed out")||d.includes("timeout"))?"IOS_BOOT_TIMEOUT":"android"===i&&"boot"===r&&(d.includes("timed out")||d.includes("timeout"))?"ANDROID_BOOT_TIMEOUT":d.includes("resource temporarily unavailable")||d.includes("killed: 9")||d.includes("cannot allocate memory")||d.includes("system is low on memory")?"CI_RESOURCE_STARVATION_SUSPECTED":"android"===i&&(d.includes("device not found")||d.includes("no devices")||d.includes("device offline")||d.includes("offline")||d.includes("unauthorized")||d.includes("not authorized")||d.includes("unable to locate device")||d.includes("invalid device"))?"ADB_TRANSPORT_UNAVAILABLE":t?.code==="COMMAND_FAILED"||d.length>0?"BOOT_COMMAND_FAILED":"UNKNOWN"}function O(e){switch(e){case"IOS_BOOT_TIMEOUT":return"Retry simulator boot and inspect simctl bootstatus logs; in CI consider increasing AGENT_DEVICE_IOS_BOOT_TIMEOUT_MS.";case"IOS_RUNNER_CONNECT_TIMEOUT":return"Retry runner startup, inspect xcodebuild logs, and verify simulator responsiveness before command execution.";case"ANDROID_BOOT_TIMEOUT":return"Retry emulator startup and verify sys.boot_completed reaches 1; consider increasing startup budget in CI.";case"ADB_TRANSPORT_UNAVAILABLE":return"Check adb server/device transport (adb devices -l), restart adb, and ensure the target device is online and authorized.";case"CI_RESOURCE_STARVATION_SUSPECTED":return"CI machine may be resource constrained; reduce parallel jobs or use a larger runner.";case"IOS_TOOL_MISSING":return"Xcode command-line tools are missing or not in PATH; run xcode-select --install and verify xcrun works.";case"BOOT_COMMAND_FAILED":return"Inspect command stderr/stdout for the failing boot phase and retry after environment validation.";default:return"Retry once and inspect verbose logs for the failing phase."}}let _=A(process.env.AGENT_DEVICE_RETRY_LOGS);function M(e){return e.startsWith("emulator-")}async function L(e,t=N){return m("adb",["-s",e,"shell","getprop","sys.boot_completed"],{allowFailure:!0,timeoutMs:t})}async function R(e,t){let i=t.replace(/_/g," ").trim();if(!M(e))return i||e;let r=await m("adb",["-s",e,"emu","avd","name"],{allowFailure:!0,timeoutMs:N}),a=r.stdout.trim();return 0===r.exitCode&&a?a.replace(/_/g," "):i||e}async function E(){if(!await w("adb"))throw new p("TOOL_MISSING","adb not found in PATH");let e=(await m("adb",["devices","-l"],{timeoutMs:N})).stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("List of devices")).map(e=>e.split(/\s+/)).filter(e=>"device"===e[1]).map(e=>({serial:e[0],rawModel:(e.find(e=>e.startsWith("model:"))??"").replace("model:","")}));return await Promise.all(e.map(async({serial:e,rawModel:t})=>{let[i,r]=await Promise.all([R(e,t),C(e)]);return{platform:"android",id:e,name:i,kind:M(e)?"emulator":"device",booted:r}}))}async function C(e){try{let t=await L(e);return"1"===t.stdout.trim()}catch{return!1}}async function T(e,t=6e4){let i,r=S.fromTimeoutMs(t),a=Math.max(1,Math.ceil(t/1e3)),n=!1;try{await x(async({deadline:a})=>{if(a?.isExpired())throw n=!0,new p("COMMAND_FAILED","Android boot deadline exceeded",{serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),message:"timeout"});let o=Math.max(1e3,a?.remainingMs()??t),s=await L(e,Math.min(o,N));if(i=s,"1"!==s.stdout.trim())throw new p("COMMAND_FAILED","Android device is still booting",{serial:e,stdout:s.stdout,stderr:s.stderr,exitCode:s.exitCode})},{maxAttempts:a,baseDelayMs:1e3,maxDelayMs:1e3,jitter:0,shouldRetry:e=>{let t=D({error:e,stdout:i?.stdout,stderr:i?.stderr,context:{platform:"android",phase:"boot"}});return"ADB_TRANSPORT_UNAVAILABLE"!==t&&"ANDROID_BOOT_TIMEOUT"!==t}},{deadline:r,phase:"boot",classifyReason:e=>D({error:e,stdout:i?.stdout,stderr:i?.stderr,context:{platform:"android",phase:"boot"}}),onEvent:e=>{_&&process.stderr.write(`[agent-device][retry] ${JSON.stringify(e)}
2
+ `)}})}catch(f){let a=l(f),o=i?.stdout,s=i?.stderr,c=i?.exitCode,u=D({error:f,stdout:o,stderr:s,context:{platform:"android",phase:"boot"}});"BOOT_COMMAND_FAILED"===u&&"Android device is still booting"===a.message&&(u="ANDROID_BOOT_TIMEOUT");let d={serial:e,timeoutMs:t,elapsedMs:r.elapsedMs(),reason:u,hint:O(u),stdout:o,stderr:s,exitCode:c};if(n||"ANDROID_BOOT_TIMEOUT"===u)throw new p("COMMAND_FAILED","Android device did not finish booting in time",d);if("TOOL_MISSING"===a.code)throw new p("TOOL_MISSING",a.message,{...d,...a.details??{}});if("ADB_TRANSPORT_UNAVAILABLE"===u)throw new p("COMMAND_FAILED",a.message,{...d,...a.details??{}});throw new p(a.code,a.message,{...d,...a.details??{}},a.cause)}}function P(e){let t=$(e),i=e=>{let i=F(t,e);if(null!==i)return"true"===i};return{text:F(t,"text"),desc:F(t,"content-desc"),resourceId:F(t,"resource-id"),className:F(t,"class"),bounds:F(t,"bounds"),clickable:i("clickable"),enabled:i("enabled"),focusable:i("focusable"),focused:i("focused")}}function $(e){let t=new Map,i=e.indexOf(" "),r=e.lastIndexOf(">");if(i<0||r<=i)return t;let a=/([^\s=/>]+)\s*=\s*(["'])([\s\S]*?)\2/y,n=i;for(;n<r;){for(;n<r;){let t=e[n];if(" "!==t&&"\n"!==t&&"\r"!==t&&" "!==t)break;n+=1}if(n>=r)break;let i=e[n];if("/"===i||">"===i)break;a.lastIndex=n;let o=a.exec(e);if(!o)break;t.set(o[1],o[3]),n=a.lastIndex}return t}function F(e,t){return e.get(t)??null}function B(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let i=Number(t[1]),r=Number(t[2]);return{x:i,y:r,width:Math.max(0,Number(t[3])-i),height:Math.max(0,Number(t[4])-r)}}function V(e){return e?e.toLowerCase():""}function U(e){let t=e.trim();return!!t&&/^[\w.]+:id\/[\w.-]+$/i.test(t)}let G={settings:{type:"intent",value:"android.settings.SETTINGS"}};function j(e,t){return["-s",e.id,...t]}async function q(e,t){let i=t.trim();if(i.includes("."))return{type:"package",value:i};let r=G[i.toLowerCase()];if(r)return r;let a=(await m("adb",j(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(i.toLowerCase()));if(1===a.length)return{type:"package",value:a[0]};if(a.length>1)throw new p("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:a});throw new p("APP_NOT_INSTALLED",`No package found matching "${t}"`)}async function W(e,t="launchable"){if("launchable"===t){let t=await m("adb",j(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c","android.intent.category.LAUNCHER"]),{allowFailure:!0});if(0===t.exitCode&&t.stdout.trim().length>0){let e=new Set;for(let i of t.stdout.split("\n")){let t=i.trim();if(!t)continue;let r=t.split(/\s+/)[0],a=r.includes("/")?r.split("/")[0]:r;a&&e.add(a)}if(e.size>0)return Array.from(e)}}return(await m("adb",j(e,"user-installed"===t?["shell","pm","list","packages","-3"]:["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function J(e,t="launchable"){let i=await W(e,t),r=new Set("launchable"===t?i:await W(e,"launchable"));return i.map(e=>({package:e,launchable:r.has(e)}))}async function H(e){let t=await z(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let i=await z(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return i||{}}async function z(e,t){for(let i of t){let t=function(e){for(let t of[/mCurrentFocus=Window\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mFocusedApp=AppWindowToken\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/,/ResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/]){let i=t.exec(e);if(i)return{package:i[1],activity:i[2]}}return null}((await m("adb",j(e,i),{allowFailure:!0})).stdout??"");if(t)return t}return null}async function X(e,t,i){e.booted||await T(e.id);let r=await q(e,t);if("intent"===r.type){if(i)throw new p("INVALID_ARGS","Activity override requires a package name, not an intent");await m("adb",j(e,["shell","am","start","-a",r.value]));return}if(i){let t=i.includes("/")?i:`${r.value}/${i.startsWith(".")?i:`.${i}`}`;await m("adb",j(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-n",t]));return}try{await m("adb",j(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-p",r.value]));return}catch(i){let t=await K(e,r.value);if(!t)throw i;await m("adb",j(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-n",t]))}}async function K(e,t){let i=await m("adb",j(e,["shell","cmd","package","resolve-activity","--brief",t]),{allowFailure:!0});return 0!==i.exitCode?null:function(e){let t=e.split("\n").map(e=>e.trim()).filter(Boolean);for(let e=t.length-1;e>=0;e-=1){let i=t[e];if(i.includes("/"))return i.split(/\s+/)[0]}return null}(i.stdout)}async function Y(e){e.booted||await T(e.id)}async function Z(e,t){if("settings"===t.trim().toLowerCase())return void await m("adb",j(e,["shell","am","force-stop","com.android.settings"]));let i=await q(e,t);if("intent"===i.type)throw new p("INVALID_ARGS","Close requires a package name, not an intent");await m("adb",j(e,["shell","am","force-stop",i.value]))}async function Q(e,t){let i=await q(e,t);if("intent"===i.type)throw new p("INVALID_ARGS","reinstall requires a package name, not an intent");let r=await m("adb",j(e,["uninstall",i.value]),{allowFailure:!0});if(0!==r.exitCode){let e=`${r.stdout}
3
+ ${r.stderr}`.toLowerCase();if(!e.includes("unknown package")&&!e.includes("not installed"))throw new p("COMMAND_FAILED",`adb uninstall failed for ${i.value}`,{stdout:r.stdout,stderr:r.stderr,exitCode:r.exitCode})}return{package:i.value}}async function ee(e,t){await m("adb",j(e,["install",t]))}async function et(e,t,i){e.booted||await T(e.id);let{package:r}=await Q(e,t);return await ee(e,i),{package:r}}async function ei(e,t,i){await m("adb",j(e,["shell","input","tap",String(t),String(i)]))}async function er(e){await m("adb",j(e,["shell","input","keyevent","4"]))}async function ea(e){await m("adb",j(e,["shell","input","keyevent","3"]))}async function en(e){await m("adb",j(e,["shell","input","keyevent","187"]))}async function eo(e,t,i,r=800){await m("adb",j(e,["shell","input","swipe",String(t),String(i),String(t),String(i),String(r)]))}async function es(e,t){let i=t.replace(/ /g,"%s");await m("adb",j(e,["shell","input","text",i]))}async function el(e,t,i){await ei(e,t,i)}async function ec(e,t,i,r){await el(e,t,i);let a=null;for(let s of[{clearPadding:12,minClear:8,maxClear:48,chunkSize:4,delayMs:0},{clearPadding:24,minClear:16,maxClear:96,chunkSize:1,delayMs:15}]){var n,o;let l=(n=r.length+s.clearPadding,o=s.minClear,Math.max(o,Math.min(s.maxClear,n)));if(await eb(e,l),await eI(e,r,s.chunkSize,s.delayMs),(a=await eN(e,t,i))===r)return}throw new p("COMMAND_FAILED","Android fill verification failed",{expected:r,actual:a??null})}async function eu(e,t,i=.6){let{width:r,height:a}=await ew(e),n=Math.floor(r*i),o=Math.floor(a*i),s=Math.floor(r/2),l=Math.floor(a/2),c=s,u=l,d=s,f=l;switch(t){case"up":u=l-Math.floor(o/2),f=l+Math.floor(o/2);break;case"down":u=l+Math.floor(o/2),f=l-Math.floor(o/2);break;case"left":c=s-Math.floor(n/2),d=s+Math.floor(n/2);break;case"right":c=s+Math.floor(n/2),d=s-Math.floor(n/2);break;default:throw new p("INVALID_ARGS",`Unknown direction: ${t}`)}await m("adb",j(e,["shell","input","swipe",String(c),String(u),String(d),String(f),"300"]))}async function ed(e,t){for(let i=0;i<8;i+=1){let i="";try{i=await eg(e)}catch(t){let e=t instanceof Error?t.message:String(t);throw new p("UNSUPPORTED_OPERATION",`uiautomator dump failed: ${e}`)}if(function(e,t){let i=t.toLowerCase(),r=/<node[^>]+>/g,a=r.exec(e);for(;a;){let t=$(a[0]),n=(F(t,"text")??"").toLowerCase(),o=(F(t,"content-desc")??"").toLowerCase();if(n.includes(i)||o.includes(i)){let e=B(F(t,"bounds"));if(e)return{x:Math.floor(e.x+e.width/2),y:Math.floor(e.y+e.height/2)};return{x:0,y:0}}a=r.exec(e)}return null}(i,t))return;await eu(e,"down",.5)}throw new p("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function ef(e,t){let i=await m("adb",j(e,["exec-out","screencap","-p"]),{binaryStdout:!0});if(!i.stdoutBuffer)throw new p("COMMAND_FAILED","Failed to capture screenshot");await s.writeFile(t,i.stdoutBuffer)}async function ep(e,t,i){let r=t.toLowerCase(),a=function(e){let t=e.toLowerCase();if("on"===t||"true"===t||"1"===t)return!0;if("off"===t||"false"===t||"0"===t)return!1;throw new p("INVALID_ARGS",`Invalid setting state: ${e}`)}(i);switch(r){case"wifi":return void await m("adb",j(e,["shell","svc","wifi",a?"enable":"disable"]));case"airplane":await m("adb",j(e,["shell","settings","put","global","airplane_mode_on",a?"1":"0"])),await m("adb",j(e,["shell","am","broadcast","-a","android.intent.action.AIRPLANE_MODE","--ez","state",a?"true":"false"]));return;case"location":return void await m("adb",j(e,["shell","settings","put","secure","location_mode",a?"3":"0"]));default:throw new p("INVALID_ARGS",`Unsupported setting: ${t}`)}}async function em(e,t={}){return function(e,t,i){let r=function(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},i=[t],r=/<node\b[^>]*>|<\/node>/g,a=r.exec(e);for(;a;){let t=a[0];if(t.startsWith("</node")){i.length>1&&i.pop(),a=r.exec(e);continue}let n=P(t),o=B(n.bounds),s=i[i.length-1],l={type:n.className,label:n.text||n.desc,value:n.text,identifier:n.resourceId,rect:o,enabled:n.enabled,hittable:n.clickable??n.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||i.push(l),a=r.exec(e)}return t}(e),a=[],n=!1,o=i.depth??1/0,s=i.scope?function(e,t){let i=t.toLowerCase(),r=[...e.children];for(;r.length>0;){let e=r.shift(),t=e.label?.toLowerCase()??"",a=e.value?.toLowerCase()??"",n=e.identifier?.toLowerCase()??"";if(t.includes(i)||a.includes(i)||n.includes(i))return e;r.push(...e.children)}return null}(r,i.scope):null,l=s?[s]:r.children,c=new Map,u=e=>{let t=c.get(e);if(void 0!==t)return t;for(let t of e.children)if(t.hittable||u(t))return c.set(e,!0),!0;return c.set(e,!1),!1},d=(e,t,r,s=!1,l=!1)=>{var c,f,p,m,h,w;let g,v,y,A,I,b,N,S;if(a.length>=800){n=!0;return}if(t>o)return;let x=!!i.raw||(c=e,f=i,p=s,m=u(e),h=l,v=V(c.type),y=!!(c.label&&c.label.trim().length>0),A=!!(c.identifier&&c.identifier.trim().length>0),I=y&&!U(c.label??""),b=A&&!U(c.identifier??""),N=(g=(w=v).split(".").pop()??w).includes("layout")||"viewgroup"===g||"view"===g,S="imageview"===v||"imagebutton"===v,f.interactiveOnly?!!c.hittable||!!(I||b)&&!S&&(!N||!!h)&&(p||m||h):f.compact?I||b||!!c.hittable:!N&&!S||!!c.hittable||!!I||!!b&&!!m||m),k=r;x&&(k=a.length,a.push({index:k,type:e.type??void 0,label:e.label??void 0,value:e.value??void 0,identifier:e.identifier??void 0,rect:e.rect,enabled:e.enabled,hittable:e.hittable,depth:t,parentIndex:r}));let D=s||!!e.hittable,O=l||function(e){if(!e)return!1;let t=V(e);return t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")}(e.type);for(let i of e.children)if(d(i,t+1,k,D,O),n)return};for(let e of l)if(d(e,0,void 0,!1,!1),n)break;return n?{nodes:a,truncated:n}:{nodes:a}}(await eg(e),0,t)}async function eh(){if(!await w("adb"))throw new p("TOOL_MISSING","adb not found in PATH")}async function ew(e){let t=(await m("adb",j(e,["shell","wm","size"]))).stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(!t)throw new p("COMMAND_FAILED","Unable to read screen size");return{width:Number(t[1]),height:Number(t[2])}}async function eg(e){return k(()=>ev(e),{shouldRetry:eA})}async function ev(e){var t,i,r;let a,n,o=await m("adb",j(e,["exec-out","uiautomator","dump","/dev/tty"]),{allowFailure:!0});if(0===o.exitCode){let e=ey(o.stdout,o.stderr);if(e)return e}let s="/sdcard/window_dump.xml",l=await m("adb",j(e,["shell","uiautomator","dump",s])),c=(t=s,i=l.stdout,r=l.stderr,a=`${i}
4
+ ${r}`,n=/dumped to:\s*(\S+)/i.exec(a),n?.[1]??t),u=await m("adb",j(e,["shell","cat",c])),d=ey(u.stdout,u.stderr);if(!d)throw new p("COMMAND_FAILED","uiautomator dump did not return XML",{stdout:u.stdout,stderr:u.stderr});return d}function ey(e,t){let i=`${e}
5
+ ${t}`,r=i.indexOf("<?xml"),a=r>=0?r:i.indexOf("<hierarchy");if(a<0)return null;let n=i.lastIndexOf("</hierarchy>");if(n<0||n<a)return null;let o=i.slice(a,n+12).trim();return o.length>0?o:null}function eA(e){if(!(e instanceof p)||"COMMAND_FAILED"!==e.code)return!1;let t=`${e.details?.stderr??""}`.toLowerCase();return!!(t.includes("device offline")||t.includes("device not found")||t.includes("transport error")||t.includes("connection reset")||t.includes("broken pipe")||t.includes("timed out")||t.includes("no such file or directory"))}async function eI(e,t,i,r){let a=Math.max(1,Math.floor(i));for(let i=0;i<t.length;i+=a){let n=t.slice(i,i+a);await es(e,n),r>0&&i+a<t.length&&await ex(r)}}async function eb(e,t){let i=Math.max(0,t);await m("adb",j(e,["shell","input","keyevent","KEYCODE_MOVE_END"]),{allowFailure:!0});for(let t=0;t<i;t+=24){let r=Math.min(24,i-t);await m("adb",j(e,["shell","input","keyevent",...Array(r).fill("KEYCODE_DEL")]),{allowFailure:!0})}}async function eN(e,t,i){let r,a=await eg(e),n=/<node\b[^>]*>/g,o=null,s=null,l=null;for(;null!==(r=n.exec(a));){let e=P(r[0]),a=B(e.bounds);if(!a)continue;let n=e.className??"",c=(e.text??"").replace(/&quot;/g,'"').replace(/&apos;/g,"'").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&"),u=e.focused??!1;if(!c)continue;let d=Math.max(1,a.width*a.height),f=t>=a.x&&t<=a.x+a.width&&i>=a.y&&i<=a.y+a.height;if(u&&eS(n)){(!o||d<=o.area)&&(o={text:c,area:d});continue}if(f&&eS(n)){(!s||d<=s.area)&&(s={text:c,area:d});continue}f&&(!l||d<=l.area)&&(l={text:c,area:d})}return o?.text??s?.text??l?.text??null}function eS(e){let t=e.toLowerCase();return t.includes("edittext")||t.includes("textfield")}async function ex(e){await new Promise(t=>setTimeout(t,e))}async function ek(){if("darwin"!==process.platform)throw new p("UNSUPPORTED_PLATFORM","iOS tools are only available on macOS");if(!await w("xcrun"))throw new p("TOOL_MISSING","xcrun not found in PATH");let e=[],t=await m("xcrun",["simctl","list","devices","-j"]);try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices))for(let i of t)i.isAvailable&&e.push({platform:"ios",id:i.udid,name:i.name,kind:"simulator",booted:"Booted"===i.state})}catch(e){throw new p("COMMAND_FAILED","Failed to parse simctl devices JSON",void 0,e)}if(await w("xcrun"))try{let t=await m("xcrun",["devicectl","list","devices","--json"]);for(let i of JSON.parse(t.stdout).devices??[])i.platform?.toLowerCase().includes("ios")&&e.push({platform:"ios",id:i.identifier,name:i.name,kind:"device",booted:!0})}catch{}return e}let eD={settings:"com.apple.Preferences"},eO=function(e,t,i){if(!e)return t;let r=Number(e);return Number.isFinite(r)?Math.max(5e3,Math.floor(r)):t}(process.env.AGENT_DEVICE_IOS_BOOT_TIMEOUT_MS,b,5e3),e_=A(process.env.AGENT_DEVICE_RETRY_LOGS);async function eM(e,t){let i=t.trim();if(i.includes("."))return i;let r=eD[i.toLowerCase()];if(r)return r;if("simulator"===e.kind){let r=(await eV(e)).filter(e=>e.name.toLowerCase()===i.toLowerCase());if(1===r.length)return r[0].bundleId;if(r.length>1)throw new p("INVALID_ARGS",`Multiple apps matched "${t}"`,{matches:r})}throw new p("APP_NOT_INSTALLED",`No app found matching "${t}"`)}async function eL(e,t){let i=await eM(e,t);if("simulator"===e.kind){await eU(e),await m("open",["-a","Simulator"],{allowFailure:!0}),await m("xcrun",["simctl","launch",e.id,i]);return}await m("xcrun",["devicectl","device","process","launch","--device",e.id,i])}async function eR(e){"simulator"!==e.kind||"Booted"!==await eG(e.id)&&(await eU(e),await m("open",["-a","Simulator"],{allowFailure:!0}))}async function eE(e,t){let i=await eM(e,t);if("simulator"===e.kind){await eU(e);let t=await m("xcrun",["simctl","terminate",e.id,i],{allowFailure:!0});if(0!==t.exitCode){if(t.stderr.toLowerCase().includes("found nothing to terminate"))return;throw new p("COMMAND_FAILED",`xcrun exited with code ${t.exitCode}`,{cmd:"xcrun",args:["simctl","terminate",e.id,i],stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode})}return}await m("xcrun",["devicectl","device","process","terminate","--device",e.id,i])}async function eC(e,t){eB(e,"reinstall");let i=await eM(e,t);await eU(e);let r=await m("xcrun",["simctl","uninstall",e.id,i],{allowFailure:!0});if(0!==r.exitCode){let e=`${r.stdout}
6
+ ${r.stderr}`.toLowerCase();if(!e.includes("not installed")&&!e.includes("not found")&&!e.includes("no such file"))throw new p("COMMAND_FAILED",`simctl uninstall failed for ${i}`,{stdout:r.stdout,stderr:r.stderr,exitCode:r.exitCode})}return{bundleId:i}}async function eT(e,t){eB(e,"reinstall"),await eU(e),await m("xcrun",["simctl","install",e.id,t])}async function eP(e,t,i){let{bundleId:r}=await eC(e,t);return await eT(e,i),{bundleId:r}}async function e$(e,t){if("simulator"===e.kind){await eU(e),await m("xcrun",["simctl","io",e.id,"screenshot",t]);return}await m("xcrun",["devicectl","device","screenshot","--device",e.id,t])}async function eF(e,t,i,r){eB(e,"settings"),await eU(e);let a=t.toLowerCase(),n=function(e){let t=e.toLowerCase();if("on"===t||"true"===t||"1"===t)return!0;if("off"===t||"false"===t||"0"===t)return!1;throw new p("INVALID_ARGS",`Invalid setting state: ${e}`)}(i);switch(a){case"wifi":return void await m("xcrun",["simctl","status_bar",e.id,"override","--wifiMode",n?"active":"failed"]);case"airplane":n?await m("xcrun",["simctl","status_bar",e.id,"override","--dataNetwork","hide","--wifiMode","failed","--wifiBars","0","--cellularMode","failed","--cellularBars","0","--operatorName",""]):await m("xcrun",["simctl","status_bar",e.id,"clear"]);return;case"location":if(!r)throw new p("INVALID_ARGS","location setting requires an active app in session");await m("xcrun",["simctl","privacy",e.id,n?"grant":"revoke","location",r]);return;default:throw new p("INVALID_ARGS",`Unsupported setting: ${t}`)}}function eB(e,t){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)}async function eV(e){let t=(await m("xcrun",["simctl","listapps",e.id],{allowFailure:!0})).stdout.trim();if(!t)return[];let i=null;if(t.startsWith("{"))try{i=JSON.parse(t)}catch{i=null}if(!i&&t.startsWith("{"))try{let e=await m("plutil",["-convert","json","-o","-","-"],{allowFailure:!0,stdin:t});0===e.exitCode&&e.stdout.trim().startsWith("{")&&(i=JSON.parse(e.stdout))}catch{i=null}return i?Object.entries(i).map(([e,t])=>({bundleId:e,name:t.CFBundleDisplayName??t.CFBundleName??e})):[]}async function eU(e){let t,i;if("simulator"!==e.kind||"Booted"===await eG(e.id))return;let r=S.fromTimeoutMs(eO);try{await x(async({deadline:r})=>{if(r?.isExpired())throw new p("COMMAND_FAILED","iOS simulator boot deadline exceeded",{timeoutMs:eO});let a=Math.max(1e3,r?.remainingMs()??eO);t=await m("xcrun",["simctl","boot",e.id],{allowFailure:!0,timeoutMs:a});let n=`${t.stdout}
7
+ ${t.stderr}`.toLowerCase(),o=n.includes("already booted")||n.includes("current state: booted");if(0!==t.exitCode&&!o)throw new p("COMMAND_FAILED","simctl boot failed",{stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode});if(i=await m("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0,timeoutMs:a}),0!==i.exitCode)throw new p("COMMAND_FAILED","simctl bootstatus failed",{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode});let s=await eG(e.id);if("Booted"!==s)throw new p("COMMAND_FAILED","Simulator is still booting",{state:s})},{maxAttempts:3,baseDelayMs:500,maxDelayMs:2e3,jitter:.2,shouldRetry:e=>{let r=D({error:e,stdout:i?.stdout??t?.stdout,stderr:i?.stderr??t?.stderr,context:{platform:"ios",phase:"boot"}});return"IOS_BOOT_TIMEOUT"!==r&&"CI_RESOURCE_STARVATION_SUSPECTED"!==r}},{deadline:r,phase:"boot",classifyReason:e=>D({error:e,stdout:i?.stdout??t?.stdout,stderr:i?.stderr??t?.stderr,context:{platform:"ios",phase:"boot"}}),onEvent:e=>{e_&&process.stderr.write(`[agent-device][retry] ${JSON.stringify(e)}
8
+ `)}})}catch(d){let a=t?.stdout,n=t?.stderr,o=t?.exitCode,s=i?.stdout,l=i?.stderr,c=i?.exitCode,u=D({error:d,stdout:s??a,stderr:l??n,context:{platform:"ios",phase:"boot"}});throw new p("COMMAND_FAILED","iOS simulator failed to boot",{platform:"ios",deviceId:e.id,timeoutMs:eO,elapsedMs:r.elapsedMs(),reason:u,hint:O(u),boot:t?{exitCode:o,stdout:a,stderr:n}:void 0,bootstatus:i?{exitCode:c,stdout:s,stderr:l}:void 0})}}async function eG(e){let t=await m("xcrun",["simctl","list","devices","-j"],{allowFailure:!0,timeoutMs:I});if(0!==t.exitCode)return null;try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices??{})){let i=t.find(t=>t.udid===e);if(i)return i.state}}catch{}return null}let ej=new Map,eq=eJ(process.env.AGENT_DEVICE_RUNNER_STARTUP_TIMEOUT_MS,12e4,5e3),eW=eJ(process.env.AGENT_DEVICE_RUNNER_COMMAND_TIMEOUT_MS,15e3,1e3);function eJ(e,t,i){if(!e)return t;let r=Number(e);return Number.isFinite(r)?Math.max(i,Math.floor(r)):t}async function eH(e,t,i={}){var r;return"snapshot"===(r=t.command)||"findText"===r||"listTappables"===r||"alert"===r?k(()=>ez(e,t,i),{shouldRetry:e4}):ez(e,t,i)}async function ez(e,t,i={}){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let r=await eQ(e,i),a=r.ready?eW:eq;return await eX(e,r,t,i.logPath,a)}catch(a){let r=a instanceof p?a:new p("COMMAND_FAILED",String(a));if("COMMAND_FAILED"===r.code&&"string"==typeof r.message&&r.message.includes("Runner did not accept connection")){await eY(e.id);let r=await eQ(e,i),a=await e5(r.device,r.port,t,i.logPath,eq);return await eK(a,r,i.logPath)}throw a}}async function eX(e,t,i,r,a){let n=await e5(e,t.port,i,r,a);return await eK(n,t,r)}async function eK(e,t,i){let r=await e.text(),a={};try{a=JSON.parse(r)}catch{throw new p("COMMAND_FAILED","Invalid runner response",{text:r})}if(!a.ok)throw new p("COMMAND_FAILED",a.error?.message??"Runner error",{runner:a,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:i});return t.ready=!0,a.data??{}}async function eY(e){let t=ej.get(e);if(t){try{await e5(t.device,t.port,{command:"shutdown"},void 0,15e3)}catch{await e0(t.child.pid,"SIGTERM")}try{await Promise.race([t.testPromise,new Promise(e=>setTimeout(e,1e4))])}catch{}await e0(t.child.pid,"SIGKILL"),e9(t.xctestrunPath),e9(t.jsonPath),ej.delete(e)}}async function eZ(e){await m("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0,timeoutMs:eq})}async function eQ(e,t){let i=ej.get(e.id);if(i)return i;await eZ(e.id);let r=await e1(e.id,t),a=await e7(),{xctestrunPath:n,jsonPath:o}=await e6(r,{AGENT_DEVICE_RUNNER_PORT:String(a)},`session-${e.id}-${a}`),{child:s,wait:l}=u("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-test-timeouts-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",n,"-destination",`platform=iOS Simulator,id=${e.id}`],{allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(a)}});s.stdout?.on("data",e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)}),s.stderr?.on("data",e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)});let c={device:e,deviceId:e.id,port:a,xctestrunPath:n,jsonPath:o,testPromise:l,child:s,ready:!1};return ej.set(e.id,c),c}async function e0(e,t){if(!e||e<=0)return;try{process.kill(e,t)}catch{}let i="SIGTERM"===t?"TERM":"KILL";try{await m("pkill",[`-${i}`,"-P",String(e)],{allowFailure:!0})}catch{}}async function e1(e,t){let i,r=n.join(f.homedir(),".agent-device","ios-runner"),a=n.join(r,"derived");if((i=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(i.toLowerCase()))try{d.rmSync(a,{recursive:!0,force:!0})}catch{}let s=e2(a);if(s)return s;let l=function(){let e=n.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=n.join(t,"package.json");if(d.existsSync(e))return t;t=n.dirname(t)}return e}(),u=n.join(l,"ios-runner","AgentDeviceRunner","AgentDeviceRunner.xcodeproj");if(!d.existsSync(u))throw new p("COMMAND_FAILED","iOS runner project not found",{projectPath:u});try{await o("xcodebuild",["build-for-testing","-project",u,"-scheme","AgentDeviceRunner","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-destination",`platform=iOS Simulator,id=${e}`,"-derivedDataPath",a],{onStdoutChunk:e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{e3(e,t.logPath,t.traceLogPath,t.verbose)}})}catch(i){let e=i instanceof p?i:new p("COMMAND_FAILED",String(i));throw new p("COMMAND_FAILED","xcodebuild build-for-testing failed",{error:e.message,details:e.details,logPath:t.logPath})}let m=e2(a);if(!m)throw new p("COMMAND_FAILED","Failed to locate .xctestrun after build");return m}function e2(e){if(!d.existsSync(e))return null;let t=[],i=[e];for(;i.length>0;){let e=i.pop();for(let r of d.readdirSync(e,{withFileTypes:!0})){let a=n.join(e,r.name);if(r.isDirectory()){i.push(a);continue}if(r.isFile()&&r.name.endsWith(".xctestrun"))try{let e=d.statSync(a);t.push({path:a,mtimeMs:e.mtimeMs})}catch{}}}return 0===t.length?null:(t.sort((e,t)=>t.mtimeMs-e.mtimeMs),t[0]?.path??null)}function e3(e,t,i,r){t&&d.appendFileSync(t,e),i&&d.appendFileSync(i,e),r&&process.stderr.write(e)}function e4(e){if(!(e instanceof p)||"COMMAND_FAILED"!==e.code)return!1;let t=`${e.message??""}`.toLowerCase();return!!(t.includes("runner did not accept connection")||t.includes("fetch failed")||t.includes("econnrefused")||t.includes("socket hang up"))}async function e5(e,t,i,r,a=eq){let n=Date.now(),o=null;for(;Date.now()-n<a;)try{return await fetch(`http://127.0.0.1:${t}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}catch(e){o=e,await new Promise(e=>setTimeout(e,100))}if("simulator"===e.kind){let r=await e8(e.id,t,i);return new Response(r.body,{status:r.status})}throw new p("COMMAND_FAILED","Runner did not accept connection",{port:t,logPath:r,lastError:o?String(o):void 0,reason:D({error:o,message:"Runner did not accept connection",context:{platform:"ios",phase:"connect"}}),hint:O("IOS_RUNNER_CONNECT_TIMEOUT")})}async function e8(e,t,i){let r=JSON.stringify(i),a=await m("xcrun",["simctl","spawn",e,"/usr/bin/curl","-s","-X","POST","-H","Content-Type: application/json","--data",r,`http://127.0.0.1:${t}/command`],{allowFailure:!0}),n=a.stdout;if(0!==a.exitCode){let e=D({message:"Runner did not accept connection (simctl spawn)",stdout:a.stdout,stderr:a.stderr,context:{platform:"ios",phase:"connect"}});throw new p("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:a.stdout,stderr:a.stderr,exitCode:a.exitCode,reason:e,hint:O(e)})}return{status:200,body:n}}async function e7(){return await new Promise((e,t)=>{let i=h.createServer();i.listen(0,"127.0.0.1",()=>{let r=i.address();i.close(),"object"==typeof r&&r?.port?e(r.port):t(new p("COMMAND_FAILED","Failed to allocate port"))}),i.on("error",t)})}async function e6(e,t,i){let r,a=n.dirname(e),o=i.replace(/[^a-zA-Z0-9._-]/g,"_"),s=n.join(a,`AgentDeviceRunner.env.${o}.json`),l=n.join(a,`AgentDeviceRunner.env.${o}.xctestrun`),c=await m("plutil",["-convert","json","-o","-",e],{allowFailure:!0});if(0!==c.exitCode||!c.stdout.trim())throw new p("COMMAND_FAILED","Failed to read xctestrun plist",{xctestrunPath:e,stderr:c.stderr});try{r=JSON.parse(c.stdout)}catch(t){throw new p("COMMAND_FAILED","Failed to parse xctestrun JSON",{xctestrunPath:e,error:String(t)})}let u=e=>{e.EnvironmentVariables={...e.EnvironmentVariables??{},...t},e.UITestEnvironmentVariables={...e.UITestEnvironmentVariables??{},...t},e.UITargetAppEnvironmentVariables={...e.UITargetAppEnvironmentVariables??{},...t},e.TestingEnvironmentVariables={...e.TestingEnvironmentVariables??{},...t}},f=r.TestConfigurations;if(Array.isArray(f))for(let e of f){if(!e||"object"!=typeof e)continue;let t=e.TestTargets;if(Array.isArray(t))for(let e of t)e&&"object"==typeof e&&u(e)}for(let[e,t]of Object.entries(r))t&&"object"==typeof t&&t.TestBundlePath&&(u(t),r[e]=t);d.writeFileSync(s,JSON.stringify(r,null,2));let h=await m("plutil",["-convert","xml1","-o",l,s],{allowFailure:!0});if(0!==h.exitCode)throw new p("COMMAND_FAILED","Failed to write xctestrun plist",{tmpXctestrunPath:l,stderr:h.stderr});return{xctestrunPath:l,jsonPath:s}}function e9(e){try{d.existsSync(e)&&d.unlinkSync(e)}catch{}}async function te(e,t={}){let i,r;if("ios"!==e.platform||"simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","AX snapshot is only supported on iOS simulators");let a=await tt(),n=await k(async()=>{var e,i;let r,n,o,s=await m(a,[],{allowFailure:!0});if(t.traceLogPath&&(e=t.traceLogPath,r=((i=s).stdout??"").toString(),n=(i.stderr??"").toString(),o=`
9
9
  [axsnapshot] exit=${i.exitCode} stdoutBytes=${r.length} stderrBytes=${n.length}
10
- `,u.appendFileSync(e,o),(0!==i.exitCode||n.length>0)&&(n.length>0&&u.appendFileSync(e,`${n}
11
- `),0!==i.exitCode&&r.length>0&&u.appendFileSync(e,`${r}
12
- `))),0!==s.exitCode){let e,t,i=(s.stderr??"").toString(),r=(e=i.toLowerCase()).includes("accessibility permission")?" Enable Accessibility for your terminal in System Settings > Privacy & Security > Accessibility, or use --backend xctest (slower snapshots via XCTest).":e.includes("could not find ios app content")?" AX snapshot sometimes caches empty content. Try restarting the Simulator app.":"",a=!!((t=i.toLowerCase()).includes("could not find ios app content")||t.includes("timeout"));throw new p("COMMAND_FAILED","AX snapshot failed",{stderr:`${i}${r}`,stdout:s.stdout,retryable:a})}return s},{shouldRetry:e=>{var t;return(t=e)instanceof p&&"COMMAND_FAILED"===t.code&&t.details?.retryable===!0}});try{let e=JSON.parse(n.stdout);if(e&&"object"==typeof e&&"root"in e){if(!e.root)throw Error("AX snapshot missing root");i=e.root,r=e.windowFrame??void 0}else i=e}catch(e){throw new p("COMMAND_FAILED","Invalid AX snapshot JSON",{error:String(e)})}let o=i.frame??r,s=[],l=[],c=(e,t)=>{e.frame&&s.push(e.frame);let i=e.frame&&o?{x:e.frame.x-o.x,y:e.frame.y-o.y,width:e.frame.width,height:e.frame.height}:e.frame;for(let r of(l.push({...e,frame:i,children:void 0,depth:t}),e.children??[]))c(r,t+1)};return c(i,0),{nodes:(function(e,t,i){if(!t||0===i.length)return e;let r=1/0,a=1/0;for(let e of i)e.x<r&&(r=e.x),e.y<a&&(a=e.y);return r<=5&&a<=5?e.map(e=>({...e,frame:e.frame?{x:e.frame.x+t.x,y:e.frame.y+t.y,width:e.frame.width,height:e.frame.height}:void 0})):e})(l,o,s).map((e,t)=>({index:t,type:e.subrole??e.role,label:e.label,value:e.value,identifier:e.identifier,rect:e.frame?{x:e.frame.x,y:e.frame.y,width:e.frame.width,height:e.frame.height}:void 0,depth:e.depth}))}}async function tt(){let e=function(){let e=n.dirname(c(import.meta.url));for(let t=0;t<6;t+=1){let t=n.join(e,"package.json");if(u.existsSync(t))return e;e=n.dirname(e)}return process.cwd()}(),t=n.join(e,"ios-runner","AXSnapshot"),i=process.env.AGENT_DEVICE_AX_BINARY;if(i&&u.existsSync(i))return i;let r=n.join(e,"dist","bin","axsnapshot");if(u.existsSync(r))return r;let a=n.join(t,".build","release","axsnapshot");if(u.existsSync(a))return a;let o=await m("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==o.exitCode||!u.existsSync(a))throw new p("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:o.stderr,stdout:o.stdout});return a}async function ti(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await eh();let e=await E();return await v(e,t)}if("ios"===t.platform){let e=await ex();return await v(e,t)}let i=[];try{i.push(...await E())}catch{}try{i.push(...await ex())}catch{}return await v(i,t)}async function tr(e,t,i,r,a){let o=function(e,t){switch(e.platform){case"android":return{open:(t,i)=>X(e,t,i?.activity),openDevice:()=>Y(e),close:t=>Z(e,t),tap:(t,i)=>ei(e,t,i),longPress:(t,i,r)=>eo(e,t,i,r),focus:(t,i)=>el(e,t,i),type:t=>es(e,t),fill:(t,i,r)=>ec(e,t,i,r),scroll:(t,i)=>ed(e,t,i),scrollIntoView:t=>eu(e,t),screenshot:t=>ef(e,t)};case"ios":var i,r;let a;return{open:t=>eL(e,t),openDevice:()=>eR(e),close:t=>eE(e,t),screenshot:t=>e$(e,t),...(i=e,a={verbose:(r=t).verbose,logPath:r.logPath,traceLogPath:r.traceLogPath},{tap:async(e,t)=>{await eH(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a)},longPress:async(e,t,n)=>{await eH(i,{command:"longPress",x:e,y:t,durationMs:n,appBundleId:r.appBundleId},a)},focus:async(e,t)=>{await eH(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a)},type:async e=>{await eH(i,{command:"type",text:e,appBundleId:r.appBundleId},a)},fill:async(e,t,n)=>{await eH(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a),await eH(i,{command:"type",text:n,clearFirst:!0,appBundleId:r.appBundleId},a)},scroll:async(e,t)=>{if(!["up","down","left","right"].includes(e))throw new p("INVALID_ARGS",`Unknown direction: ${e}`);let n=function(e){switch(e){case"up":return"down";case"down":return"up";case"left":return"right";case"right":return"left"}}(e);await eH(i,{command:"swipe",direction:n,appBundleId:r.appBundleId},a)},scrollIntoView:async e=>{for(let t=0;t<8;t+=1){let n=await eH(i,{command:"findText",text:e,appBundleId:r.appBundleId},a);if(n?.found)return{attempts:t+1};await eH(i,{command:"swipe",direction:"up",appBundleId:r.appBundleId},a),await new Promise(e=>setTimeout(e,300))}throw new p("COMMAND_FAILED",`scrollintoview could not find text: ${e}`)}})};default:throw new p("UNSUPPORTED_PLATFORM",`Unsupported platform: ${e.platform}`)}}(e,{appBundleId:a?.appBundleId,verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath});switch(t){case"open":{let e=i[0];if(!e)return await o.openDevice(),{app:null};return await o.open(e,{activity:a?.activity}),{app:e}}case"close":{let e=i[0];if(!e)return{closed:"session"};return await o.close(e),{app:e}}case"press":{let[e,t]=i.map(Number);if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","press requires x y");return await o.tap(e,t),{x:e,y:t}}case"long-press":{let e=Number(i[0]),t=Number(i[1]),r=i[2]?Number(i[2]):void 0;if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","long-press requires x y [durationMs]");return await o.longPress(e,t,r),{x:e,y:t,durationMs:r}}case"focus":{let[e,t]=i.map(Number);if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","focus requires x y");return await o.focus(e,t),{x:e,y:t}}case"type":{let e=i.join(" ");if(!e)throw new p("INVALID_ARGS","type requires text");return await o.type(e),{text:e}}case"fill":{let e=Number(i[0]),t=Number(i[1]),r=i.slice(2).join(" ");if(Number.isNaN(e)||Number.isNaN(t)||!r)throw new p("INVALID_ARGS","fill requires x y text");return await o.fill(e,t,r),{x:e,y:t,text:r}}case"scroll":{let e=i[0],t=i[1]?Number(i[1]):void 0;if(!e)throw new p("INVALID_ARGS","scroll requires direction");return await o.scroll(e,t),{direction:e,amount:t}}case"scrollintoview":{let e=i.join(" ").trim();if(!e)throw new p("INVALID_ARGS","scrollintoview requires text");let t=await o.scrollIntoView(e);if(t?.attempts)return{text:e,attempts:t.attempts};return{text:e}}case"pinch":{let t=Number(i[0]),r=i[1]?Number(i[1]):void 0,n=i[2]?Number(i[2]):void 0;if(Number.isNaN(t)||t<=0)throw new p("INVALID_ARGS","pinch requires scale > 0");return await eH(e,{command:"pinch",scale:t,x:r,y:n,appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{scale:t,x:r,y:n}}case"screenshot":{let e=i[0]??r??`./screenshot-${Date.now()}.png`;return await s.mkdir(n.dirname(e),{recursive:!0}),await o.screenshot(e),{path:e}}case"back":if("ios"===e.platform)return await eH(e,{command:"back",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"back"};return await er(e),{action:"back"};case"home":if("ios"===e.platform)return await eH(e,{command:"home",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"home"};return await ea(e),{action:"home"};case"app-switcher":if("ios"===e.platform)return await eH(e,{command:"appSwitcher",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"app-switcher"};return await en(e),{action:"app-switcher"};case"settings":{let[t,r,n]=i;if("ios"===e.platform)return await eF(e,t,r,n??a?.appBundleId),{setting:t,state:r};return await ep(e,t,r),{setting:t,state:r}}case"snapshot":{let t=a?.snapshotBackend??"xctest";if("ios"===e.platform){if("ax"===t)return{nodes:(await te(e,{traceLogPath:a?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"};let i=await eH(e,{command:"snapshot",appBundleId:a?.appBundleId,interactiveOnly:a?.snapshotInteractiveOnly,compact:a?.snapshotCompact,depth:a?.snapshotDepth,scope:a?.snapshotScope,raw:a?.snapshotRaw},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),r=i.nodes??[];if(0===r.length)try{return{nodes:(await te(e,{traceLogPath:a?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"}}catch{}return{nodes:r,truncated:i.truncated??!1,backend:"xctest"}}let i=await em(e,{interactiveOnly:a?.snapshotInteractiveOnly,compact:a?.snapshotCompact,depth:a?.snapshotDepth,scope:a?.snapshotScope,raw:a?.snapshotRaw});return{nodes:i.nodes??[],truncated:i.truncated??!1,backend:"android"}}default:throw new p("INVALID_ARGS",`Unknown command: ${t}`)}}let ta={alert:{ios:{simulator:!0},android:{}},pinch:{ios:{simulator:!0},android:{}},"app-switcher":{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},apps:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},back:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},boot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},click:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},close:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},fill:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},find:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},focus:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},get:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},is:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},home:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},"long-press":{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},open:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},reinstall:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},press:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},record:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},screenshot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},scroll:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},settings:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},snapshot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},type:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},wait:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}}};function tn(e,t){let i=ta[e];if(!i)return!0;let r=i[t.platform];return!!r&&!0===r[t.kind??"unknown"]}function to(e){let t=e.result?.text;if("string"==typeof t&&t.trim().length>0)return t;let i=e.positionals??[];return 0===i.length?"":i[0].startsWith("@")?i.length>=3?i.slice(2).join(" ").trim():i.slice(1).join(" ").trim():!(i.length>=3)||Number.isNaN(Number(i[0]))||Number.isNaN(Number(i[1]))?i.slice(1).join(" ").trim():i.slice(2).join(" ").trim()}function ts(e){let t=new Set,i=[];for(let r of e)t.has(r)||(t.add(r),i.push(r));return i}function tl(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}class tc{get(e){return this.sessions.get(e)}has(e){return this.sessions.has(e)}set(e,t){this.sessions.set(e,t)}delete(e){return this.sessions.delete(e)}values(){return this.sessions.values()}toArray(){return Array.from(this.sessions.values())}recordAction(e,t){t.flags?.noRecord||(t.flags?.saveScript&&(e.recordSession=!0),e.actions.push({ts:Date.now(),command:t.command,positionals:t.positionals,flags:function(e){if(!e)return{};let{platform:t,device:i,udid:r,serial:a,out:n,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:d,snapshotRaw:u,snapshotBackend:f,appsMetadata:p,saveScript:m,noRecord:h}=e;return{platform:t,device:i,udid:r,serial:a,out:n,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:d,snapshotRaw:u,snapshotBackend:f,appsMetadata:p,saveScript:m,noRecord:h}}(t.flags),result:t.result}))}writeSessionLog(e){try{if(!e.recordSession)return;u.existsSync(this.sessionsDir)||u.mkdirSync(this.sessionsDir,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),r=n.join(this.sessionsDir,`${t}-${i}.ad`),a=function(e,t){let i=[],r=e.device.name.replace(/"/g,'\\"'),a=e.device.kind?` kind=${e.device.kind}`:"";for(let n of(i.push(`context platform=${e.device.platform} device="${r}"${a} theme=unknown`),t))n.flags?.noRecord||i.push(function(e){let t=[e.command];if("click"===e.command){let i=e.positionals?.[0];if(i){if(t.push(td(i)),i.startsWith("@")){let i=e.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push(td(i))}return t.join(" ")}}if("fill"===e.command){let i=e.positionals?.[0];if(i&&i.startsWith("@")){t.push(td(i));let r=e.result?.refLabel,a=e.positionals.slice(1).join(" ");return"string"==typeof r&&r.trim().length>0&&t.push(td(r)),a&&t.push(td(a)),t.join(" ")}}if("get"===e.command){let i=e.positionals?.[0],r=e.positionals?.[1];if(i&&r){if(t.push(td(i)),t.push(td(r)),r.startsWith("@")){let i=e.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push(td(i))}return t.join(" ")}}if("snapshot"===e.command)return e.flags?.snapshotInteractiveOnly&&t.push("-i"),e.flags?.snapshotCompact&&t.push("-c"),"number"==typeof e.flags?.snapshotDepth&&t.push("-d",String(e.flags.snapshotDepth)),e.flags?.snapshotScope&&t.push("-s",td(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let i of e.positionals??[])t.push(td(i));return t.join(" ")}(n));return`${i.join("\n")}
13
- `}(e,this.buildOptimizedActions(e));u.writeFileSync(r,a)}catch{}}defaultTracePath(e){let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date().toISOString().replace(/[:.]/g,"-");return n.join(this.sessionsDir,`${t}-${i}.trace.log`)}static expandHome(e){return e.startsWith("~/")?n.join(f.homedir(),e.slice(2)):n.resolve(e)}buildOptimizedActions(e){let t=[];for(let i of e.actions){if("snapshot"===i.command)continue;let r=Array.isArray(i.result?.selectorChain)&&i.result?.selectorChain.every(e=>"string"==typeof e)?i.result.selectorChain:[];if(r.length>0&&("click"===i.command||"fill"===i.command||"get"===i.command)){let e=r.join(" || ");if("click"===i.command){t.push({...i,positionals:[e]});continue}if("fill"===i.command){let r=to(i);if(r.length>0){t.push({...i,positionals:[e,r]});continue}}if("get"===i.command){let r=i.positionals?.[0];if("text"===r||"attrs"===r){t.push({...i,positionals:[r,e]});continue}}}if("click"===i.command||"fill"===i.command||"get"===i.command){let r=i.result?.refLabel;"string"==typeof r&&r.trim().length>0&&t.push({ts:i.ts,command:"snapshot",positionals:[],flags:{platform:e.device.platform,snapshotInteractiveOnly:!0,snapshotCompact:!0,snapshotScope:r.trim()},result:{scope:r.trim()}})}t.push(i)}return t}constructor(e){tl(this,"sessions",new Map),tl(this,"sessionsDir",void 0),this.sessionsDir=e}}function td(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function tu(e,t,i,r){return{appBundleId:i,activity:t?.activity,verbose:t?.verbose,logPath:e,traceLogPath:r,snapshotInteractiveOnly:t?.snapshotInteractiveOnly,snapshotCompact:t?.snapshotCompact,snapshotDepth:t?.snapshotDepth,snapshotScope:t?.snapshotScope,snapshotRaw:t?.snapshotRaw,snapshotBackend:t?.snapshotBackend}}async function tf(e){if("ios"===e.platform&&"simulator"===e.kind){let{ensureBootedSimulator:t}=await Promise.resolve().then(()=>({ensureBootedSimulator:eU}));await t(e);return}if("android"===e.platform){let{waitForAndroidBoot:t}=await Promise.resolve().then(()=>({waitForAndroidBoot:T}));await t(e.id)}}function tp(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function tm(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function th(e,t){return e.find(e=>e.ref===t)??null}function tw(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}function tg(e,t){let i=t.toLowerCase();return e.find(e=>{let t=(e.label??"").toLowerCase(),r=(e.value??"").toLowerCase(),a=(e.identifier??"").toLowerCase();return t.includes(i)||r.includes(i)||a.includes(i)})??null}function tv(e,t){let i=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);return i&&tA(i)?i:function(e,t){if(!e.rect)return;let i=e.rect.y+e.rect.height/2,r=null;for(let e of t){if(!e.rect)continue;let t=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);if(!t||!tA(t))continue;let a=Math.abs(e.rect.y+e.rect.height/2-i);(!r||a<r.distance)&&(r={label:t,distance:a})}return r?.label}(e,t)??(i&&tA(i)?i:void 0)}function tA(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}function ty(e){let t=[],i=[];for(let r of e){let e=r.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let a=tI(r.type??""),n=[r.label,r.value,r.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),o=!!n&&tA(n);if(("group"===a||"ioscontentgroup"===a)&&!o){t.push(e);continue}let s=Math.max(0,e-t.length);i.push({...r,depth:s})}return i}function tI(e){let t=e.trim().replace(/XCUIElementType/gi,"").toLowerCase();t.startsWith("ax")&&(t=t.replace(/^ax/,""));let i=Math.max(t.lastIndexOf("."),t.lastIndexOf("/"));return -1!==i&&(t=t.slice(i+1)),t}function tb(e,t){let i=tI(e);return!i||("android"===t?i.includes("edittext")||i.includes("autocompletetextview"):i.includes("textfield")||i.includes("securetextfield")||i.includes("searchfield")||i.includes("textview")||i.includes("textarea")||"search"===i)}function tN(e){return[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??""}async function tS(e,t,i,r){let a=tD(await tr(e,"snapshot",[],r?.out,{...tu(t,{...r,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"ax"},void 0,i)}));if(a?.appName||a?.appBundleId)return{appName:a.appName??a.appBundleId??"unknown",appBundleId:a.appBundleId,source:"snapshot-ax"};let n=tD(await tr(e,"snapshot",[],r?.out,{...tu(t,{...r,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"xctest"},void 0,i)}));return{appName:n?.appName??n?.appBundleId??"unknown",appBundleId:n?.appBundleId,source:"snapshot-xctest"}}function tD(e){let t=tp(e?.nodes??[]),i=t.find(e=>"application"===tI(e.type??""))??t[0];if(!i)return null;let r=i.label?.trim(),a=i.identifier?.trim();return r||a?{appName:r||void 0,appBundleId:a||void 0}:null}let tx=new Set(["id","role","text","label","value"]),tk=new Set(["visible","hidden","editable","selected","enabled","hittable"]),tO=new Set([...tx,...tk]);function t_(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Selector expression cannot be empty");let i=function(e){let t=[],i="",r=null;for(let a=0;a<e.length;a+=1){let n=e[a];if(('"'===n||"'"===n)&&!tH(e,a)){r?r===n&&(r=null):r=n,i+=n;continue}if(!r&&"|"===n&&"|"===e[a+1]){let r=i.trim();if(!r)throw new p("INVALID_ARGS",`Invalid selector fallback expression: ${e}`);t.push(r),i="",a+=1;continue}i+=n}let a=i.trim();if(!a)throw new p("INVALID_ARGS",`Invalid selector fallback expression: ${e}`);return t.push(a),t}(t);if(0===i.length)throw new p("INVALID_ARGS","Selector expression cannot be empty");return{raw:t,selectors:i.map(e=>(function(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Selector segment cannot be empty");let i=function(e){let t=[],i="",r=null;for(let a=0;a<e.length;a+=1){let n=e[a];if(('"'===n||"'"===n)&&!tH(e,a)){r?r===n&&(r=null):r=n,i+=n;continue}if(!r&&/\s/.test(n)){i.trim().length>0&&t.push(i.trim()),i="";continue}i+=n}if(r)throw new p("INVALID_ARGS",`Unclosed quote in selector: ${e}`);return i.trim().length>0&&t.push(i.trim()),t}(t);if(0===i.length)throw new p("INVALID_ARGS",`Invalid selector segment: ${e}`);return{raw:t,terms:i.map(tB)}})(e))}}function tM(e){try{return t_(e)}catch{return null}}function tL(e,t,i){let r=i.requireRect??!1,a=i.requireUnique??!0,n=i.disambiguateAmbiguous??!1,o=[];for(let s=0;s<t.selectors.length;s+=1){let l=t.selectors[s],c=function(e,t,i){let r=0,a=null,n=null,o=!1;for(let s of e){if(i.requireRect&&!s.rect||!tV(s,t,i.platform))continue;if(r+=1,a||(a=s),!n){n=s,o=!1;continue}let e=function(e,t){let i=e.depth??0,r=t.depth??0;if(i!==r)return i>r?1:-1;let a=tJ(e),n=tJ(t);return a!==n?a<n?1:-1:0}(s,n);if(e>0){n=s,o=!1;continue}0===e&&(o=!0)}return{count:r,firstNode:a,disambiguated:o?null:n}}(e,l,{platform:i.platform,requireRect:r});if(o.push({selector:l.raw,matches:c.count}),0!==c.count&&c.firstNode){if(a&&1!==c.count){if(!n)continue;let e=c.disambiguated;if(!e)continue;return{node:e,selector:l,selectorIndex:s,matches:c.count,diagnostics:o}}return{node:c.firstNode,selector:l,selectorIndex:s,matches:c.count,diagnostics:o}}}return null}function tR(e,t,i){let r=i.requireRect??!1,a=[];for(let n=0;n<t.selectors.length;n+=1){let o=t.selectors[n],s=function(e,t,i){let r=0;for(let a of e)(!i.requireRect||a.rect)&&tV(a,t,i.platform)&&(r+=1);return r}(e,o,{platform:i.platform,requireRect:r});if(a.push({selector:o.raw,matches:s}),s>0)return{selectorIndex:n,selector:o,matches:s,diagnostics:a}}return null}function tE(e,t,i){let r=i.unique??!0;if(0===t.length)return`Selector did not match: ${e.raw}`;let a=t.map(e=>`${e.selector} -> ${e.matches}`).join(", ");return r?`Selector did not resolve uniquely (${a})`:`Selector did not match (${a})`}function tC(e,t={}){if(0===e.length)return null;let i=t.preferTrailingValue??!1,r=0,a=[];for(;r<e.length&&function(e){let t=e.trim();if(!t)return!1;if("||"===t)return!0;let i=t.indexOf("=");if(-1!==i){let e=t.slice(0,i).trim().toLowerCase();return tO.has(e)}return tO.has(t.toLowerCase())}(e[r]);){r+=1;let t=e.slice(0,r).join(" ").trim();t&&tM(t)&&a.push(r)}if(0===a.length)return null;let n=a[a.length-1];if(i){for(let t=a.length-1;t>=0;t-=1)if(a[t]<e.length){n=a[t];break}}let o=e.slice(0,n).join(" ").trim();return o?{selectorExpression:o,rest:e.slice(n)}:null}function tT(e){let t=e[0]??"",i=tC(e.slice(1),{preferTrailingValue:"text"===t});return{predicate:t,split:i}}function tP(e){return!0===e.hittable||!!e.rect&&e.rect.width>0&&e.rect.height>0}function t$(e,t){return tb(e.type??"",t)&&!1!==e.enabled}function tF(e,t,i={}){let r=[],a=tI(e.type??""),n=tW(e.identifier),o=tW(e.label),s=tW(e.value),l=tW(tN(e)),c="fill"===i.action;n&&r.push(`id=${tq(n)}`),a&&o&&r.push(c?`role=${tq(a)} label=${tq(o)} editable=true`:`role=${tq(a)} label=${tq(o)}`),o&&r.push(c?`label=${tq(o)} editable=true`:`label=${tq(o)}`),s&&r.push(c?`value=${tq(s)} editable=true`:`value=${tq(s)}`),l&&l!==o&&l!==s&&r.push(c?`text=${tq(l)} editable=true`:`text=${tq(l)}`),a&&c&&!r.some(e=>e.includes("editable=true"))&&r.push(`role=${tq(a)} editable=true`);let d=ts(r);return 0===d.length&&a&&d.push(c?`role=${tq(a)} editable=true`:`role=${tq(a)}`),0===d.length&&tP(e)&&d.push("visible=true"),d}function tB(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Empty selector term");let i=t.indexOf("=");if(-1===i){let i=t.toLowerCase();if(!tk.has(i))throw new p("INVALID_ARGS",`Invalid selector term "${e}", expected key=value`);return{key:i,value:!0}}let r=t.slice(0,i).trim().toLowerCase(),a=t.slice(i+1).trim();if(!tO.has(r))throw new p("INVALID_ARGS",`Unknown selector key: ${r}`);if(!a)throw new p("INVALID_ARGS",`Missing selector value for key: ${r}`);if(tk.has(r)){let e,t="true"===(e=tU(a).toLowerCase())||"false"!==e&&null;if(null===t)throw new p("INVALID_ARGS",`Invalid boolean value for ${r}: ${a}`);return{key:r,value:t}}return{key:r,value:tU(a)}}function tV(e,t,i){return t.terms.every(t=>(function(e,t,i){switch(t.key){case"id":return tG(e.identifier,String(t.value));case"role":var r,a;return r=e.type,a=String(t.value),function(e){return tI(e)}(r??"")===function(e){return tI(e)}(a);case"label":return tG(e.label,String(t.value));case"value":return tG(e.value,String(t.value));case"text":{let i=tj(String(t.value));return tj(tN(e))===i}case"visible":return tP(e)===!!t.value;case"hidden":return!tP(e)==!!t.value;case"editable":return t$(e,i)===!!t.value;case"selected":return!0===e.selected==!!t.value;case"enabled":return!1!==e.enabled==!!t.value;case"hittable":return!0===e.hittable==!!t.value;default:return!1}})(e,t,i))}function tU(e){let t=e.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1).replace(/\\(["'])/g,"$1"):t}function tG(e,t){return tj(e??"")===tj(t)}function tj(e){return e.trim().toLowerCase().replace(/\s+/g," ")}function tq(e){return JSON.stringify(e)}function tW(e){if(!e)return null;let t=e.trim();return t||null}function tJ(e){return e.rect?e.rect.width*e.rect.height:1/0}function tH(e,t){let i=0;for(let r=t-1;r>=0&&"\\"===e[r];r-=1)i+=1;return i%2==1}function tz(e){return!!(e?.platform||e?.device||e?.udid||e?.serial)}async function tX(e){let t=e.session?.device??await ti(e.flags??{});return!1!==e.ensureReady&&await e.ensureReadyFn(t),t}let tK={ios:async(e,t,i)=>{let{reinstallIosApp:r}=await Promise.resolve().then(()=>({reinstallIosApp:eP}));return await r(e,t,i)},android:async(e,t,i)=>{let{reinstallAndroidApp:r}=await Promise.resolve().then(()=>({reinstallAndroidApp:et}));return await r(e,t,i)}};async function tY(e){let{req:t,sessionName:i,logPath:r,sessionStore:a,invoke:n,dispatch:o,ensureReady:s,reinstallOps:c=tK}=e,d=o??tr,f=s??tf,m=t.command;if("session_list"===m)return{ok:!0,data:{sessions:a.toArray().map(e=>({name:e.name,platform:e.device.platform,device:e.device.name,id:e.device.id,createdAt:e.createdAt}))}};if("devices"===m)try{let e=[];if(t.flags?.platform==="android"){let{listAndroidDevices:t}=await Promise.resolve().then(()=>({listAndroidDevices:E}));e.push(...await t())}else if(t.flags?.platform==="ios"){let{listIosDevices:t}=await Promise.resolve().then(()=>({listIosDevices:ex}));e.push(...await t())}else{let{listAndroidDevices:t}=await Promise.resolve().then(()=>({listAndroidDevices:E})),{listIosDevices:i}=await Promise.resolve().then(()=>({listIosDevices:ex}));try{e.push(...await t())}catch{}try{e.push(...await i())}catch{}}return{ok:!0,data:{devices:e}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message,details:e.details}}}if("apps"===m){let e=a.get(i),r=t.flags??{};if(!e&&!tz(r))return{ok:!1,error:{code:"INVALID_ARGS",message:"apps requires an active session or an explicit device selector (e.g. --platform ios)."}};let n=await tX({session:e,flags:r,ensureReadyFn:f,ensureReady:!0});if(!tn("apps",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"apps is not supported on this device"}};if("ios"===n.platform){let{listSimulatorApps:e}=await Promise.resolve().then(()=>({listSimulatorApps:eV})),i=await e(n);return t.flags?.appsMetadata?{ok:!0,data:{apps:i}}:{ok:!0,data:{apps:i.map(e=>e.name&&e.name!==e.bundleId?`${e.name} (${e.bundleId})`:e.bundleId)}}}let{listAndroidApps:o,listAndroidAppsMetadata:s}=await Promise.resolve().then(()=>({listAndroidApps:W,listAndroidAppsMetadata:J}));return t.flags?.appsMetadata?{ok:!0,data:{apps:await s(n,t.flags?.appsFilter)}}:{ok:!0,data:{apps:await o(n,t.flags?.appsFilter)}}}if("boot"===m){let e=a.get(i),r=t.flags??{};if(!e&&!tz(r))return{ok:!1,error:{code:"INVALID_ARGS",message:"boot requires an active session or an explicit device selector (e.g. --platform ios)."}};let n=e?.device??await ti(r);return tn("boot",n)?(await f(n),{ok:!0,data:{platform:n.platform,device:n.name,id:n.id,kind:n.kind,booted:!0}}):{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"boot is not supported on this device"}}}if("appstate"===m){let e=a.get(i),n=t.flags??{},o=await tX({session:e,flags:n,ensureReadyFn:f,ensureReady:!0});if("ios"===o.platform){if(e?.appBundleId)return{ok:!0,data:{platform:"ios",appBundleId:e.appBundleId,appName:e.appName??e.appBundleId,source:"session"}};let i=await tS(o,r,e?.trace?.outPath,t.flags);return{ok:!0,data:{platform:"ios",appName:i.appName,appBundleId:i.appBundleId,source:i.source}}}let{getAndroidAppState:s}=await Promise.resolve().then(()=>({getAndroidAppState:H})),l=await s(o);return{ok:!0,data:{platform:"android",package:l.package,activity:l.activity}}}if("reinstall"===m){let e,r=a.get(i),n=t.flags??{};if(!r&&!tz(n))return{ok:!1,error:{code:"INVALID_ARGS",message:"reinstall requires an active session or an explicit device selector (e.g. --platform ios)."}};let o=t.positionals?.[0]?.trim(),s=t.positionals?.[1]?.trim();if(!o||!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"reinstall requires: reinstall <app> <path-to-app-binary>"}};let l=tc.expandHome(s);if(!u.existsSync(l))return{ok:!1,error:{code:"INVALID_ARGS",message:`App binary not found: ${l}`}};let d=await tX({session:r,flags:n,ensureReadyFn:f,ensureReady:!1});if(!tn("reinstall",d))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"reinstall is not supported on this device"}};if("ios"===d.platform){let t=await c.ios(d,o,l);e={platform:"ios",appId:t.bundleId,bundleId:t.bundleId}}else{let t=await c.android(d,o,l);e={platform:"android",appId:t.package,package:t.package}}let p={app:o,appPath:l,...e};return r&&a.recordAction(r,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:p}),{ok:!0,data:p}}if("open"===m){let e;if(a.has(i)){let e,n=a.get(i),o=t.positionals?.[0];if(!n||!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};if("ios"===n.device.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:eM}));e=await t(n.device,o)}catch{e=void 0}await d(n.device,"open",t.positionals??[],t.flags?.out,{...tu(r,t.flags,e)});let s={...n,appBundleId:e,appName:o,recordSession:n.recordSession||t.flags?.saveScript===!0,snapshot:void 0};return a.recordAction(s,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:{session:i,appName:o,appBundleId:e}}),a.set(i,s),{ok:!0,data:{session:i,appName:o,appBundleId:e}}}let n=await ti(t.flags??{}),o=a.toArray().find(e=>e.device.id===n.id);if(o)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${o.name}".`,details:{session:o.name,deviceId:n.id,deviceName:n.name}}};let s=t.positionals?.[0];if("ios"===n.platform)try{let{resolveIosApp:i}=await Promise.resolve().then(()=>({resolveIosApp:eM}));e=await i(n,t.positionals?.[0]??"")}catch{e=void 0}await d(n,"open",t.positionals??[],t.flags?.out,{...tu(r,t.flags,e)});let l={name:i,device:n,createdAt:Date.now(),appBundleId:e,appName:s,recordSession:t.flags?.saveScript===!0,actions:[]};return a.recordAction(l,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:{session:i}}),a.set(i,l),{ok:!0,data:{session:i}}}if("replay"===m){let e=t.positionals?.[0];if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay requires a path"}};try{let o=tc.expandHome(e),s=u.readFileSync(o,"utf8"),l=s.trimStart()[0];if("{"===l||"["===l)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay accepts .ad script files. JSON replay payloads are no longer supported."}};let c=function(e){let t=[];for(let i of e.split(/\r?\n/)){let e=function(e){let t=e.trim();if(0===t.length||t.startsWith("#"))return null;let i=function(e){let t=[],i=0;for(;i<e.length;){for(;i<e.length&&/\s/.test(e[i]);)i+=1;if(i>=e.length)break;if('"'===e[i]){let r=i+1,a=!1;for(;r<e.length;){let t=e[r];if('"'===t&&!a)break;a="\\"===t&&!a,"\\"!==t&&(a=!1),r+=1}if(r>=e.length)throw new p("INVALID_ARGS",`Invalid replay script line: ${e}`);let n=e.slice(i,r+1);t.push(JSON.parse(n)),i=r+1;continue}let r=i;for(;r<e.length&&!/\s/.test(e[r]);)r+=1;t.push(e.slice(i,r)),i=r}return t}(t);if(0===i.length)return null;let[r,...a]=i;if("context"===r)return null;let n={ts:Date.now(),command:r,positionals:[],flags:{}};if("snapshot"===r){n.positionals=[];for(let e=0;e<a.length;e+=1){let t=a[e];if("-i"===t){n.flags.snapshotInteractiveOnly=!0;continue}if("-c"===t){n.flags.snapshotCompact=!0;continue}if("--raw"===t){n.flags.snapshotRaw=!0;continue}if(("-d"===t||"--depth"===t)&&e+1<a.length){let t=Number(a[e+1]);Number.isFinite(t)&&t>=0&&(n.flags.snapshotDepth=Math.floor(t)),e+=1;continue}if(("-s"===t||"--scope"===t)&&e+1<a.length){n.flags.snapshotScope=a[e+1],e+=1;continue}if("--backend"===t&&e+1<a.length){let t=a[e+1];("ax"===t||"xctest"===t)&&(n.flags.snapshotBackend=t),e+=1}}return n}if("click"===r){if(0===a.length)return n;let e=a[0];return e.startsWith("@")?(n.positionals=[e],a[1]&&(n.result={refLabel:a[1]})):n.positionals=[a.join(" ")],n}if("fill"===r){if(a.length<2)return n.positionals=a,n;let e=a[0];return e.startsWith("@")?(a.length>=3?(n.positionals=[e,a.slice(2).join(" ")],n.result={refLabel:a[1]}):n.positionals=[e,a[1]],n):(n.positionals=[e,a.slice(1).join(" ")],n)}if("get"===r){if(a.length<2)return n.positionals=a,n;let e=a[0],t=a[1];return t.startsWith("@")?(n.positionals=[e,t],a[2]&&(n.result={refLabel:a[2]})):n.positionals=[e,a.slice(1).join(" ")],n}return n.positionals=a,n}(i);e&&t.push(e)}return t}(s),f=t.flags?.replayUpdate===!0,m=0;for(let e=0;e<c.length;e+=1){let s=c[e];if(!s||"replay"===s.command)continue;let l=await n({token:t.token,session:i,command:s.command,positionals:s.positionals??[],flags:s.flags??{}});if(l.ok)continue;if(!f)return tZ(l,s,e,o);let u=await tQ({action:s,sessionName:i,logPath:r,sessionStore:a,dispatch:d});if(!u)return tZ(l,s,e,o);if(c[e]=u,!(l=await n({token:t.token,session:i,command:u.command,positionals:u.positionals??[],flags:u.flags??{}})).ok)return tZ(l,u,e,o);m+=1}if(f&&m>0){let e=a.get(i);!function(e,t,i){let r=[];if(i){let e=i.device.name.replace(/"/g,'\\"'),t=i.device.kind?` kind=${i.device.kind}`:"";r.push(`context platform=${i.device.platform} device="${e}"${t} theme=unknown`)}for(let e of t)r.push(function(e){let t=[e.command];if("snapshot"===e.command)return e.flags?.snapshotInteractiveOnly&&t.push("-i"),e.flags?.snapshotCompact&&t.push("-c"),"number"==typeof e.flags?.snapshotDepth&&t.push("-d",String(e.flags.snapshotDepth)),e.flags?.snapshotScope&&t.push("-s",t2(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let i of e.positionals??[])t.push(t2(i));return t.join(" ")}(e));let a=`${r.join("\n")}
14
- `,n=`${e}.tmp-${process.pid}-${Date.now()}`;u.writeFileSync(n,a),u.renameSync(n,e)}(o,c,e)}return{ok:!0,data:{replayed:c.length,healed:m,session:i}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===m){let e=a.get(i);return e?(t.positionals&&t.positionals.length>0&&await d(e.device,"close",t.positionals??[],t.flags?.out,{...tu(r,t.flags,e.appBundleId,e.trace?.outPath)}),"ios"===e.device.platform&&"simulator"===e.device.kind&&await eY(e.device.id),a.recordAction(e,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:{session:i}}),t.flags?.saveScript&&(e.recordSession=!0),a.writeSessionLog(e),a.delete(i),{ok:!0,data:{session:i}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}return null}function tZ(e,t,i,r){var a;let n;if(e.ok)return e;let o=i+1,s=(n=((a=t).positionals??[]).map(e=>{let t=e.trim();return/^-?\d+(\.\d+)?$/.test(t)||t.startsWith("@")?t:JSON.stringify(t)}),[a.command,...n].join(" ")),l={...e.error.details??{},replayPath:r,step:o,action:t.command,positionals:t.positionals??[]};return{ok:!1,error:{code:e.error.code,message:`Replay failed at step ${o} (${s}): ${e.error.message}`,details:l}}}async function tQ(e){let{action:t,sessionName:i,logPath:r,sessionStore:a,dispatch:n}=e;if(!["click","fill","get","is","wait"].includes(t.command))return null;let o=a.get(i);if(!o)return null;let s="click"===t.command||"fill"===t.command,l=await t0(o,t,r,s,n,a);for(let e of function(e){let t=[],i=Array.isArray(e.result?.selectorChain)&&e.result?.selectorChain.every(e=>"string"==typeof e)?e.result.selectorChain:[];if(t.push(...i),"click"===e.command){let i=e.positionals?.[0]??"";i&&!i.startsWith("@")&&t.push(e.positionals.join(" "))}if("fill"===e.command){let i=e.positionals?.[0]??"";i&&!i.startsWith("@")&&Number.isNaN(Number(i))&&t.push(i)}if("get"===e.command){let i=e.positionals?.[1]??"";i&&!i.startsWith("@")&&t.push(e.positionals.slice(1).join(" "))}if("is"===e.command){let{split:i}=tT(e.positionals);i&&t.push(i.selectorExpression)}if("wait"===e.command){let{selectorExpression:i}=t1(e.positionals??[]);i&&t.push(i)}let r="string"==typeof e.result?.refLabel?e.result.refLabel.trim():"";if(r.length>0){let i=JSON.stringify(r);"fill"===e.command?(t.push(`id=${i} editable=true`),t.push(`label=${i} editable=true`),t.push(`text=${i} editable=true`),t.push(`value=${i} editable=true`)):(t.push(`id=${i}`),t.push(`label=${i}`),t.push(`text=${i}`),t.push(`value=${i}`))}return ts(t).filter(e=>e.trim().length>0)}(t)){let i=tM(e);if(!i)continue;let r=tL(l.nodes,i,{platform:o.device.platform,requireRect:s,requireUnique:!0,disambiguateAmbiguous:s});if(!r)continue;let a=tF(r.node,o.device.platform,{action:"click"===t.command?"click":"fill"===t.command?"fill":"get"}).join(" || ");if("click"===t.command)return{...t,positionals:[a]};if("fill"===t.command){let e=to(t);if(!e)continue;return{...t,positionals:[a,e]}}if("get"===t.command){let e=t.positionals?.[0];if("text"!==e&&"attrs"!==e)continue;return{...t,positionals:[e,a]}}if("is"===t.command){let{predicate:e,split:i}=tT(t.positionals);if(!e)continue;let r=i?.rest.join(" ").trim()??"",n=[e,a];return"text"===e&&r.length>0&&n.push(r),{...t,positionals:n}}if("wait"===t.command){let{selectorTimeout:e}=t1(t.positionals??[]),i=[a];return e&&i.push(e),{...t,positionals:i}}}return null}async function t0(e,t,i,r,a,n){let o=await a(e.device,"snapshot",[],t.flags?.out,{...tu(i,{...t.flags??{},snapshotInteractiveOnly:r,snapshotCompact:r},e.appBundleId,e.trace?.outPath)}),s=o?.nodes??[],l={nodes:tp(t.flags?.snapshotRaw?s:ty(s)),truncated:o?.truncated,createdAt:Date.now(),backend:o?.backend};return e.snapshot=l,n.set(e.name,e),l}function t1(e){if(0===e.length)return{selectorExpression:null,selectorTimeout:null};let t=e[e.length-1],i=/^\d+$/.test(t??""),r=tC(i?e.slice(0,-1):e.slice());return!r||r.rest.length>0?{selectorExpression:null,selectorTimeout:null}:{selectorExpression:r.selectorExpression,selectorTimeout:i?t:null}}function t2(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function t3(e){if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}async function t4(e){let{req:t,sessionName:i,logPath:r,sessionStore:a}=e,n=t.command;if("snapshot"===n){let{session:e,device:n}=await t5(a,i,t.flags);if(!tn("snapshot",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"snapshot is only supported on iOS simulators in v1"}};let o=e?.appBundleId,s=t.flags?.snapshotScope;if(s&&s.trim().startsWith("@")){if(!e?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let t=tm(s.trim());if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${s}`}};let i=th(e.snapshot.nodes,t),r=i?tv(i,e.snapshot.nodes):void 0;if(!r)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${s} not found or has no label`}};s=r}let l=await tr(n,"snapshot",[],t.flags?.out,{...tu(r,{...t.flags,snapshotScope:s},o,e?.trace?.outPath)}),c=l?.nodes??[],d=tp(t.flags?.snapshotRaw?c:ty(c)),u={nodes:d,truncated:l?.truncated,createdAt:Date.now(),backend:l?.backend},f=e?{...e,snapshot:u}:{name:i,device:n,createdAt:Date.now(),appBundleId:o,snapshot:u,actions:[]};return t8(a,f,t,{nodes:d.length,truncated:l?.truncated??!1}),a.set(i,f),{ok:!0,data:{nodes:d,truncated:l?.truncated??!1,appName:f.appBundleId?f.appName??f.appBundleId:void 0,appBundleId:f.appBundleId}}}if("wait"===n){let e,n,{session:o,device:s}=await t5(a,i,t.flags),l=function(e){if(0===e.length)return null;let t=t3(e[0]);if(null!==t)return{kind:"sleep",durationMs:t};if("text"===e[0]){let t=t3(e[e.length-1]);return{kind:"text",text:(null!==t?e.slice(1,-1).join(" "):e.slice(1).join(" ")).trim(),timeoutMs:t}}if(e[0].startsWith("@")){let t=t3(e[e.length-1]);return{kind:"ref",rawRef:e[0],timeoutMs:t}}let i=t3(e[e.length-1]),r=tC(null!==i?e.slice(0,-1):e.slice());if(r&&0===r.rest.length){let e=tM(r.selectorExpression);if(e)return{kind:"selector",selector:e,selectorExpression:r.selectorExpression,timeoutMs:i}}return{kind:"text",text:(null!==i?e.slice(0,-1).join(" "):e.join(" ")).trim(),timeoutMs:i}}(t.positionals??[]);if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires a duration or text"}};if("sleep"===l.kind)return await new Promise(e=>setTimeout(e,l.durationMs)),t8(a,o,t,{waitedMs:l.durationMs}),{ok:!0,data:{waitedMs:l.durationMs}};if(!tn("wait",s))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"wait is not supported on this device"}};if("selector"===l.kind){let e=l.timeoutMs??1e4,n=Date.now();for(;Date.now()-n<e;){let e=await tr(s,"snapshot",[],t.flags?.out,{...tu(r,{...t.flags,snapshotInteractiveOnly:!1,snapshotCompact:!1},o?.appBundleId,o?.trace?.outPath)}),c=e?.nodes??[],d=tp(t.flags?.snapshotRaw?c:ty(c));o&&(o.snapshot={nodes:d,truncated:e?.truncated,createdAt:Date.now(),backend:e?.backend},a.set(i,o));let u=tR(d,l.selector,{platform:s.platform});if(u)return t8(a,o,t,{selector:u.selector.raw,waitedMs:Date.now()-n}),{ok:!0,data:{selector:u.selector.raw,waitedMs:Date.now()-n}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:`wait timed out for selector: ${l.selectorExpression}`}}}if("ref"===l.kind){if(!o?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let t=tm(l.rawRef);if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${l.rawRef}`}};let i=th(o.snapshot.nodes,t),r=i?tv(i,o.snapshot.nodes):void 0;if(!r)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${l.rawRef} not found or has no label`}};e=r,n=l.timeoutMs}else e=l.text,n=l.timeoutMs;if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires text"}};let c=n??1e4,d=Date.now();for(;Date.now()-d<c;){if("ios"===s.platform&&"simulator"===s.kind){let i=await eH(s,{command:"findText",text:e,appBundleId:o?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:o?.trace?.outPath});if(i?.found)return t8(a,o,t,{text:e,waitedMs:Date.now()-d}),{ok:!0,data:{text:e,waitedMs:Date.now()-d}}}else if("android"===s.platform&&tg(tp((await em(s,{scope:e})).nodes??[]),e))return t8(a,o,t,{text:e,waitedMs:Date.now()-d}),{ok:!0,data:{text:e,waitedMs:Date.now()-d}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:`wait timed out for text: ${e}`}}}if("alert"===n){let{session:e,device:n}=await t5(a,i,t.flags),o=(t.positionals?.[0]??"get").toLowerCase();if(!tn("alert",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"alert is only supported on iOS simulators in v1"}};if("wait"===o){let i=t3(t.positionals?.[1])??1e4,o=Date.now();for(;Date.now()-o<i;){try{let i=await eH(n,{command:"alert",action:"get",appBundleId:e?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:e?.trace?.outPath});return t8(a,e,t,i),{ok:!0,data:i}}catch{}await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"alert wait timed out"}}}let s=await eH(n,{command:"alert",action:"accept"===o||"dismiss"===o?o:"get",appBundleId:e?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:e?.trace?.outPath});return t8(a,e,t,s),{ok:!0,data:s}}if("settings"===n){let e=t.positionals?.[0],n=t.positionals?.[1];if(!e||!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"settings requires <wifi|airplane|location> <on|off>"}};let{session:o,device:s}=await t5(a,i,t.flags),l=o?.appBundleId,c=await tr(s,"settings",[e,n,l??""],t.flags?.out,{...tu(r,t.flags,l,o?.trace?.outPath)});return t8(a,o,t,c??{setting:e,state:n}),{ok:!0,data:c??{setting:e,state:n}}}return null}async function t5(e,t,i){let r=e.get(t),a=r?.device??await ti(i??{});return r||await tf(a),{session:r,device:a}}function t8(e,t,i,r){t&&e.recordAction(t,{command:i.command,positionals:i.positionals??[],flags:i.flags??{},result:r})}function t7(e,t,i,r={}){let a=t9(i);if(!a)return{matches:[],score:0};let n=0,o=[];for(let i of e){if(r.requireRect&&!i.rect)continue;let e=function(e,t,i){switch(t){case"role":return function(e,t){let i=function(e){let t=e.trim();return t?((t=(t.split(".").pop()??t).replace(/XCUIElementType/gi,"").toLowerCase()).startsWith("ax")&&(t=t.replace(/^ax/,"")),t):""}(e??"");return i?i===t?2:+!!i.includes(t):0}(e.type,i);case"label":return t6(e.label,i);case"value":return t6(e.value,i);case"id":return t6(e.identifier,i);default:return Math.max(t6(e.label,i),t6(e.value,i),t6(e.identifier,i))}}(i,t,a);if(!(e<=0)){if(e>n){n=e,o.length=0,o.push(i);continue}e===n&&o.push(i)}}return{matches:o,score:n}}function t6(e,t){let i=t9(e??"");return i?i===t?2:+!!i.includes(t):0}function t9(e){return e.trim().toLowerCase().replace(/\s+/g," ")}async function ie(e){let{req:t,sessionName:i,logPath:r,sessionStore:a,invoke:n}=e,o=t.command;if("find"!==o)return null;let s=t.positionals??[];if(0===s.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a locator or text"}};let{locator:l,query:c,action:d,value:u,timeoutMs:f}=function(e){let t="any",i=0;["text","label","value","role","id"].includes(e[0])&&(t=e[0],i=1);let r=e[i]??"",a=e.slice(i+1);if(0===a.length)return{locator:t,query:r,action:"click"};let n=a[0].toLowerCase();if("get"===n){let e=a[1]?.toLowerCase();if("text"===e)return{locator:t,query:r,action:"get_text"};if("attrs"===e)return{locator:t,query:r,action:"get_attrs"};throw new p("INVALID_ARGS","find get only supports text or attrs")}if("wait"===n)return{locator:t,query:r,action:"wait",timeoutMs:t3(a[1])??void 0};if("exists"===n)return{locator:t,query:r,action:"exists"};if("click"===n)return{locator:t,query:r,action:"click"};if("focus"===n)return{locator:t,query:r,action:"focus"};if("fill"===n)return{locator:t,query:r,action:"fill",value:a.slice(1).join(" ")};if("type"===n)return{locator:t,query:r,action:"type",value:a.slice(1).join(" ")};throw new p("INVALID_ARGS",`Unsupported find action: ${a[0]}`)}(s);if(!c)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a value"}};let m=a.get(i);if(!m&&"exists"!==d&&"wait"!==d&&"get_text"!==d&&"get_attrs"!==d)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let h=m?.device??await ti(t.flags??{});m||await tf(h);let w=m?.appBundleId,g="role"!==l?c:void 0,v="click"===d||"focus"===d||"fill"===d||"type"===d,A=0,y=null,I=async()=>{let e=Date.now();if(y&&e-A<750)return{nodes:y};let n=await tr(h,"snapshot",[],t.flags?.out,{...tu(r,{...t.flags,snapshotScope:g,snapshotInteractiveOnly:v,snapshotCompact:v},w,m?.trace?.outPath)}),o=n?.nodes??[],s=tp(t.flags?.snapshotRaw?o:ty(o));return A=e,y=s,m&&(m.snapshot={nodes:s,truncated:n?.truncated,createdAt:Date.now(),backend:n?.backend},a.set(i,m)),{nodes:s,truncated:n?.truncated,backend:n?.backend}};if("wait"===d){let e=f??1e4,i=Date.now();for(;Date.now()-i<e;){let{nodes:e}=await I();if(t7(e,l,c,{requireRect:!1}).matches[0])return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{found:!0,waitedMs:Date.now()-i}}),{ok:!0,data:{found:!0,waitedMs:Date.now()-i}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"find wait timed out"}}}let{nodes:b}=await I(),N=t7(b,l,c,{requireRect:v});if(v&&N.matches.length>1){let e=N.matches.slice(0,8).map(e=>{let t=tN(e)||e.label||e.identifier||e.type||"";return`@${e.ref}${t?`(${t})`:""}`});return{ok:!1,error:{code:"AMBIGUOUS_MATCH",message:`find matched ${N.matches.length} elements for ${l} "${c}". Use a more specific locator or selector.`,details:{locator:l,query:c,matches:N.matches.length,candidates:e}}}}let S=N.matches[0]??null;if(!S)return{ok:!1,error:{code:"COMMAND_FAILED",message:"find did not match any element"}};let D="click"===d||"focus"===d||"fill"===d||"type"===d?function(e,t){if(t.hittable)return t;let i=t,r=new Set;for(;void 0!==i.parentIndex&&!r.has(i.ref);){r.add(i.ref);let t=e[i.parentIndex];if(!t)break;if(t.hittable)return t;i=t}return null}(b,S)??S:S,x=`@${D.ref}`,k={...t.flags??{},noRecord:!0};if("exists"===d)return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{found:!0}}),{ok:!0,data:{found:!0}};if("get_text"===d){let e=tN(S);return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:x,action:"get text",text:e}}),{ok:!0,data:{ref:x,text:e,node:S}}}if("get_attrs"===d)return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:x,action:"get attrs"}}),{ok:!0,data:{ref:x,node:S}};if("click"===d){let e=await n({token:t.token,session:i,command:"click",positionals:[x],flags:k});return e.ok&&m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:x,action:"click"}}),e}if("fill"===d){if(!u)return{ok:!1,error:{code:"INVALID_ARGS",message:"find fill requires text"}};let e=await n({token:t.token,session:i,command:"fill",positionals:[x,u],flags:k});return e.ok&&m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:x,action:"fill"}}),e}if("focus"===d){let e=S.rect?tw(S.rect):null;if(!e)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};let i=await tr(h,"focus",[String(e.x),String(e.y)],t.flags?.out,{...tu(r,t.flags,m?.appBundleId,m?.trace?.outPath)});return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:x,action:"focus"}}),{ok:!0,data:i??{ref:x}}}if("type"===d){if(!u)return{ok:!1,error:{code:"INVALID_ARGS",message:"find type requires text"}};let e=S.rect?tw(S.rect):null;if(!e)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};await tr(h,"focus",[String(e.x),String(e.y)],t.flags?.out,{...tu(r,t.flags,m?.appBundleId,m?.trace?.outPath)});let i=await tr(h,"type",[u],t.flags?.out,{...tu(r,t.flags,m?.appBundleId,m?.trace?.outPath)});return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:x,action:"type"}}),{ok:!0,data:i??{ref:x}}}return null}async function it(e){let{req:t,sessionName:i,sessionStore:r}=e,a=t.command;if("record"===a){let e=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"record requires start|stop"}};let o=r.get(i),s=o?.device??await ti(t.flags??{});o||await tf(s);let l=o??{name:i,device:s,createdAt:Date.now(),actions:[]};if("start"===e){if(l.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"recording already in progress"}};let e=t.positionals?.[1]??`./recording-${Date.now()}.mp4`,o=n.resolve(e),c=n.dirname(o);if(u.existsSync(c)||u.mkdirSync(c,{recursive:!0}),!tn("record",s))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"record is only supported on iOS simulators in v1"}};if("ios"===s.platform){let{child:e,wait:t}=d("xcrun",["simctl","io",s.id,"recordVideo",o],{allowFailure:!0});l.recording={platform:"ios",outPath:o,child:e,wait:t}}else{let e=`/sdcard/agent-device-recording-${Date.now()}.mp4`,{child:t,wait:i}=d("adb",["-s",s.id,"shell","screenrecord",e],{allowFailure:!0});l.recording={platform:"android",outPath:o,remotePath:e,child:t,wait:i}}return r.set(i,l),r.recordAction(l,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"start"}}),{ok:!0,data:{recording:"started",outPath:e}}}if(!l.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active recording"}};let c=l.recording;c.child.kill("SIGINT");try{await c.wait}catch{}if("android"===c.platform&&c.remotePath)try{await m("adb",["-s",s.id,"pull",c.remotePath,c.outPath],{allowFailure:!0}),await m("adb",["-s",s.id,"shell","rm","-f",c.remotePath],{allowFailure:!0})}catch{}return l.recording=void 0,r.recordAction(l,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"stop",outPath:c.outPath}}),{ok:!0,data:{recording:"stopped",outPath:c.outPath}}}if("trace"===a){let e=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"trace requires start|stop"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}};if("start"===e){if(o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"trace already in progress"}};let e=t.positionals?.[1]??r.defaultTracePath(o),i=tc.expandHome(e);return u.mkdirSync(n.dirname(i),{recursive:!0}),u.appendFileSync(i,""),o.trace={outPath:i,startedAt:Date.now()},r.recordAction(o,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"start",outPath:i}}),{ok:!0,data:{trace:"started",outPath:i}}}if(!o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active trace"}};let s=o.trace.outPath;if(t.positionals?.[1]){let e=tc.expandHome(t.positionals[1]);u.mkdirSync(n.dirname(e),{recursive:!0}),u.existsSync(s)?u.renameSync(s,e):u.appendFileSync(e,""),s=e}return o.trace=void 0,r.recordAction(o,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"stop",outPath:s}}),{ok:!0,data:{trace:"stopped",outPath:s}}}return null}async function ii(e){let{req:t,sessionName:i,sessionStore:r,contextFromFlags:a}=e,n=t.command;if("click"===n){let e=r.get(i);if(!e)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=t.positionals?.[0]??"";if(o.startsWith("@")){if(!e.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=tm(o);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let s=th(e.snapshot.nodes,i);if(!s?.rect&&t.positionals.length>1){let i=t.positionals.slice(1).join(" ").trim();i.length>0&&(s=tg(e.snapshot.nodes,i))}if(!s?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o} not found or has no bounds`}};let l=tv(s,e.snapshot.nodes),c=tF(s,e.device.platform,{action:"click"}),{x:d,y:u}=tw(s.rect);return await tr(e.device,"press",[String(d),String(u)],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)}),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,x:d,y:u,refLabel:l,selectorChain:c}}),{ok:!0,data:{ref:i,x:d,y:u}}}let s=(t.positionals??[]).join(" ").trim();if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires @ref or selector expression"}};let l=t_(s),c=await ir(e,t.flags,r,a,{interactiveOnly:!0}),d=tL(c.nodes,l,{platform:e.device.platform,requireRect:!0,requireUnique:!0,disambiguateAmbiguous:!0});if(!d||!d.node.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(l,d?.diagnostics??[],{unique:!0})}};let{x:u,y:f}=tw(d.node.rect);await tr(e.device,"press",[String(u),String(f)],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)});let p=tF(d.node,e.device.platform,{action:"click"}),m=tv(d.node,c.nodes);return r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{x:u,y:f,selector:d.selector.raw,selectorChain:p,refLabel:m}}),{ok:!0,data:{selector:d.selector.raw,x:u,y:f}}}if("fill"===n){let e=r.get(i);if(t.positionals?.[0]?.startsWith("@")){if(!e?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=tm(t.positionals[0]);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let o=t.positionals.length>=3?t.positionals[1]:"",s=t.positionals.length>=3?t.positionals.slice(2).join(" "):t.positionals.slice(1).join(" ");if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after ref"}};let l=th(e.snapshot.nodes,i);if(!l?.rect&&o&&(l=tg(e.snapshot.nodes,o)),!l?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${t.positionals[0]} not found or has no bounds`}};let c=l.type??"",d=c&&!tb(c,e.device.platform)?`fill target ${t.positionals[0]} resolved to "${c}", attempting fill anyway.`:void 0,u=tv(l,e.snapshot.nodes),f=tF(l,e.device.platform,{action:"fill"}),{x:p,y:m}=tw(l.rect),h={...await tr(e.device,"fill",[String(p),String(m),s],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)})??{ref:i,x:p,y:m}};return d&&(h.warning=d),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{...h,refLabel:u,selectorChain:f}}),{ok:!0,data:h}}if(!e)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=tC(t.positionals??[],{preferTrailingValue:!0});if(o){if(0===o.rest.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after selector"}};let i=o.rest.join(" ").trim();if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after selector"}};let s=t_(o.selectorExpression),l=await ir(e,t.flags,r,a,{interactiveOnly:!0}),c=tL(l.nodes,s,{platform:e.device.platform,requireRect:!0,requireUnique:!0,disambiguateAmbiguous:!0});if(!c||!c.node.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(s,c?.diagnostics??[],{unique:!0})}};let d=c.node,u=d.type??"",f=u&&!tb(u,e.device.platform)?`fill target ${c.selector.raw} resolved to "${u}", attempting fill anyway.`:void 0,{x:p,y:m}=tw(c.node.rect),h=await tr(e.device,"fill",[String(p),String(m),i],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)}),w=tF(d,e.device.platform,{action:"fill"}),g={...h??{x:p,y:m,text:i},selector:c.selector.raw,selectorChain:w,refLabel:tv(d,l.nodes)};return f&&(g.warning=f),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:g}),{ok:!0,data:g}}return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires x y text, @ref text, or selector text"}}}if("get"===n){let e=t.positionals?.[0];if("text"!==e&&"attrs"!==e)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let s=t.positionals?.[1]??"";if(s.startsWith("@")){if(!o.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=tm(s??"");if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let a=th(o.snapshot.nodes,i);if(!a&&t.positionals.length>2){let e=t.positionals.slice(2).join(" ").trim();e.length>0&&(a=tg(o.snapshot.nodes,e))}if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${s} not found`}};let l=tF(a,o.device.platform,{action:"get"});if("attrs"===e)return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,selectorChain:l}}),{ok:!0,data:{ref:i,node:a}};let c=tN(a);return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,text:c,refLabel:c||void 0,selectorChain:l}}),{ok:!0,data:{ref:i,text:c,node:a}}}let l=t.positionals.slice(1).join(" ").trim();if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"get requires @ref or selector expression"}};let c=t_(l),d=tL((await ir(o,t.flags,r,a,{interactiveOnly:!1})).nodes,c,{platform:o.device.platform,requireRect:!1,requireUnique:!0});if(!d)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(c,[],{unique:!0})}};let u=d.node,f=tF(u,o.device.platform,{action:"get"});if("attrs"===e)return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{selector:d.selector.raw,selectorChain:f}}),{ok:!0,data:{selector:d.selector.raw,node:u}};let p=tN(u);return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{text:p,refLabel:p||void 0,selector:d.selector.raw,selectorChain:f}}),{ok:!0,data:{selector:d.selector.raw,text:p,node:u}}}if("is"===n){let e=(t.positionals?.[0]??"").toLowerCase();if(!["visible","hidden","exists","editable","selected","text"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"is requires predicate: visible|hidden|exists|editable|selected|text"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};if(!tn("is",o.device))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"is is not supported on this device"}};let{split:s}=tT(t.positionals);if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"is requires a selector expression"}};let l=s.rest.join(" ").trim();if("text"===e&&!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"is text requires expected text value"}};if("text"!==e&&s.rest.length>0)return{ok:!1,error:{code:"INVALID_ARGS",message:`is ${e} does not accept trailing values`}};let c=t_(s.selectorExpression),d=await ir(o,t.flags,r,a,{interactiveOnly:!1});if("exists"===e){let i=tR(d.nodes,c,{platform:o.device.platform});return i?(r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{predicate:e,selector:i.selector.raw,selectorChain:c.selectors.map(e=>e.raw),pass:!0,matches:i.matches}}),{ok:!0,data:{predicate:e,pass:!0,selector:i.selector.raw,matches:i.matches}}):{ok:!1,error:{code:"COMMAND_FAILED",message:tE(c,[],{unique:!1})}}}let u=tL(d.nodes,c,{platform:o.device.platform,requireUnique:!0});if(!u)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(c,[],{unique:!0})}};let f=function(e){let{predicate:t,node:i,expectedText:r,platform:a}=e,n=tN(i),o=!1;switch(t){case"visible":o=tP(i);break;case"hidden":o=!tP(i);break;case"editable":o=t$(i,a);break;case"selected":o=!0===i.selected;break;case"text":o=n===(r??"")}let s="text"===t?`expected="${r??""}" actual="${n}"`:`actual=${JSON.stringify({visible:tP(i),editable:t$(i,a),selected:!0===i.selected})}`;return{pass:o,actualText:n,details:s}}({predicate:e,node:u.node,expectedText:l,platform:o.device.platform});return f.pass?(r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{predicate:e,selector:u.selector.raw,selectorChain:c.selectors.map(e=>e.raw),pass:!0,text:"text"===e?f.actualText:void 0}}),{ok:!0,data:{predicate:e,pass:!0,selector:u.selector.raw}}):{ok:!1,error:{code:"COMMAND_FAILED",message:`is ${e} failed for selector ${u.selector.raw}: ${f.details}`}}}return null}async function ir(e,t,i,r,a){let n=await tr(e.device,"snapshot",[],t?.out,{...r({...t??{},snapshotInteractiveOnly:a.interactiveOnly,snapshotCompact:a.interactiveOnly},e.appBundleId,e.trace?.outPath)}),o=n?.nodes??[];return e.snapshot={nodes:tp(t?.snapshotRaw?o:ty(o)),truncated:n?.truncated,createdAt:Date.now(),backend:n?.backend},i.set(e.name,e),e.snapshot}let ia=n.join(f.homedir(),".agent-device"),io=n.join(ia,"daemon.json"),is=n.join(ia,"daemon.log"),il=new tc(n.join(ia,"sessions")),ic=g(),id=i.randomBytes(24).toString("hex"),iu=new Set(["session_list","devices"]);function ip(e,t,i){return tu(is,e,t,i)}async function im(e){if(e.token!==id)return{ok:!1,error:{code:"UNAUTHORIZED",message:"Invalid token"}};let t=e.command,i=function(e,t){var i;let r,a=e.session||"default";if(i=e,"string"==typeof(r=i.flags?.session)&&r.trim().length>0||"default"!==a||t.has(a))return a;let n=t.toArray();return 1===n.length?n[0].name:a}(e,il),r=il.get(i);r&&!iu.has(t)&&function(e,t){if(!t)return;let i=[],r=e.device;if(t.platform&&t.platform!==r.platform&&i.push(`--platform=${t.platform}`),t.udid&&("ios"!==r.platform||t.udid!==r.id)&&i.push(`--udid=${t.udid}`),t.serial&&("android"!==r.platform||t.serial!==r.id)&&i.push(`--serial=${t.serial}`),0!==i.length){var a;let t,r,n;throw new p("INVALID_ARGS",`Session "${e.name}" is bound to ${(t=(a=e).device.platform,r=a.device.name.trim(),n=a.device.id,`${t} device "${r}" (${n})`)} and cannot be used with ${i.join(", ")}. Use a different --session name or close this session first.`)}}(r,e.flags);let a=await tY({req:e,sessionName:i,logPath:is,sessionStore:il,invoke:im});if(a)return a;let n=await t4({req:e,sessionName:i,logPath:is,sessionStore:il});if(n)return n;let o=await it({req:e,sessionName:i,sessionStore:il});if(o)return o;let s=await ie({req:e,sessionName:i,logPath:is,sessionStore:il,invoke:im});if(s)return s;let l=await ii({req:e,sessionName:i,sessionStore:il,contextFromFlags:ip});if(l)return l;let c=il.get(i);if(!c)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};if(!tn(t,c.device))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:`${t} is not supported on this device`}};let d=await tr(c.device,t,e.positionals??[],e.flags?.out,{...ip(e.flags,c.appBundleId,c.trace?.outPath)});return il.recordAction(c,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:d??{}}),{ok:!0,data:d??{}}}(e=h.createServer(e=>{let t="";e.setEncoding("utf8"),e.on("data",async i=>{let r=(t+=i).indexOf("\n");for(;-1!==r;){let i,a=t.slice(0,r).trim();if(t=t.slice(r+1),0===a.length){r=t.indexOf("\n");continue}try{let e=JSON.parse(a);i=await im(e)}catch(t){let e=l(t);i={ok:!1,error:{code:e.code,message:e.message,details:e.details}}}e.write(`${JSON.stringify(i)}
15
- `),r=t.indexOf("\n")}})})).listen(0,"127.0.0.1",()=>{let t=e.address();if("object"==typeof t&&t?.port){var i;i=t.port,u.existsSync(ia)||u.mkdirSync(ia,{recursive:!0}),u.writeFileSync(is,""),u.writeFileSync(io,JSON.stringify({port:i,token:id,pid:process.pid,version:ic},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
16
- `)}}),t=async()=>{for(let e of il.toArray())"ios"===e.device.platform&&"simulator"===e.device.kind&&await eY(e.device.id),il.writeSessionLog(e);e.close(()=>{u.existsSync(io)&&u.unlinkSync(io),process.exit(0)})},process.on("SIGINT",()=>{t()}),process.on("SIGTERM",()=>{t()}),process.on("SIGHUP",()=>{t()}),process.on("uncaughtException",e=>{let i=e instanceof p?e:l(e);process.stderr.write(`Daemon error: ${i.message}
10
+ `,d.appendFileSync(e,o),(0!==i.exitCode||n.length>0)&&(n.length>0&&d.appendFileSync(e,`${n}
11
+ `),0!==i.exitCode&&r.length>0&&d.appendFileSync(e,`${r}
12
+ `))),0!==s.exitCode){let e,t,i=(s.stderr??"").toString(),r=(e=i.toLowerCase()).includes("accessibility permission")?" Enable Accessibility for your terminal in System Settings > Privacy & Security > Accessibility, or use --backend xctest (slower snapshots via XCTest).":e.includes("could not find ios app content")?" AX snapshot sometimes caches empty content. Try restarting the Simulator app.":"",a=!!((t=i.toLowerCase()).includes("could not find ios app content")||t.includes("timeout"));throw new p("COMMAND_FAILED","AX snapshot failed",{stderr:`${i}${r}`,stdout:s.stdout,retryable:a})}return s},{shouldRetry:e=>{var t;return(t=e)instanceof p&&"COMMAND_FAILED"===t.code&&t.details?.retryable===!0}});try{let e=JSON.parse(n.stdout);if(e&&"object"==typeof e&&"root"in e){if(!e.root)throw Error("AX snapshot missing root");i=e.root,r=e.windowFrame??void 0}else i=e}catch(e){throw new p("COMMAND_FAILED","Invalid AX snapshot JSON",{error:String(e)})}let o=i.frame??r,s=[],l=[],c=(e,t)=>{e.frame&&s.push(e.frame);let i=e.frame&&o?{x:e.frame.x-o.x,y:e.frame.y-o.y,width:e.frame.width,height:e.frame.height}:e.frame;for(let r of(l.push({...e,frame:i,children:void 0,depth:t}),e.children??[]))c(r,t+1)};return c(i,0),{nodes:(function(e,t,i){if(!t||0===i.length)return e;let r=1/0,a=1/0;for(let e of i)e.x<r&&(r=e.x),e.y<a&&(a=e.y);return r<=5&&a<=5?e.map(e=>({...e,frame:e.frame?{x:e.frame.x+t.x,y:e.frame.y+t.y,width:e.frame.width,height:e.frame.height}:void 0})):e})(l,o,s).map((e,t)=>({index:t,type:e.subrole??e.role,label:e.label,value:e.value,identifier:e.identifier,rect:e.frame?{x:e.frame.x,y:e.frame.y,width:e.frame.width,height:e.frame.height}:void 0,depth:e.depth}))}}async function tt(){let e=function(){let e=n.dirname(c(import.meta.url));for(let t=0;t<6;t+=1){let t=n.join(e,"package.json");if(d.existsSync(t))return e;e=n.dirname(e)}return process.cwd()}(),t=n.join(e,"ios-runner","AXSnapshot"),i=process.env.AGENT_DEVICE_AX_BINARY;if(i&&d.existsSync(i))return i;let r=n.join(e,"dist","bin","axsnapshot");if(d.existsSync(r))return r;let a=n.join(t,".build","release","axsnapshot");if(d.existsSync(a))return a;let o=await m("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==o.exitCode||!d.existsSync(a))throw new p("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:o.stderr,stdout:o.stdout});return a}async function ti(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await eh();let e=await E();return await v(e,t)}if("ios"===t.platform){let e=await ek();return await v(e,t)}let i=[];try{i.push(...await E())}catch{}try{i.push(...await ek())}catch{}return await v(i,t)}async function tr(e,t,i,r,a){let o=function(e,t){switch(e.platform){case"android":return{open:(t,i)=>X(e,t,i?.activity),openDevice:()=>Y(e),close:t=>Z(e,t),tap:(t,i)=>ei(e,t,i),longPress:(t,i,r)=>eo(e,t,i,r),focus:(t,i)=>el(e,t,i),type:t=>es(e,t),fill:(t,i,r)=>ec(e,t,i,r),scroll:(t,i)=>eu(e,t,i),scrollIntoView:t=>ed(e,t),screenshot:t=>ef(e,t)};case"ios":var i,r;let a;return{open:t=>eL(e,t),openDevice:()=>eR(e),close:t=>eE(e,t),screenshot:t=>e$(e,t),...(i=e,a={verbose:(r=t).verbose,logPath:r.logPath,traceLogPath:r.traceLogPath},{tap:async(e,t)=>{await eH(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a)},longPress:async(e,t,n)=>{await eH(i,{command:"longPress",x:e,y:t,durationMs:n,appBundleId:r.appBundleId},a)},focus:async(e,t)=>{await eH(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a)},type:async e=>{await eH(i,{command:"type",text:e,appBundleId:r.appBundleId},a)},fill:async(e,t,n)=>{await eH(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a),await eH(i,{command:"type",text:n,clearFirst:!0,appBundleId:r.appBundleId},a)},scroll:async(e,t)=>{if(!["up","down","left","right"].includes(e))throw new p("INVALID_ARGS",`Unknown direction: ${e}`);let n=function(e){switch(e){case"up":return"down";case"down":return"up";case"left":return"right";case"right":return"left"}}(e);await eH(i,{command:"swipe",direction:n,appBundleId:r.appBundleId},a)},scrollIntoView:async e=>{for(let t=0;t<8;t+=1){let n=await eH(i,{command:"findText",text:e,appBundleId:r.appBundleId},a);if(n?.found)return{attempts:t+1};await eH(i,{command:"swipe",direction:"up",appBundleId:r.appBundleId},a),await new Promise(e=>setTimeout(e,300))}throw new p("COMMAND_FAILED",`scrollintoview could not find text: ${e}`)}})};default:throw new p("UNSUPPORTED_PLATFORM",`Unsupported platform: ${e.platform}`)}}(e,{appBundleId:a?.appBundleId,verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath});switch(t){case"open":{let e=i[0];if(!e)return await o.openDevice(),{app:null};return await o.open(e,{activity:a?.activity}),{app:e}}case"close":{let e=i[0];if(!e)return{closed:"session"};return await o.close(e),{app:e}}case"press":{let[e,t]=i.map(Number);if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","press requires x y");return await o.tap(e,t),{x:e,y:t}}case"long-press":{let e=Number(i[0]),t=Number(i[1]),r=i[2]?Number(i[2]):void 0;if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","long-press requires x y [durationMs]");return await o.longPress(e,t,r),{x:e,y:t,durationMs:r}}case"focus":{let[e,t]=i.map(Number);if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","focus requires x y");return await o.focus(e,t),{x:e,y:t}}case"type":{let e=i.join(" ");if(!e)throw new p("INVALID_ARGS","type requires text");return await o.type(e),{text:e}}case"fill":{let e=Number(i[0]),t=Number(i[1]),r=i.slice(2).join(" ");if(Number.isNaN(e)||Number.isNaN(t)||!r)throw new p("INVALID_ARGS","fill requires x y text");return await o.fill(e,t,r),{x:e,y:t,text:r}}case"scroll":{let e=i[0],t=i[1]?Number(i[1]):void 0;if(!e)throw new p("INVALID_ARGS","scroll requires direction");return await o.scroll(e,t),{direction:e,amount:t}}case"scrollintoview":{let e=i.join(" ").trim();if(!e)throw new p("INVALID_ARGS","scrollintoview requires text");let t=await o.scrollIntoView(e);if(t?.attempts)return{text:e,attempts:t.attempts};return{text:e}}case"pinch":{let t=Number(i[0]),r=i[1]?Number(i[1]):void 0,n=i[2]?Number(i[2]):void 0;if(Number.isNaN(t)||t<=0)throw new p("INVALID_ARGS","pinch requires scale > 0");return await eH(e,{command:"pinch",scale:t,x:r,y:n,appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{scale:t,x:r,y:n}}case"screenshot":{let e=i[0]??r??`./screenshot-${Date.now()}.png`;return await s.mkdir(n.dirname(e),{recursive:!0}),await o.screenshot(e),{path:e}}case"back":if("ios"===e.platform)return await eH(e,{command:"back",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"back"};return await er(e),{action:"back"};case"home":if("ios"===e.platform)return await eH(e,{command:"home",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"home"};return await ea(e),{action:"home"};case"app-switcher":if("ios"===e.platform)return await eH(e,{command:"appSwitcher",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"app-switcher"};return await en(e),{action:"app-switcher"};case"settings":{let[t,r,n]=i;if("ios"===e.platform)return await eF(e,t,r,n??a?.appBundleId),{setting:t,state:r};return await ep(e,t,r),{setting:t,state:r}}case"snapshot":{let t=a?.snapshotBackend??"xctest";if("ios"===e.platform){if("ax"===t)return{nodes:(await te(e,{traceLogPath:a?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"};let i=await eH(e,{command:"snapshot",appBundleId:a?.appBundleId,interactiveOnly:a?.snapshotInteractiveOnly,compact:a?.snapshotCompact,depth:a?.snapshotDepth,scope:a?.snapshotScope,raw:a?.snapshotRaw},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),r=i.nodes??[];if(0===r.length)try{return{nodes:(await te(e,{traceLogPath:a?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"}}catch{}return{nodes:r,truncated:i.truncated??!1,backend:"xctest"}}let i=await em(e,{interactiveOnly:a?.snapshotInteractiveOnly,compact:a?.snapshotCompact,depth:a?.snapshotDepth,scope:a?.snapshotScope,raw:a?.snapshotRaw});return{nodes:i.nodes??[],truncated:i.truncated??!1,backend:"android"}}default:throw new p("INVALID_ARGS",`Unknown command: ${t}`)}}let ta={alert:{ios:{simulator:!0},android:{}},pinch:{ios:{simulator:!0},android:{}},"app-switcher":{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},apps:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},back:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},boot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},click:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},close:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},fill:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},find:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},focus:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},get:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},is:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},home:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},"long-press":{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},open:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},reinstall:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},press:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},record:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},screenshot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},scroll:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},settings:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},snapshot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},type:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},wait:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}}};function tn(e,t){let i=ta[e];if(!i)return!0;let r=i[t.platform];return!!r&&!0===r[t.kind??"unknown"]}function to(e){let t=e.result?.text;if("string"==typeof t&&t.trim().length>0)return t;let i=e.positionals??[];return 0===i.length?"":i[0].startsWith("@")?i.length>=3?i.slice(2).join(" ").trim():i.slice(1).join(" ").trim():!(i.length>=3)||Number.isNaN(Number(i[0]))||Number.isNaN(Number(i[1]))?i.slice(1).join(" ").trim():i.slice(2).join(" ").trim()}function ts(e){let t=new Set,i=[];for(let r of e)t.has(r)||(t.add(r),i.push(r));return i}function tl(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}class tc{get(e){return this.sessions.get(e)}has(e){return this.sessions.has(e)}set(e,t){this.sessions.set(e,t)}delete(e){return this.sessions.delete(e)}values(){return this.sessions.values()}toArray(){return Array.from(this.sessions.values())}recordAction(e,t){t.flags?.noRecord||(t.flags?.saveScript&&(e.recordSession=!0),e.actions.push({ts:Date.now(),command:t.command,positionals:t.positionals,flags:function(e){if(!e)return{};let{platform:t,device:i,udid:r,serial:a,out:n,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:f,appsMetadata:p,saveScript:m,noRecord:h}=e;return{platform:t,device:i,udid:r,serial:a,out:n,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:f,appsMetadata:p,saveScript:m,noRecord:h}}(t.flags),result:t.result}))}writeSessionLog(e){try{if(!e.recordSession)return;d.existsSync(this.sessionsDir)||d.mkdirSync(this.sessionsDir,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),r=n.join(this.sessionsDir,`${t}-${i}.ad`),a=function(e,t){let i=[],r=e.device.name.replace(/"/g,'\\"'),a=e.device.kind?` kind=${e.device.kind}`:"";for(let n of(i.push(`context platform=${e.device.platform} device="${r}"${a} theme=unknown`),t))n.flags?.noRecord||i.push(function(e){let t=[e.command];if("click"===e.command){let i=e.positionals?.[0];if(i){if(t.push(tu(i)),i.startsWith("@")){let i=e.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push(tu(i))}return t.join(" ")}}if("fill"===e.command){let i=e.positionals?.[0];if(i&&i.startsWith("@")){t.push(tu(i));let r=e.result?.refLabel,a=e.positionals.slice(1).join(" ");return"string"==typeof r&&r.trim().length>0&&t.push(tu(r)),a&&t.push(tu(a)),t.join(" ")}}if("get"===e.command){let i=e.positionals?.[0],r=e.positionals?.[1];if(i&&r){if(t.push(tu(i)),t.push(tu(r)),r.startsWith("@")){let i=e.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push(tu(i))}return t.join(" ")}}if("snapshot"===e.command)return e.flags?.snapshotInteractiveOnly&&t.push("-i"),e.flags?.snapshotCompact&&t.push("-c"),"number"==typeof e.flags?.snapshotDepth&&t.push("-d",String(e.flags.snapshotDepth)),e.flags?.snapshotScope&&t.push("-s",tu(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let i of e.positionals??[])t.push(tu(i));return t.join(" ")}(n));return`${i.join("\n")}
13
+ `}(e,this.buildOptimizedActions(e));d.writeFileSync(r,a)}catch{}}defaultTracePath(e){let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date().toISOString().replace(/[:.]/g,"-");return n.join(this.sessionsDir,`${t}-${i}.trace.log`)}static expandHome(e){return e.startsWith("~/")?n.join(f.homedir(),e.slice(2)):n.resolve(e)}buildOptimizedActions(e){let t=[];for(let i of e.actions){if("snapshot"===i.command)continue;let r=Array.isArray(i.result?.selectorChain)&&i.result?.selectorChain.every(e=>"string"==typeof e)?i.result.selectorChain:[];if(r.length>0&&("click"===i.command||"fill"===i.command||"get"===i.command)){let e=r.join(" || ");if("click"===i.command){t.push({...i,positionals:[e]});continue}if("fill"===i.command){let r=to(i);if(r.length>0){t.push({...i,positionals:[e,r]});continue}}if("get"===i.command){let r=i.positionals?.[0];if("text"===r||"attrs"===r){t.push({...i,positionals:[r,e]});continue}}}if("click"===i.command||"fill"===i.command||"get"===i.command){let r=i.result?.refLabel;"string"==typeof r&&r.trim().length>0&&t.push({ts:i.ts,command:"snapshot",positionals:[],flags:{platform:e.device.platform,snapshotInteractiveOnly:!0,snapshotCompact:!0,snapshotScope:r.trim()},result:{scope:r.trim()}})}t.push(i)}return t}constructor(e){tl(this,"sessions",new Map),tl(this,"sessionsDir",void 0),this.sessionsDir=e}}function tu(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function td(e,t,i,r){return{appBundleId:i,activity:t?.activity,verbose:t?.verbose,logPath:e,traceLogPath:r,snapshotInteractiveOnly:t?.snapshotInteractiveOnly,snapshotCompact:t?.snapshotCompact,snapshotDepth:t?.snapshotDepth,snapshotScope:t?.snapshotScope,snapshotRaw:t?.snapshotRaw,snapshotBackend:t?.snapshotBackend}}async function tf(e){if("ios"===e.platform&&"simulator"===e.kind){let{ensureBootedSimulator:t}=await Promise.resolve().then(()=>({ensureBootedSimulator:eU}));await t(e);return}if("android"===e.platform){let{waitForAndroidBoot:t}=await Promise.resolve().then(()=>({waitForAndroidBoot:T}));await t(e.id)}}function tp(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function tm(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function th(e,t){return e.find(e=>e.ref===t)??null}function tw(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}function tg(e,t){let i=t.toLowerCase();return e.find(e=>{let t=(e.label??"").toLowerCase(),r=(e.value??"").toLowerCase(),a=(e.identifier??"").toLowerCase();return t.includes(i)||r.includes(i)||a.includes(i)})??null}function tv(e,t){let i=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);return i&&ty(i)?i:function(e,t){if(!e.rect)return;let i=e.rect.y+e.rect.height/2,r=null;for(let e of t){if(!e.rect)continue;let t=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);if(!t||!ty(t))continue;let a=Math.abs(e.rect.y+e.rect.height/2-i);(!r||a<r.distance)&&(r={label:t,distance:a})}return r?.label}(e,t)??(i&&ty(i)?i:void 0)}function ty(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}function tA(e){let t=[],i=[];for(let r of e){let e=r.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let a=tI(r.type??""),n=[r.label,r.value,r.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),o=!!n&&ty(n);if(("group"===a||"ioscontentgroup"===a)&&!o){t.push(e);continue}let s=Math.max(0,e-t.length);i.push({...r,depth:s})}return i}function tI(e){let t=e.trim().replace(/XCUIElementType/gi,"").toLowerCase();t.startsWith("ax")&&(t=t.replace(/^ax/,""));let i=Math.max(t.lastIndexOf("."),t.lastIndexOf("/"));return -1!==i&&(t=t.slice(i+1)),t}function tb(e,t){let i=tI(e);return!i||("android"===t?i.includes("edittext")||i.includes("autocompletetextview"):i.includes("textfield")||i.includes("securetextfield")||i.includes("searchfield")||i.includes("textview")||i.includes("textarea")||"search"===i)}function tN(e){return[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??""}async function tS(e,t,i,r){let a=tx(await tr(e,"snapshot",[],r?.out,{...td(t,{...r,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"ax"},void 0,i)}));if(a?.appName||a?.appBundleId)return{appName:a.appName??a.appBundleId??"unknown",appBundleId:a.appBundleId,source:"snapshot-ax"};let n=tx(await tr(e,"snapshot",[],r?.out,{...td(t,{...r,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"xctest"},void 0,i)}));return{appName:n?.appName??n?.appBundleId??"unknown",appBundleId:n?.appBundleId,source:"snapshot-xctest"}}function tx(e){let t=tp(e?.nodes??[]),i=t.find(e=>"application"===tI(e.type??""))??t[0];if(!i)return null;let r=i.label?.trim(),a=i.identifier?.trim();return r||a?{appName:r||void 0,appBundleId:a||void 0}:null}let tk=new Set(["id","role","text","label","value"]),tD=new Set(["visible","hidden","editable","selected","enabled","hittable"]),tO=new Set([...tk,...tD]);function t_(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Selector expression cannot be empty");let i=function(e){let t=[],i="",r=null;for(let a=0;a<e.length;a+=1){let n=e[a];if(('"'===n||"'"===n)&&!tH(e,a)){r?r===n&&(r=null):r=n,i+=n;continue}if(!r&&"|"===n&&"|"===e[a+1]){let r=i.trim();if(!r)throw new p("INVALID_ARGS",`Invalid selector fallback expression: ${e}`);t.push(r),i="",a+=1;continue}i+=n}let a=i.trim();if(!a)throw new p("INVALID_ARGS",`Invalid selector fallback expression: ${e}`);return t.push(a),t}(t);if(0===i.length)throw new p("INVALID_ARGS","Selector expression cannot be empty");return{raw:t,selectors:i.map(e=>(function(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Selector segment cannot be empty");let i=function(e){let t=[],i="",r=null;for(let a=0;a<e.length;a+=1){let n=e[a];if(('"'===n||"'"===n)&&!tH(e,a)){r?r===n&&(r=null):r=n,i+=n;continue}if(!r&&/\s/.test(n)){i.trim().length>0&&t.push(i.trim()),i="";continue}i+=n}if(r)throw new p("INVALID_ARGS",`Unclosed quote in selector: ${e}`);return i.trim().length>0&&t.push(i.trim()),t}(t);if(0===i.length)throw new p("INVALID_ARGS",`Invalid selector segment: ${e}`);return{raw:t,terms:i.map(tB)}})(e))}}function tM(e){try{return t_(e)}catch{return null}}function tL(e,t,i){let r=i.requireRect??!1,a=i.requireUnique??!0,n=i.disambiguateAmbiguous??!1,o=[];for(let s=0;s<t.selectors.length;s+=1){let l=t.selectors[s],c=function(e,t,i){let r=0,a=null,n=null,o=!1;for(let s of e){if(i.requireRect&&!s.rect||!tV(s,t,i.platform))continue;if(r+=1,a||(a=s),!n){n=s;continue}let e=function(e,t){let i=e.depth??0,r=t.depth??0;if(i!==r)return i>r?1:-1;let a=tJ(e),n=tJ(t);return a!==n?a<n?1:-1:0}(s,n);if(e>0){n=s,o=!1;continue}0===e&&(o=!0)}return{count:r,firstNode:a,disambiguated:o?null:n}}(e,l,{platform:i.platform,requireRect:r});if(o.push({selector:l.raw,matches:c.count}),0!==c.count&&c.firstNode){if(a&&1!==c.count){if(!n)continue;let e=c.disambiguated;if(!e)continue;return{node:e,selector:l,selectorIndex:s,matches:c.count,diagnostics:o}}return{node:c.firstNode,selector:l,selectorIndex:s,matches:c.count,diagnostics:o}}}return null}function tR(e,t,i){let r=i.requireRect??!1,a=[];for(let n=0;n<t.selectors.length;n+=1){let o=t.selectors[n],s=function(e,t,i){let r=0;for(let a of e)(!i.requireRect||a.rect)&&tV(a,t,i.platform)&&(r+=1);return r}(e,o,{platform:i.platform,requireRect:r});if(a.push({selector:o.raw,matches:s}),s>0)return{selectorIndex:n,selector:o,matches:s,diagnostics:a}}return null}function tE(e,t,i){let r=i.unique??!0;if(0===t.length)return`Selector did not match: ${e.raw}`;let a=t.map(e=>`${e.selector} -> ${e.matches}`).join(", ");return r?`Selector did not resolve uniquely (${a})`:`Selector did not match (${a})`}function tC(e,t={}){if(0===e.length)return null;let i=t.preferTrailingValue??!1,r=0,a=[];for(;r<e.length&&function(e){let t=e.trim();if(!t)return!1;if("||"===t)return!0;let i=t.indexOf("=");if(-1!==i){let e=t.slice(0,i).trim().toLowerCase();return tO.has(e)}return tO.has(t.toLowerCase())}(e[r]);){r+=1;let t=e.slice(0,r).join(" ").trim();t&&tM(t)&&a.push(r)}if(0===a.length)return null;let n=a[a.length-1];if(i){for(let t=a.length-1;t>=0;t-=1)if(a[t]<e.length){n=a[t];break}}let o=e.slice(0,n).join(" ").trim();return o?{selectorExpression:o,rest:e.slice(n)}:null}function tT(e){let t=e[0]??"",i=tC(e.slice(1),{preferTrailingValue:"text"===t});return{predicate:t,split:i}}function tP(e){return!0===e.hittable||!!e.rect&&e.rect.width>0&&e.rect.height>0}function t$(e,t){return tb(e.type??"",t)&&!1!==e.enabled}function tF(e,t,i={}){let r=[],a=tI(e.type??""),n=tW(e.identifier),o=tW(e.label),s=tW(e.value),l=tW(tN(e)),c="fill"===i.action;n&&r.push(`id=${tq(n)}`),a&&o&&r.push(c?`role=${tq(a)} label=${tq(o)} editable=true`:`role=${tq(a)} label=${tq(o)}`),o&&r.push(c?`label=${tq(o)} editable=true`:`label=${tq(o)}`),s&&r.push(c?`value=${tq(s)} editable=true`:`value=${tq(s)}`),l&&l!==o&&l!==s&&r.push(c?`text=${tq(l)} editable=true`:`text=${tq(l)}`),a&&c&&!r.some(e=>e.includes("editable=true"))&&r.push(`role=${tq(a)} editable=true`);let u=ts(r);return 0===u.length&&a&&u.push(c?`role=${tq(a)} editable=true`:`role=${tq(a)}`),0===u.length&&tP(e)&&u.push("visible=true"),u}function tB(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Empty selector term");let i=t.indexOf("=");if(-1===i){let i=t.toLowerCase();if(!tD.has(i))throw new p("INVALID_ARGS",`Invalid selector term "${e}", expected key=value`);return{key:i,value:!0}}let r=t.slice(0,i).trim().toLowerCase(),a=t.slice(i+1).trim();if(!tO.has(r))throw new p("INVALID_ARGS",`Unknown selector key: ${r}`);if(!a)throw new p("INVALID_ARGS",`Missing selector value for key: ${r}`);if(tD.has(r)){let e,t="true"===(e=tU(a).toLowerCase())||"false"!==e&&null;if(null===t)throw new p("INVALID_ARGS",`Invalid boolean value for ${r}: ${a}`);return{key:r,value:t}}return{key:r,value:tU(a)}}function tV(e,t,i){return t.terms.every(t=>(function(e,t,i){switch(t.key){case"id":return tG(e.identifier,String(t.value));case"role":var r,a;return r=e.type,a=String(t.value),function(e){return tI(e)}(r??"")===function(e){return tI(e)}(a);case"label":return tG(e.label,String(t.value));case"value":return tG(e.value,String(t.value));case"text":{let i=tj(String(t.value));return tj(tN(e))===i}case"visible":return tP(e)===!!t.value;case"hidden":return!tP(e)==!!t.value;case"editable":return t$(e,i)===!!t.value;case"selected":return!0===e.selected==!!t.value;case"enabled":return!1!==e.enabled==!!t.value;case"hittable":return!0===e.hittable==!!t.value;default:return!1}})(e,t,i))}function tU(e){let t=e.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1).replace(/\\(["'])/g,"$1"):t}function tG(e,t){return tj(e??"")===tj(t)}function tj(e){return e.trim().toLowerCase().replace(/\s+/g," ")}function tq(e){return JSON.stringify(e)}function tW(e){if(!e)return null;let t=e.trim();return t||null}function tJ(e){return e.rect?e.rect.width*e.rect.height:1/0}function tH(e,t){let i=0;for(let r=t-1;r>=0&&"\\"===e[r];r-=1)i+=1;return i%2==1}function tz(e){return!!(e?.platform||e?.device||e?.udid||e?.serial)}async function tX(e){let t=e.session?.device??await ti(e.flags??{});return!1!==e.ensureReady&&await e.ensureReadyFn(t),t}let tK={ios:async(e,t,i)=>{let{reinstallIosApp:r}=await Promise.resolve().then(()=>({reinstallIosApp:eP}));return await r(e,t,i)},android:async(e,t,i)=>{let{reinstallAndroidApp:r}=await Promise.resolve().then(()=>({reinstallAndroidApp:et}));return await r(e,t,i)}};async function tY(e){let{req:t,sessionName:i,logPath:r,sessionStore:a,invoke:n,dispatch:o,ensureReady:s,reinstallOps:c=tK}=e,u=o??tr,f=s??tf,m=t.command;if("session_list"===m)return{ok:!0,data:{sessions:a.toArray().map(e=>({name:e.name,platform:e.device.platform,device:e.device.name,id:e.device.id,createdAt:e.createdAt}))}};if("devices"===m)try{let e=[];if(t.flags?.platform==="android"){let{listAndroidDevices:t}=await Promise.resolve().then(()=>({listAndroidDevices:E}));e.push(...await t())}else if(t.flags?.platform==="ios"){let{listIosDevices:t}=await Promise.resolve().then(()=>({listIosDevices:ek}));e.push(...await t())}else{let{listAndroidDevices:t}=await Promise.resolve().then(()=>({listAndroidDevices:E})),{listIosDevices:i}=await Promise.resolve().then(()=>({listIosDevices:ek}));try{e.push(...await t())}catch{}try{e.push(...await i())}catch{}}return{ok:!0,data:{devices:e}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message,details:e.details}}}if("apps"===m){let e=a.get(i),r=t.flags??{};if(!e&&!tz(r))return{ok:!1,error:{code:"INVALID_ARGS",message:"apps requires an active session or an explicit device selector (e.g. --platform ios)."}};let n=await tX({session:e,flags:r,ensureReadyFn:f,ensureReady:!0});if(!tn("apps",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"apps is not supported on this device"}};if("ios"===n.platform){let{listSimulatorApps:e}=await Promise.resolve().then(()=>({listSimulatorApps:eV})),i=await e(n);return t.flags?.appsMetadata?{ok:!0,data:{apps:i}}:{ok:!0,data:{apps:i.map(e=>e.name&&e.name!==e.bundleId?`${e.name} (${e.bundleId})`:e.bundleId)}}}let{listAndroidApps:o,listAndroidAppsMetadata:s}=await Promise.resolve().then(()=>({listAndroidApps:W,listAndroidAppsMetadata:J}));return t.flags?.appsMetadata?{ok:!0,data:{apps:await s(n,t.flags?.appsFilter)}}:{ok:!0,data:{apps:await o(n,t.flags?.appsFilter)}}}if("boot"===m){let e=a.get(i),r=t.flags??{};if(!e&&!tz(r))return{ok:!1,error:{code:"INVALID_ARGS",message:"boot requires an active session or an explicit device selector (e.g. --platform ios)."}};let n=e?.device??await ti(r);return tn("boot",n)?(await f(n),{ok:!0,data:{platform:n.platform,device:n.name,id:n.id,kind:n.kind,booted:!0}}):{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"boot is not supported on this device"}}}if("appstate"===m){let e=a.get(i),n=t.flags??{},o=await tX({session:e,flags:n,ensureReadyFn:f,ensureReady:!0});if("ios"===o.platform){if(e?.appBundleId)return{ok:!0,data:{platform:"ios",appBundleId:e.appBundleId,appName:e.appName??e.appBundleId,source:"session"}};let i=await tS(o,r,e?.trace?.outPath,t.flags);return{ok:!0,data:{platform:"ios",appName:i.appName,appBundleId:i.appBundleId,source:i.source}}}let{getAndroidAppState:s}=await Promise.resolve().then(()=>({getAndroidAppState:H})),l=await s(o);return{ok:!0,data:{platform:"android",package:l.package,activity:l.activity}}}if("reinstall"===m){let e,r=a.get(i),n=t.flags??{};if(!r&&!tz(n))return{ok:!1,error:{code:"INVALID_ARGS",message:"reinstall requires an active session or an explicit device selector (e.g. --platform ios)."}};let o=t.positionals?.[0]?.trim(),s=t.positionals?.[1]?.trim();if(!o||!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"reinstall requires: reinstall <app> <path-to-app-binary>"}};let l=tc.expandHome(s);if(!d.existsSync(l))return{ok:!1,error:{code:"INVALID_ARGS",message:`App binary not found: ${l}`}};let u=await tX({session:r,flags:n,ensureReadyFn:f,ensureReady:!1});if(!tn("reinstall",u))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"reinstall is not supported on this device"}};if("ios"===u.platform){let t=await c.ios(u,o,l);e={platform:"ios",appId:t.bundleId,bundleId:t.bundleId}}else{let t=await c.android(u,o,l);e={platform:"android",appId:t.package,package:t.package}}let p={app:o,appPath:l,...e};return r&&a.recordAction(r,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:p}),{ok:!0,data:p}}if("open"===m){let e;if(a.has(i)){let e,n=a.get(i),o=t.positionals?.[0];if(!n||!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};if("ios"===n.device.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:eM}));e=await t(n.device,o)}catch{e=void 0}await u(n.device,"open",t.positionals??[],t.flags?.out,{...td(r,t.flags,e)});let s={...n,appBundleId:e,appName:o,recordSession:n.recordSession||t.flags?.saveScript===!0,snapshot:void 0};return a.recordAction(s,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:{session:i,appName:o,appBundleId:e}}),a.set(i,s),{ok:!0,data:{session:i,appName:o,appBundleId:e}}}let n=await ti(t.flags??{}),o=a.toArray().find(e=>e.device.id===n.id);if(o)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${o.name}".`,details:{session:o.name,deviceId:n.id,deviceName:n.name}}};let s=t.positionals?.[0];if("ios"===n.platform)try{let{resolveIosApp:i}=await Promise.resolve().then(()=>({resolveIosApp:eM}));e=await i(n,t.positionals?.[0]??"")}catch{e=void 0}await u(n,"open",t.positionals??[],t.flags?.out,{...td(r,t.flags,e)});let l={name:i,device:n,createdAt:Date.now(),appBundleId:e,appName:s,recordSession:t.flags?.saveScript===!0,actions:[]};return a.recordAction(l,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:{session:i}}),a.set(i,l),{ok:!0,data:{session:i}}}if("replay"===m){let e=t.positionals?.[0];if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay requires a path"}};try{let o=tc.expandHome(e),s=d.readFileSync(o,"utf8"),l=s.trimStart()[0];if("{"===l||"["===l)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay accepts .ad script files. JSON replay payloads are no longer supported."}};let c=function(e){let t=[];for(let i of e.split(/\r?\n/)){let e=function(e){let t=e.trim();if(0===t.length||t.startsWith("#"))return null;let i=function(e){let t=[],i=0;for(;i<e.length;){for(;i<e.length&&/\s/.test(e[i]);)i+=1;if(i>=e.length)break;if('"'===e[i]){let r=i+1,a=!1;for(;r<e.length;){let t=e[r];if('"'===t&&!a)break;a="\\"===t&&!a,"\\"!==t&&(a=!1),r+=1}if(r>=e.length)throw new p("INVALID_ARGS",`Invalid replay script line: ${e}`);let n=e.slice(i,r+1);t.push(JSON.parse(n)),i=r+1;continue}let r=i;for(;r<e.length&&!/\s/.test(e[r]);)r+=1;t.push(e.slice(i,r)),i=r}return t}(t);if(0===i.length)return null;let[r,...a]=i;if("context"===r)return null;let n={ts:Date.now(),command:r,positionals:[],flags:{}};if("snapshot"===r){n.positionals=[];for(let e=0;e<a.length;e+=1){let t=a[e];if("-i"===t){n.flags.snapshotInteractiveOnly=!0;continue}if("-c"===t){n.flags.snapshotCompact=!0;continue}if("--raw"===t){n.flags.snapshotRaw=!0;continue}if(("-d"===t||"--depth"===t)&&e+1<a.length){let t=Number(a[e+1]);Number.isFinite(t)&&t>=0&&(n.flags.snapshotDepth=Math.floor(t)),e+=1;continue}if(("-s"===t||"--scope"===t)&&e+1<a.length){n.flags.snapshotScope=a[e+1],e+=1;continue}if("--backend"===t&&e+1<a.length){let t=a[e+1];("ax"===t||"xctest"===t)&&(n.flags.snapshotBackend=t),e+=1}}return n}if("click"===r){if(0===a.length)return n;let e=a[0];return e.startsWith("@")?(n.positionals=[e],a[1]&&(n.result={refLabel:a[1]})):n.positionals=[a.join(" ")],n}if("fill"===r){if(a.length<2)return n.positionals=a,n;let e=a[0];return e.startsWith("@")?(a.length>=3?(n.positionals=[e,a.slice(2).join(" ")],n.result={refLabel:a[1]}):n.positionals=[e,a[1]],n):(n.positionals=[e,a.slice(1).join(" ")],n)}if("get"===r){if(a.length<2)return n.positionals=a,n;let e=a[0],t=a[1];return t.startsWith("@")?(n.positionals=[e,t],a[2]&&(n.result={refLabel:a[2]})):n.positionals=[e,a.slice(1).join(" ")],n}return n.positionals=a,n}(i);e&&t.push(e)}return t}(s),f=t.flags?.replayUpdate===!0,m=0;for(let e=0;e<c.length;e+=1){let s=c[e];if(!s||"replay"===s.command)continue;let l=await n({token:t.token,session:i,command:s.command,positionals:s.positionals??[],flags:s.flags??{}});if(l.ok)continue;if(!f)return tZ(l,s,e,o);let d=await tQ({action:s,sessionName:i,logPath:r,sessionStore:a,dispatch:u});if(!d)return tZ(l,s,e,o);if(c[e]=d,!(l=await n({token:t.token,session:i,command:d.command,positionals:d.positionals??[],flags:d.flags??{}})).ok)return tZ(l,d,e,o);m+=1}if(f&&m>0){let e=a.get(i);!function(e,t,i){let r=[];if(i){let e=i.device.name.replace(/"/g,'\\"'),t=i.device.kind?` kind=${i.device.kind}`:"";r.push(`context platform=${i.device.platform} device="${e}"${t} theme=unknown`)}for(let e of t)r.push(function(e){let t=[e.command];if("snapshot"===e.command)return e.flags?.snapshotInteractiveOnly&&t.push("-i"),e.flags?.snapshotCompact&&t.push("-c"),"number"==typeof e.flags?.snapshotDepth&&t.push("-d",String(e.flags.snapshotDepth)),e.flags?.snapshotScope&&t.push("-s",t2(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let i of e.positionals??[])t.push(t2(i));return t.join(" ")}(e));let a=`${r.join("\n")}
14
+ `,n=`${e}.tmp-${process.pid}-${Date.now()}`;d.writeFileSync(n,a),d.renameSync(n,e)}(o,c,e)}return{ok:!0,data:{replayed:c.length,healed:m,session:i}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===m){let e=a.get(i);return e?(t.positionals&&t.positionals.length>0&&await u(e.device,"close",t.positionals??[],t.flags?.out,{...td(r,t.flags,e.appBundleId,e.trace?.outPath)}),"ios"===e.device.platform&&"simulator"===e.device.kind&&await eY(e.device.id),a.recordAction(e,{command:m,positionals:t.positionals??[],flags:t.flags??{},result:{session:i}}),t.flags?.saveScript&&(e.recordSession=!0),a.writeSessionLog(e),a.delete(i),{ok:!0,data:{session:i}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}return null}function tZ(e,t,i,r){var a;let n;if(e.ok)return e;let o=i+1,s=(n=((a=t).positionals??[]).map(e=>{let t=e.trim();return/^-?\d+(\.\d+)?$/.test(t)||t.startsWith("@")?t:JSON.stringify(t)}),[a.command,...n].join(" ")),l={...e.error.details??{},replayPath:r,step:o,action:t.command,positionals:t.positionals??[]};return{ok:!1,error:{code:e.error.code,message:`Replay failed at step ${o} (${s}): ${e.error.message}`,details:l}}}async function tQ(e){let{action:t,sessionName:i,logPath:r,sessionStore:a,dispatch:n}=e;if(!["click","fill","get","is","wait"].includes(t.command))return null;let o=a.get(i);if(!o)return null;let s="click"===t.command||"fill"===t.command,l="click"===t.command||"fill"===t.command||"get"===t.command&&t.positionals?.[0]==="text",c=await t0(o,t,r,s,n,a);for(let e of function(e){let t=[],i=Array.isArray(e.result?.selectorChain)&&e.result?.selectorChain.every(e=>"string"==typeof e)?e.result.selectorChain:[];if(t.push(...i),"click"===e.command){let i=e.positionals?.[0]??"";i&&!i.startsWith("@")&&t.push(e.positionals.join(" "))}if("fill"===e.command){let i=e.positionals?.[0]??"";i&&!i.startsWith("@")&&Number.isNaN(Number(i))&&t.push(i)}if("get"===e.command){let i=e.positionals?.[1]??"";i&&!i.startsWith("@")&&t.push(e.positionals.slice(1).join(" "))}if("is"===e.command){let{split:i}=tT(e.positionals);i&&t.push(i.selectorExpression)}if("wait"===e.command){let{selectorExpression:i}=t1(e.positionals??[]);i&&t.push(i)}let r="string"==typeof e.result?.refLabel?e.result.refLabel.trim():"";if(r.length>0){let i=JSON.stringify(r);"fill"===e.command?(t.push(`id=${i} editable=true`),t.push(`label=${i} editable=true`),t.push(`text=${i} editable=true`),t.push(`value=${i} editable=true`)):(t.push(`id=${i}`),t.push(`label=${i}`),t.push(`text=${i}`),t.push(`value=${i}`))}return ts(t).filter(e=>e.trim().length>0)}(t)){let i=tM(e);if(!i)continue;let r=tL(c.nodes,i,{platform:o.device.platform,requireRect:s,requireUnique:!0,disambiguateAmbiguous:l});if(!r)continue;let a=tF(r.node,o.device.platform,{action:"click"===t.command?"click":"fill"===t.command?"fill":"get"}).join(" || ");if("click"===t.command)return{...t,positionals:[a]};if("fill"===t.command){let e=to(t);if(!e)continue;return{...t,positionals:[a,e]}}if("get"===t.command){let e=t.positionals?.[0];if("text"!==e&&"attrs"!==e)continue;return{...t,positionals:[e,a]}}if("is"===t.command){let{predicate:e,split:i}=tT(t.positionals);if(!e)continue;let r=i?.rest.join(" ").trim()??"",n=[e,a];return"text"===e&&r.length>0&&n.push(r),{...t,positionals:n}}if("wait"===t.command){let{selectorTimeout:e}=t1(t.positionals??[]),i=[a];return e&&i.push(e),{...t,positionals:i}}}let u=function(e,t,i){if("get"!==e.command||e.positionals?.[0]!=="text")return null;let r=e.positionals?.[1];if(!r)return null;let a=tM(r);if(!a)return null;let n=new Set,o=!1;for(let e of a.selectors)for(let t of e.terms)"role"===t.key&&"string"==typeof t.value&&n.add(tI(t.value)),("text"===t.key||"label"===t.key||"value"===t.key)&&"string"==typeof t.value&&/^\d+$/.test(t.value.trim())&&(o=!0);if(!o)return null;let s=t.nodes.filter(e=>{let t=tN(e).trim();return!!/^\d+$/.test(t)&&(0===n.size||n.has(tI(e.type??"")))});if(0===s.length||1!==ts(s.map(e=>tN(e).trim())).length)return null;let l=s[0];if(!l)return null;let c=tF(l,i.device.platform,{action:"get"});return 0===c.length?null:{...e,positionals:["text",c.join(" || ")]}}(t,c,o);return u||null}async function t0(e,t,i,r,a,n){let o=await a(e.device,"snapshot",[],t.flags?.out,{...td(i,{...t.flags??{},snapshotInteractiveOnly:r,snapshotCompact:r},e.appBundleId,e.trace?.outPath)}),s=o?.nodes??[],l={nodes:tp(t.flags?.snapshotRaw?s:tA(s)),truncated:o?.truncated,createdAt:Date.now(),backend:o?.backend};return e.snapshot=l,n.set(e.name,e),l}function t1(e){if(0===e.length)return{selectorExpression:null,selectorTimeout:null};let t=e[e.length-1],i=/^\d+$/.test(t??""),r=tC(i?e.slice(0,-1):e.slice());return!r||r.rest.length>0?{selectorExpression:null,selectorTimeout:null}:{selectorExpression:r.selectorExpression,selectorTimeout:i?t:null}}function t2(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function t3(e){if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}async function t4(e){let{req:t,sessionName:i,logPath:r,sessionStore:a}=e,n=t.command;if("snapshot"===n){let{session:e,device:n}=await t5(a,i,t.flags);if(!tn("snapshot",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"snapshot is only supported on iOS simulators in v1"}};let o=e?.appBundleId,s=t.flags?.snapshotScope;if(s&&s.trim().startsWith("@")){if(!e?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let t=tm(s.trim());if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${s}`}};let i=th(e.snapshot.nodes,t),r=i?tv(i,e.snapshot.nodes):void 0;if(!r)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${s} not found or has no label`}};s=r}let l=await tr(n,"snapshot",[],t.flags?.out,{...td(r,{...t.flags,snapshotScope:s},o,e?.trace?.outPath)}),c=l?.nodes??[],u=tp(t.flags?.snapshotRaw?c:tA(c)),d={nodes:u,truncated:l?.truncated,createdAt:Date.now(),backend:l?.backend},f=e?{...e,snapshot:d}:{name:i,device:n,createdAt:Date.now(),appBundleId:o,snapshot:d,actions:[]};return t8(a,f,t,{nodes:u.length,truncated:l?.truncated??!1}),a.set(i,f),{ok:!0,data:{nodes:u,truncated:l?.truncated??!1,appName:f.appBundleId?f.appName??f.appBundleId:void 0,appBundleId:f.appBundleId}}}if("wait"===n){let e,n,{session:o,device:s}=await t5(a,i,t.flags),l=function(e){if(0===e.length)return null;let t=t3(e[0]);if(null!==t)return{kind:"sleep",durationMs:t};if("text"===e[0]){let t=t3(e[e.length-1]);return{kind:"text",text:(null!==t?e.slice(1,-1).join(" "):e.slice(1).join(" ")).trim(),timeoutMs:t}}if(e[0].startsWith("@")){let t=t3(e[e.length-1]);return{kind:"ref",rawRef:e[0],timeoutMs:t}}let i=t3(e[e.length-1]),r=tC(null!==i?e.slice(0,-1):e.slice());if(r&&0===r.rest.length){let e=tM(r.selectorExpression);if(e)return{kind:"selector",selector:e,selectorExpression:r.selectorExpression,timeoutMs:i}}return{kind:"text",text:(null!==i?e.slice(0,-1).join(" "):e.join(" ")).trim(),timeoutMs:i}}(t.positionals??[]);if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires a duration or text"}};if("sleep"===l.kind)return await new Promise(e=>setTimeout(e,l.durationMs)),t8(a,o,t,{waitedMs:l.durationMs}),{ok:!0,data:{waitedMs:l.durationMs}};if(!tn("wait",s))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"wait is not supported on this device"}};if("selector"===l.kind){let e=l.timeoutMs??1e4,n=Date.now();for(;Date.now()-n<e;){let e=await tr(s,"snapshot",[],t.flags?.out,{...td(r,{...t.flags,snapshotInteractiveOnly:!1,snapshotCompact:!1},o?.appBundleId,o?.trace?.outPath)}),c=e?.nodes??[],u=tp(t.flags?.snapshotRaw?c:tA(c));o&&(o.snapshot={nodes:u,truncated:e?.truncated,createdAt:Date.now(),backend:e?.backend},a.set(i,o));let d=tR(u,l.selector,{platform:s.platform});if(d)return t8(a,o,t,{selector:d.selector.raw,waitedMs:Date.now()-n}),{ok:!0,data:{selector:d.selector.raw,waitedMs:Date.now()-n}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:`wait timed out for selector: ${l.selectorExpression}`}}}if("ref"===l.kind){if(!o?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let t=tm(l.rawRef);if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${l.rawRef}`}};let i=th(o.snapshot.nodes,t),r=i?tv(i,o.snapshot.nodes):void 0;if(!r)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${l.rawRef} not found or has no label`}};e=r,n=l.timeoutMs}else e=l.text,n=l.timeoutMs;if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires text"}};let c=n??1e4,u=Date.now();for(;Date.now()-u<c;){if("ios"===s.platform&&"simulator"===s.kind){let i=await eH(s,{command:"findText",text:e,appBundleId:o?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:o?.trace?.outPath});if(i?.found)return t8(a,o,t,{text:e,waitedMs:Date.now()-u}),{ok:!0,data:{text:e,waitedMs:Date.now()-u}}}else if("android"===s.platform&&tg(tp((await em(s,{scope:e})).nodes??[]),e))return t8(a,o,t,{text:e,waitedMs:Date.now()-u}),{ok:!0,data:{text:e,waitedMs:Date.now()-u}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:`wait timed out for text: ${e}`}}}if("alert"===n){let{session:e,device:n}=await t5(a,i,t.flags),o=(t.positionals?.[0]??"get").toLowerCase();if(!tn("alert",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"alert is only supported on iOS simulators in v1"}};if("wait"===o){let i=t3(t.positionals?.[1])??1e4,o=Date.now();for(;Date.now()-o<i;){try{let i=await eH(n,{command:"alert",action:"get",appBundleId:e?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:e?.trace?.outPath});return t8(a,e,t,i),{ok:!0,data:i}}catch{}await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"alert wait timed out"}}}let s=await eH(n,{command:"alert",action:"accept"===o||"dismiss"===o?o:"get",appBundleId:e?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:e?.trace?.outPath});return t8(a,e,t,s),{ok:!0,data:s}}if("settings"===n){let e=t.positionals?.[0],n=t.positionals?.[1];if(!e||!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"settings requires <wifi|airplane|location> <on|off>"}};let{session:o,device:s}=await t5(a,i,t.flags),l=o?.appBundleId,c=await tr(s,"settings",[e,n,l??""],t.flags?.out,{...td(r,t.flags,l,o?.trace?.outPath)});return t8(a,o,t,c??{setting:e,state:n}),{ok:!0,data:c??{setting:e,state:n}}}return null}async function t5(e,t,i){let r=e.get(t),a=r?.device??await ti(i??{});return r||await tf(a),{session:r,device:a}}function t8(e,t,i,r){t&&e.recordAction(t,{command:i.command,positionals:i.positionals??[],flags:i.flags??{},result:r})}function t7(e,t,i,r={}){let a=t9(i);if(!a)return{matches:[],score:0};let n=0,o=[];for(let i of e){if(r.requireRect&&!i.rect)continue;let e=function(e,t,i){switch(t){case"role":return function(e,t){let i=function(e){let t=e.trim();return t?((t=(t.split(".").pop()??t).replace(/XCUIElementType/gi,"").toLowerCase()).startsWith("ax")&&(t=t.replace(/^ax/,"")),t):""}(e??"");return i?i===t?2:+!!i.includes(t):0}(e.type,i);case"label":return t6(e.label,i);case"value":return t6(e.value,i);case"id":return t6(e.identifier,i);default:return Math.max(t6(e.label,i),t6(e.value,i),t6(e.identifier,i))}}(i,t,a);if(!(e<=0)){if(e>n){n=e,o.length=0,o.push(i);continue}e===n&&o.push(i)}}return{matches:o,score:n}}function t6(e,t){let i=t9(e??"");return i?i===t?2:+!!i.includes(t):0}function t9(e){return e.trim().toLowerCase().replace(/\s+/g," ")}async function ie(e){let{req:t,sessionName:i,logPath:r,sessionStore:a,invoke:n}=e,o=t.command;if("find"!==o)return null;let s=t.positionals??[];if(0===s.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a locator or text"}};let{locator:l,query:c,action:u,value:d,timeoutMs:f}=function(e){let t="any",i=0;["text","label","value","role","id"].includes(e[0])&&(t=e[0],i=1);let r=e[i]??"",a=e.slice(i+1);if(0===a.length)return{locator:t,query:r,action:"click"};let n=a[0].toLowerCase();if("get"===n){let e=a[1]?.toLowerCase();if("text"===e)return{locator:t,query:r,action:"get_text"};if("attrs"===e)return{locator:t,query:r,action:"get_attrs"};throw new p("INVALID_ARGS","find get only supports text or attrs")}if("wait"===n)return{locator:t,query:r,action:"wait",timeoutMs:t3(a[1])??void 0};if("exists"===n)return{locator:t,query:r,action:"exists"};if("click"===n)return{locator:t,query:r,action:"click"};if("focus"===n)return{locator:t,query:r,action:"focus"};if("fill"===n)return{locator:t,query:r,action:"fill",value:a.slice(1).join(" ")};if("type"===n)return{locator:t,query:r,action:"type",value:a.slice(1).join(" ")};throw new p("INVALID_ARGS",`Unsupported find action: ${a[0]}`)}(s);if(!c)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a value"}};let m=a.get(i);if(!m&&"exists"!==u&&"wait"!==u&&"get_text"!==u&&"get_attrs"!==u)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let h=m?.device??await ti(t.flags??{});m||await tf(h);let w=m?.appBundleId,g="role"!==l?c:void 0,v="click"===u||"focus"===u||"fill"===u||"type"===u,y=0,A=null,I=async()=>{let e=Date.now();if(A&&e-y<750)return{nodes:A};let n=await tr(h,"snapshot",[],t.flags?.out,{...td(r,{...t.flags,snapshotScope:g,snapshotInteractiveOnly:v,snapshotCompact:v},w,m?.trace?.outPath)}),o=n?.nodes??[],s=tp(t.flags?.snapshotRaw?o:tA(o));return y=e,A=s,m&&(m.snapshot={nodes:s,truncated:n?.truncated,createdAt:Date.now(),backend:n?.backend},a.set(i,m)),{nodes:s,truncated:n?.truncated,backend:n?.backend}};if("wait"===u){let e=f??1e4,i=Date.now();for(;Date.now()-i<e;){let{nodes:e}=await I();if(t7(e,l,c,{requireRect:!1}).matches[0])return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{found:!0,waitedMs:Date.now()-i}}),{ok:!0,data:{found:!0,waitedMs:Date.now()-i}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"find wait timed out"}}}let{nodes:b}=await I(),N=t7(b,l,c,{requireRect:v});if(v&&N.matches.length>1){let e=N.matches.slice(0,8).map(e=>{let t=tN(e)||e.label||e.identifier||e.type||"";return`@${e.ref}${t?`(${t})`:""}`});return{ok:!1,error:{code:"AMBIGUOUS_MATCH",message:`find matched ${N.matches.length} elements for ${l} "${c}". Use a more specific locator or selector.`,details:{locator:l,query:c,matches:N.matches.length,candidates:e}}}}let S=N.matches[0]??null;if(!S)return{ok:!1,error:{code:"COMMAND_FAILED",message:"find did not match any element"}};let x="click"===u||"focus"===u||"fill"===u||"type"===u?function(e,t){if(t.hittable)return t;let i=t,r=new Set;for(;void 0!==i.parentIndex&&!r.has(i.ref);){r.add(i.ref);let t=e[i.parentIndex];if(!t)break;if(t.hittable)return t;i=t}return null}(b,S)??S:S,k=`@${x.ref}`,D={...t.flags??{},noRecord:!0};if("exists"===u)return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{found:!0}}),{ok:!0,data:{found:!0}};if("get_text"===u){let e=tN(S);return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:k,action:"get text",text:e}}),{ok:!0,data:{ref:k,text:e,node:S}}}if("get_attrs"===u)return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:k,action:"get attrs"}}),{ok:!0,data:{ref:k,node:S}};if("click"===u){let e=await n({token:t.token,session:i,command:"click",positionals:[k],flags:D});return e.ok&&m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:k,action:"click"}}),e}if("fill"===u){if(!d)return{ok:!1,error:{code:"INVALID_ARGS",message:"find fill requires text"}};let e=await n({token:t.token,session:i,command:"fill",positionals:[k,d],flags:D});return e.ok&&m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:k,action:"fill"}}),e}if("focus"===u){let e=S.rect?tw(S.rect):null;if(!e)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};let i=await tr(h,"focus",[String(e.x),String(e.y)],t.flags?.out,{...td(r,t.flags,m?.appBundleId,m?.trace?.outPath)});return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:k,action:"focus"}}),{ok:!0,data:i??{ref:k}}}if("type"===u){if(!d)return{ok:!1,error:{code:"INVALID_ARGS",message:"find type requires text"}};let e=S.rect?tw(S.rect):null;if(!e)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};await tr(h,"focus",[String(e.x),String(e.y)],t.flags?.out,{...td(r,t.flags,m?.appBundleId,m?.trace?.outPath)});let i=await tr(h,"type",[d],t.flags?.out,{...td(r,t.flags,m?.appBundleId,m?.trace?.outPath)});return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:k,action:"type"}}),{ok:!0,data:i??{ref:k}}}return null}async function it(e){let{req:t,sessionName:i,sessionStore:r}=e,a=t.command;if("record"===a){let e=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"record requires start|stop"}};let o=r.get(i),s=o?.device??await ti(t.flags??{});o||await tf(s);let l=o??{name:i,device:s,createdAt:Date.now(),actions:[]};if("start"===e){if(l.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"recording already in progress"}};let e=t.positionals?.[1]??`./recording-${Date.now()}.mp4`,o=n.resolve(e),c=n.dirname(o);if(d.existsSync(c)||d.mkdirSync(c,{recursive:!0}),!tn("record",s))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"record is only supported on iOS simulators in v1"}};if("ios"===s.platform){let{child:e,wait:t}=u("xcrun",["simctl","io",s.id,"recordVideo",o],{allowFailure:!0});l.recording={platform:"ios",outPath:o,child:e,wait:t}}else{let e=`/sdcard/agent-device-recording-${Date.now()}.mp4`,{child:t,wait:i}=u("adb",["-s",s.id,"shell","screenrecord",e],{allowFailure:!0});l.recording={platform:"android",outPath:o,remotePath:e,child:t,wait:i}}return r.set(i,l),r.recordAction(l,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"start"}}),{ok:!0,data:{recording:"started",outPath:e}}}if(!l.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active recording"}};let c=l.recording;c.child.kill("SIGINT");try{await c.wait}catch{}if("android"===c.platform&&c.remotePath)try{await m("adb",["-s",s.id,"pull",c.remotePath,c.outPath],{allowFailure:!0}),await m("adb",["-s",s.id,"shell","rm","-f",c.remotePath],{allowFailure:!0})}catch{}return l.recording=void 0,r.recordAction(l,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"stop",outPath:c.outPath}}),{ok:!0,data:{recording:"stopped",outPath:c.outPath}}}if("trace"===a){let e=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"trace requires start|stop"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}};if("start"===e){if(o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"trace already in progress"}};let e=t.positionals?.[1]??r.defaultTracePath(o),i=tc.expandHome(e);return d.mkdirSync(n.dirname(i),{recursive:!0}),d.appendFileSync(i,""),o.trace={outPath:i,startedAt:Date.now()},r.recordAction(o,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"start",outPath:i}}),{ok:!0,data:{trace:"started",outPath:i}}}if(!o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active trace"}};let s=o.trace.outPath;if(t.positionals?.[1]){let e=tc.expandHome(t.positionals[1]);d.mkdirSync(n.dirname(e),{recursive:!0}),d.existsSync(s)?d.renameSync(s,e):d.appendFileSync(e,""),s=e}return o.trace=void 0,r.recordAction(o,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"stop",outPath:s}}),{ok:!0,data:{trace:"stopped",outPath:s}}}return null}async function ii(e){let{req:t,sessionName:i,sessionStore:r,contextFromFlags:a}=e,n=t.command;if("click"===n){let e=r.get(i);if(!e)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=t.positionals?.[0]??"";if(o.startsWith("@")){if(!e.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=tm(o);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let s=th(e.snapshot.nodes,i);if(!s?.rect&&t.positionals.length>1){let i=t.positionals.slice(1).join(" ").trim();i.length>0&&(s=tg(e.snapshot.nodes,i))}if(!s?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o} not found or has no bounds`}};let l=tv(s,e.snapshot.nodes),c=tF(s,e.device.platform,{action:"click"}),{x:u,y:d}=tw(s.rect);return await tr(e.device,"press",[String(u),String(d)],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)}),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,x:u,y:d,refLabel:l,selectorChain:c}}),{ok:!0,data:{ref:i,x:u,y:d}}}let s=(t.positionals??[]).join(" ").trim();if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires @ref or selector expression"}};let l=t_(s),c=await ir(e,t.flags,r,a,{interactiveOnly:!0}),u=tL(c.nodes,l,{platform:e.device.platform,requireRect:!0,requireUnique:!0,disambiguateAmbiguous:!0});if(!u||!u.node.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(l,u?.diagnostics??[],{unique:!0})}};let{x:d,y:f}=tw(u.node.rect);await tr(e.device,"press",[String(d),String(f)],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)});let p=tF(u.node,e.device.platform,{action:"click"}),m=tv(u.node,c.nodes);return r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{x:d,y:f,selector:u.selector.raw,selectorChain:p,refLabel:m}}),{ok:!0,data:{selector:u.selector.raw,x:d,y:f}}}if("fill"===n){let e=r.get(i);if(t.positionals?.[0]?.startsWith("@")){if(!e?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=tm(t.positionals[0]);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let o=t.positionals.length>=3?t.positionals[1]:"",s=t.positionals.length>=3?t.positionals.slice(2).join(" "):t.positionals.slice(1).join(" ");if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after ref"}};let l=th(e.snapshot.nodes,i);if(!l?.rect&&o&&(l=tg(e.snapshot.nodes,o)),!l?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${t.positionals[0]} not found or has no bounds`}};let c=l.type??"",u=c&&!tb(c,e.device.platform)?`fill target ${t.positionals[0]} resolved to "${c}", attempting fill anyway.`:void 0,d=tv(l,e.snapshot.nodes),f=tF(l,e.device.platform,{action:"fill"}),{x:p,y:m}=tw(l.rect),h={...await tr(e.device,"fill",[String(p),String(m),s],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)})??{ref:i,x:p,y:m}};return u&&(h.warning=u),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{...h,refLabel:d,selectorChain:f}}),{ok:!0,data:h}}if(!e)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=tC(t.positionals??[],{preferTrailingValue:!0});if(o){if(0===o.rest.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after selector"}};let i=o.rest.join(" ").trim();if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after selector"}};let s=t_(o.selectorExpression),l=await ir(e,t.flags,r,a,{interactiveOnly:!0}),c=tL(l.nodes,s,{platform:e.device.platform,requireRect:!0,requireUnique:!0,disambiguateAmbiguous:!0});if(!c||!c.node.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(s,c?.diagnostics??[],{unique:!0})}};let u=c.node,d=u.type??"",f=d&&!tb(d,e.device.platform)?`fill target ${c.selector.raw} resolved to "${d}", attempting fill anyway.`:void 0,{x:p,y:m}=tw(c.node.rect),h=await tr(e.device,"fill",[String(p),String(m),i],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)}),w=tF(u,e.device.platform,{action:"fill"}),g={...h??{x:p,y:m,text:i},selector:c.selector.raw,selectorChain:w,refLabel:tv(u,l.nodes)};return f&&(g.warning=f),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:g}),{ok:!0,data:g}}return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires x y text, @ref text, or selector text"}}}if("get"===n){let e=t.positionals?.[0];if("text"!==e&&"attrs"!==e)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let s=t.positionals?.[1]??"";if(s.startsWith("@")){if(!o.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=tm(s??"");if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let a=th(o.snapshot.nodes,i);if(!a&&t.positionals.length>2){let e=t.positionals.slice(2).join(" ").trim();e.length>0&&(a=tg(o.snapshot.nodes,e))}if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${s} not found`}};let l=tF(a,o.device.platform,{action:"get"});if("attrs"===e)return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,selectorChain:l}}),{ok:!0,data:{ref:i,node:a}};let c=tN(a);return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,text:c,refLabel:c||void 0,selectorChain:l}}),{ok:!0,data:{ref:i,text:c,node:a}}}let l=t.positionals.slice(1).join(" ").trim();if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"get requires @ref or selector expression"}};let c=t_(l),u=tL((await ir(o,t.flags,r,a,{interactiveOnly:!1})).nodes,c,{platform:o.device.platform,requireRect:!1,requireUnique:!0,disambiguateAmbiguous:"text"===e});if(!u)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(c,[],{unique:!0})}};let d=u.node,f=tF(d,o.device.platform,{action:"get"});if("attrs"===e)return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{selector:u.selector.raw,selectorChain:f}}),{ok:!0,data:{selector:u.selector.raw,node:d}};let p=tN(d);return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{text:p,refLabel:p||void 0,selector:u.selector.raw,selectorChain:f}}),{ok:!0,data:{selector:u.selector.raw,text:p,node:d}}}if("is"===n){let e=(t.positionals?.[0]??"").toLowerCase();if(!["visible","hidden","exists","editable","selected","text"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"is requires predicate: visible|hidden|exists|editable|selected|text"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};if(!tn("is",o.device))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"is is not supported on this device"}};let{split:s}=tT(t.positionals);if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"is requires a selector expression"}};let l=s.rest.join(" ").trim();if("text"===e&&!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"is text requires expected text value"}};if("text"!==e&&s.rest.length>0)return{ok:!1,error:{code:"INVALID_ARGS",message:`is ${e} does not accept trailing values`}};let c=t_(s.selectorExpression),u=await ir(o,t.flags,r,a,{interactiveOnly:!1});if("exists"===e){let i=tR(u.nodes,c,{platform:o.device.platform});return i?(r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{predicate:e,selector:i.selector.raw,selectorChain:c.selectors.map(e=>e.raw),pass:!0,matches:i.matches}}),{ok:!0,data:{predicate:e,pass:!0,selector:i.selector.raw,matches:i.matches}}):{ok:!1,error:{code:"COMMAND_FAILED",message:tE(c,[],{unique:!1})}}}let d=tL(u.nodes,c,{platform:o.device.platform,requireUnique:!0});if(!d)return{ok:!1,error:{code:"COMMAND_FAILED",message:tE(c,[],{unique:!0})}};let f=function(e){let{predicate:t,node:i,expectedText:r,platform:a}=e,n=tN(i),o=!1;switch(t){case"visible":o=tP(i);break;case"hidden":o=!tP(i);break;case"editable":o=t$(i,a);break;case"selected":o=!0===i.selected;break;case"text":o=n===(r??"")}let s="text"===t?`expected="${r??""}" actual="${n}"`:`actual=${JSON.stringify({visible:tP(i),editable:t$(i,a),selected:!0===i.selected})}`;return{pass:o,actualText:n,details:s}}({predicate:e,node:d.node,expectedText:l,platform:o.device.platform});return f.pass?(r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{predicate:e,selector:d.selector.raw,selectorChain:c.selectors.map(e=>e.raw),pass:!0,text:"text"===e?f.actualText:void 0}}),{ok:!0,data:{predicate:e,pass:!0,selector:d.selector.raw}}):{ok:!1,error:{code:"COMMAND_FAILED",message:`is ${e} failed for selector ${d.selector.raw}: ${f.details}`}}}return null}async function ir(e,t,i,r,a){let n=await tr(e.device,"snapshot",[],t?.out,{...r({...t??{},snapshotInteractiveOnly:a.interactiveOnly,snapshotCompact:a.interactiveOnly},e.appBundleId,e.trace?.outPath)}),o=n?.nodes??[];return e.snapshot={nodes:tp(t?.snapshotRaw?o:tA(o)),truncated:n?.truncated,createdAt:Date.now(),backend:n?.backend},i.set(e.name,e),e.snapshot}let ia=n.join(f.homedir(),".agent-device"),io=n.join(ia,"daemon.json"),is=n.join(ia,"daemon.log"),il=new tc(n.join(ia,"sessions")),ic=g(),iu=i.randomBytes(24).toString("hex"),id=new Set(["session_list","devices"]);function ip(e,t,i){return td(is,e,t,i)}async function im(e){if(e.token!==iu)return{ok:!1,error:{code:"UNAUTHORIZED",message:"Invalid token"}};let t=e.command,i=function(e,t){var i;let r,a=e.session||"default";if(i=e,"string"==typeof(r=i.flags?.session)&&r.trim().length>0||"default"!==a||t.has(a))return a;let n=t.toArray();return 1===n.length?n[0].name:a}(e,il),r=il.get(i);r&&!id.has(t)&&function(e,t){if(!t)return;let i=[],r=e.device;if(t.platform&&t.platform!==r.platform&&i.push(`--platform=${t.platform}`),t.udid&&("ios"!==r.platform||t.udid!==r.id)&&i.push(`--udid=${t.udid}`),t.serial&&("android"!==r.platform||t.serial!==r.id)&&i.push(`--serial=${t.serial}`),0!==i.length){var a;let t,r,n;throw new p("INVALID_ARGS",`Session "${e.name}" is bound to ${(t=(a=e).device.platform,r=a.device.name.trim(),n=a.device.id,`${t} device "${r}" (${n})`)} and cannot be used with ${i.join(", ")}. Use a different --session name or close this session first.`)}}(r,e.flags);let a=await tY({req:e,sessionName:i,logPath:is,sessionStore:il,invoke:im});if(a)return a;let n=await t4({req:e,sessionName:i,logPath:is,sessionStore:il});if(n)return n;let o=await it({req:e,sessionName:i,sessionStore:il});if(o)return o;let s=await ie({req:e,sessionName:i,logPath:is,sessionStore:il,invoke:im});if(s)return s;let l=await ii({req:e,sessionName:i,sessionStore:il,contextFromFlags:ip});if(l)return l;let c=il.get(i);if(!c)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};if(!tn(t,c.device))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:`${t} is not supported on this device`}};let u=await tr(c.device,t,e.positionals??[],e.flags?.out,{...ip(e.flags,c.appBundleId,c.trace?.outPath)});return il.recordAction(c,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:u??{}}),{ok:!0,data:u??{}}}(e=h.createServer(e=>{let t="";e.setEncoding("utf8"),e.on("data",async i=>{let r=(t+=i).indexOf("\n");for(;-1!==r;){let i,a=t.slice(0,r).trim();if(t=t.slice(r+1),0===a.length){r=t.indexOf("\n");continue}try{let e=JSON.parse(a);i=await im(e)}catch(t){let e=l(t);i={ok:!1,error:{code:e.code,message:e.message,details:e.details}}}e.write(`${JSON.stringify(i)}
15
+ `),r=t.indexOf("\n")}})})).listen(0,"127.0.0.1",()=>{let t=e.address();if("object"==typeof t&&t?.port){var i;i=t.port,d.existsSync(ia)||d.mkdirSync(ia,{recursive:!0}),d.writeFileSync(is,""),d.writeFileSync(io,JSON.stringify({port:i,token:iu,pid:process.pid,version:ic},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
16
+ `)}}),t=async()=>{for(let e of il.toArray())"ios"===e.device.platform&&"simulator"===e.device.kind&&await eY(e.device.id),il.writeSessionLog(e);e.close(()=>{d.existsSync(io)&&d.unlinkSync(io),process.exit(0)})},process.on("SIGINT",()=>{t()}),process.on("SIGTERM",()=>{t()}),process.on("SIGHUP",()=>{t()}),process.on("uncaughtException",e=>{let i=e instanceof p?e:l(e);process.stderr.write(`Daemon error: ${i.message}
17
17
  `),t()});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -365,6 +365,87 @@ test('replay --update heals selector in is command', async () => {
365
365
  assert.ok(rewrittenSelector.includes('auth_continue'));
366
366
  });
367
367
 
368
+ test('replay --update heals numeric get text drift when numeric candidate value is unique', async () => {
369
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-replay-heal-get-numeric-'));
370
+ const sessionsDir = path.join(tempRoot, 'sessions');
371
+ const replayPath = path.join(tempRoot, 'replay.ad');
372
+ const sessionStore = new SessionStore(sessionsDir);
373
+ const sessionName = 'heal-get-numeric-session';
374
+ sessionStore.set(sessionName, makeSession(sessionName));
375
+
376
+ writeReplayFile(replayPath, {
377
+ ts: Date.now(),
378
+ command: 'get',
379
+ positionals: ['text', 'role="statictext" label="2" || label="2"'],
380
+ flags: {},
381
+ result: {},
382
+ });
383
+
384
+ const invokeCalls: string[] = [];
385
+ const invoke = async (request: DaemonRequest): Promise<DaemonResponse> => {
386
+ if (request.command !== 'get') {
387
+ return { ok: false, error: { code: 'INVALID_ARGS', message: `unexpected command ${request.command}` } };
388
+ }
389
+ const selector = request.positionals?.[1] ?? '';
390
+ invokeCalls.push(selector);
391
+ if (selector.includes('label="2"')) {
392
+ return { ok: false, error: { code: 'COMMAND_FAILED', message: 'selector stale' } };
393
+ }
394
+ if (selector.includes('label="20"')) {
395
+ return { ok: true, data: { text: '20' } };
396
+ }
397
+ return { ok: false, error: { code: 'COMMAND_FAILED', message: 'unexpected selector' } };
398
+ };
399
+
400
+ const dispatch = async (): Promise<Record<string, unknown> | void> => {
401
+ return {
402
+ nodes: [
403
+ {
404
+ index: 0,
405
+ type: 'XCUIElementTypeStaticText',
406
+ label: '20',
407
+ rect: { x: 0, y: 100, width: 100, height: 24 },
408
+ enabled: true,
409
+ hittable: true,
410
+ },
411
+ {
412
+ index: 1,
413
+ type: 'XCUIElementTypeStaticText',
414
+ label: 'Version: 0.84.0',
415
+ rect: { x: 0, y: 200, width: 220, height: 17 },
416
+ enabled: true,
417
+ hittable: true,
418
+ },
419
+ ],
420
+ truncated: false,
421
+ backend: 'xctest',
422
+ };
423
+ };
424
+
425
+ const response = await handleSessionCommands({
426
+ req: {
427
+ token: 't',
428
+ session: sessionName,
429
+ command: 'replay',
430
+ positionals: [replayPath],
431
+ flags: { replayUpdate: true },
432
+ },
433
+ sessionName,
434
+ logPath: path.join(tempRoot, 'daemon.log'),
435
+ sessionStore,
436
+ invoke,
437
+ dispatch,
438
+ });
439
+
440
+ assert.ok(response);
441
+ assert.equal(response.ok, true, JSON.stringify(response));
442
+ if (response.ok) {
443
+ assert.equal(response.data?.healed, 1);
444
+ assert.equal(response.data?.replayed, 1);
445
+ }
446
+ assert.equal(invokeCalls.length, 2);
447
+ });
448
+
368
449
  test('replay rejects legacy JSON payload files', async () => {
369
450
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-replay-json-rejected-'));
370
451
  const sessionsDir = path.join(tempRoot, 'sessions');
@@ -310,6 +310,7 @@ export async function handleInteractionCommands(params: {
310
310
  platform: session.device.platform,
311
311
  requireRect: false,
312
312
  requireUnique: true,
313
+ disambiguateAmbiguous: sub === 'text',
313
314
  });
314
315
  if (!resolved) {
315
316
  return {
@@ -10,7 +10,7 @@ import { ensureDeviceReady } from '../device-ready.ts';
10
10
  import { resolveIosAppStateFromSnapshots } from '../app-state.ts';
11
11
  import { stopIosRunnerSession } from '../../platforms/ios/runner-client.ts';
12
12
  import { attachRefs, type RawSnapshotNode, type SnapshotState } from '../../utils/snapshot.ts';
13
- import { pruneGroupNodes } from '../snapshot-processing.ts';
13
+ import { extractNodeText, normalizeType, pruneGroupNodes } from '../snapshot-processing.ts';
14
14
  import {
15
15
  buildSelectorChainForNode,
16
16
  resolveSelectorChain,
@@ -517,6 +517,10 @@ async function healReplayAction(params: {
517
517
  const session = sessionStore.get(sessionName);
518
518
  if (!session) return null;
519
519
  const requiresRect = action.command === 'click' || action.command === 'fill';
520
+ const allowDisambiguation =
521
+ action.command === 'click' ||
522
+ action.command === 'fill' ||
523
+ (action.command === 'get' && action.positionals?.[0] === 'text');
520
524
  const snapshot = await captureSnapshotForReplay(session, action, logPath, requiresRect, dispatch, sessionStore);
521
525
  const selectorCandidates = collectReplaySelectorCandidates(action);
522
526
  for (const candidate of selectorCandidates) {
@@ -526,7 +530,7 @@ async function healReplayAction(params: {
526
530
  platform: session.device.platform,
527
531
  requireRect: requiresRect,
528
532
  requireUnique: true,
529
- disambiguateAmbiguous: requiresRect,
533
+ disambiguateAmbiguous: allowDisambiguation,
530
534
  });
531
535
  if (!resolved) continue;
532
536
  const selectorChain = buildSelectorChainForNode(resolved.node, session.device.platform, {
@@ -580,6 +584,10 @@ async function healReplayAction(params: {
580
584
  };
581
585
  }
582
586
  }
587
+ const numericDriftHeal = healNumericGetTextDrift(action, snapshot, session);
588
+ if (numericDriftHeal) {
589
+ return numericDriftHeal;
590
+ }
583
591
  return null;
584
592
  }
585
593
 
@@ -697,6 +705,56 @@ function parseSelectorWaitPositionals(positionals: string[]): {
697
705
  };
698
706
  }
699
707
 
708
+ function healNumericGetTextDrift(
709
+ action: SessionAction,
710
+ snapshot: SnapshotState,
711
+ session: SessionState,
712
+ ): SessionAction | null {
713
+ if (action.command !== 'get') return null;
714
+ if (action.positionals?.[0] !== 'text') return null;
715
+ const selectorExpression = action.positionals?.[1];
716
+ if (!selectorExpression) return null;
717
+ const chain = tryParseSelectorChain(selectorExpression);
718
+ if (!chain) return null;
719
+
720
+ const roleFilters = new Set<string>();
721
+ let hasNumericTerm = false;
722
+ for (const selector of chain.selectors) {
723
+ for (const term of selector.terms) {
724
+ if (term.key === 'role' && typeof term.value === 'string') {
725
+ roleFilters.add(normalizeType(term.value));
726
+ }
727
+ if (
728
+ (term.key === 'text' || term.key === 'label' || term.key === 'value') &&
729
+ typeof term.value === 'string' &&
730
+ /^\d+$/.test(term.value.trim())
731
+ ) {
732
+ hasNumericTerm = true;
733
+ }
734
+ }
735
+ }
736
+ if (!hasNumericTerm) return null;
737
+
738
+ const numericNodes = snapshot.nodes.filter((node) => {
739
+ const text = extractNodeText(node).trim();
740
+ if (!/^\d+$/.test(text)) return false;
741
+ if (roleFilters.size === 0) return true;
742
+ return roleFilters.has(normalizeType(node.type ?? ''));
743
+ });
744
+ if (numericNodes.length === 0) return null;
745
+ const numericValues = uniqueStrings(numericNodes.map((node) => extractNodeText(node).trim()));
746
+ if (numericValues.length !== 1) return null;
747
+
748
+ const targetNode = numericNodes[0];
749
+ if (!targetNode) return null;
750
+ const selectorChain = buildSelectorChainForNode(targetNode, session.device.platform, { action: 'get' });
751
+ if (selectorChain.length === 0) return null;
752
+ return {
753
+ ...action,
754
+ positionals: ['text', selectorChain.join(' || ')],
755
+ };
756
+ }
757
+
700
758
  function parseReplayScript(script: string): SessionAction[] {
701
759
  const actions: SessionAction[] = [];
702
760
  const lines = script.split(/\r?\n/);
@@ -483,7 +483,6 @@ function analyzeSelectorMatches(
483
483
  }
484
484
  if (!best) {
485
485
  best = node;
486
- tie = false;
487
486
  continue;
488
487
  }
489
488
  const comparison = compareDisambiguationCandidates(node, best);