agent-device 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -175,6 +175,7 @@ declare type ApplePlatform = 'ios' | 'macos';
175
175
  declare type AppOpenOptions = AgentDeviceRequestOverrides & AgentDeviceSelectionOptions & {
176
176
  app: string;
177
177
  url?: string;
178
+ surface?: 'app' | 'frontmost-app' | 'desktop' | 'menubar';
178
179
  activity?: string;
179
180
  relaunch?: boolean;
180
181
  saveScript?: boolean | string;
@@ -202,10 +203,12 @@ declare type BatchStep = {
202
203
 
203
204
  declare type CaptureScreenshotOptions = AgentDeviceRequestOverrides & {
204
205
  path?: string;
206
+ overlayRefs?: boolean;
205
207
  };
206
208
 
207
209
  declare type CaptureScreenshotResult = {
208
210
  path: string;
211
+ overlayRefs?: ScreenshotOverlayRef[];
209
212
  identifiers: AgentDeviceIdentifiers;
210
213
  };
211
214
 
@@ -276,6 +279,8 @@ declare type CliFlags = {
276
279
  snapshotDepth?: number;
277
280
  snapshotScope?: string;
278
281
  snapshotRaw?: boolean;
282
+ overlayRefs?: boolean;
283
+ screenshotFullscreen?: boolean;
279
284
  baseline?: string;
280
285
  threshold?: string;
281
286
  appsFilter?: 'user-installed' | 'all';
@@ -309,6 +314,7 @@ declare type CliFlags = {
309
314
  timeoutMs?: number;
310
315
  retries?: number;
311
316
  artifactsDir?: string;
317
+ reportJunit?: string;
312
318
  steps?: string;
313
319
  stepsFile?: string;
314
320
  batchOnError?: 'stop';
@@ -490,6 +496,11 @@ declare type Platform = ApplePlatform | 'android';
490
496
 
491
497
  declare type PlatformSelector = Platform | 'apple';
492
498
 
499
+ declare type Point = {
500
+ x: number;
501
+ y: number;
502
+ };
503
+
493
504
  declare type PrepareMetroRuntimeResult = {
494
505
  projectRoot: string;
495
506
  kind: ResolvedMetroKind;
@@ -536,6 +547,14 @@ declare type Rect = {
536
547
 
537
548
  declare type ResolvedMetroKind = Exclude<MetroPrepareKind, 'auto'>;
538
549
 
550
+ declare type ScreenshotOverlayRef = {
551
+ ref: string;
552
+ label?: string;
553
+ rect: Rect;
554
+ overlayRect: Rect;
555
+ center: Point;
556
+ };
557
+
539
558
  declare type SessionCloseResult = {
540
559
  session: string;
541
560
  shutdown?: Record<string, unknown>;
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import e from"node:net";import t from"node:http";import r from"node:https";import n from"node:fs";import o from"node:path";import{AsyncLocalStorage as a}from"node:async_hooks";import i from"node:crypto";import s from"node:os";import{spawn as l,spawnSync as d}from"node:child_process";import{fileURLToPath as u}from"node:url";let c=new a,m=/(token|secret|password|authorization|cookie|api[_-]?key|access[_-]?key|private[_-]?key)/i,p=/(bearer\s+[a-z0-9._-]+|(?:api[_-]?key|token|secret|password)\s*[=:]\s*\S+)/i;function f(){return i.randomBytes(8).toString("hex")}function h(e){let t=c.getStore();if(!t)return;let r={ts:new Date().toISOString(),level:e.level??"info",phase:e.phase,session:t.session,requestId:t.requestId,command:t.command,durationMs:e.durationMs,data:e.data?function e(t,r,n){if(null==t)return t;if("string"==typeof t){var o=t,a=n;let e=o.trim();if(!e)return o;if(a&&m.test(a)||p.test(e))return"[REDACTED]";let r=function(e){try{let t=new URL(e);return t.search&&(t.search="?REDACTED"),(t.username||t.password)&&(t.username="REDACTED",t.password="REDACTED"),t.toString()}catch{return null}}(e);return r||(e.length>400?`${e.slice(0,200)}...<truncated>`:e)}if("object"!=typeof t)return t;if(r.has(t))return"[Circular]";if(r.add(t),Array.isArray(t))return t.map(t=>e(t,r));let i={};for(let[n,o]of Object.entries(t)){if(m.test(n)){i[n]="[REDACTED]";continue}i[n]=e(o,r,n)}return i}(e.data,new WeakSet):void 0};if(t.events.push(r),!t.debug)return;let o=`[agent-device][diag] ${JSON.stringify(r)}
2
- `;try{t.logPath&&n.appendFile(t.logPath,o,()=>{}),t.traceLogPath&&n.appendFile(t.traceLogPath,o,()=>{}),t.logPath||t.traceLogPath||process.stderr.write(o)}catch{}}async function g(e,t,r){let n=Date.now();try{let o=await t();return h({level:"info",phase:e,durationMs:Date.now()-n,data:r}),o}catch(t){throw h({level:"error",phase:e,durationMs:Date.now()-n,data:{...r??{},error:t instanceof Error?t.message:String(t)}}),t}}class w extends Error{code;details;cause;constructor(e,t,r,n){super(t),this.code=e,this.details=r,this.cause=n}}function I(e,t,r={}){let n=d(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],encoding:r.binaryStdout?void 0:"utf8",input:r.stdin,timeout:y(r.timeoutMs)});if(n.error){let o=n.error.code;if("ETIMEDOUT"===o)throw new w("COMMAND_FAILED",`${e} timed out after ${y(r.timeoutMs)}ms`,{cmd:e,args:t,timeoutMs:y(r.timeoutMs)},n.error);if("ENOENT"===o)throw new w("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},n.error);throw new w("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},n.error)}let o=r.binaryStdout?Buffer.isBuffer(n.stdout)?n.stdout:Buffer.from(n.stdout??""):void 0,a=r.binaryStdout?"":"string"==typeof n.stdout?n.stdout:(n.stdout??"").toString(),i="string"==typeof n.stderr?n.stderr:(n.stderr??"").toString(),s=n.status??1;if(0!==s&&!r.allowFailure)throw new w("COMMAND_FAILED",`${e} exited with code ${s}`,{cmd:e,args:t,stdout:a,stderr:i,exitCode:s,processExitError:!0});return{stdout:a,stderr:i,exitCode:s,stdoutBuffer:o}}function y(e){if(!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t}function b(){let e=o.dirname(u(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=o.join(t,"package.json");if(n.existsSync(e))return t;t=o.dirname(t)}return e}let v=[/(^|[\/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[\/\s"'=])src\/daemon\.ts($|[\s"'])/];function A(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function D(e,t){let r;if(!A(e))return!1;if(t){let r=function(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=I("ps",["-p",String(e),"-o","lstart="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}(e);if(!r||r!==t)return!1}let n=function(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=I("ps",["-p",String(e),"-o","command="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}(e);return!!n&&!!(r=n.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&v.some(e=>e.test(r))}function E(e,t){try{return process.kill(e,t),!0}catch(t){let e=t.code;if("ESRCH"===e||"EPERM"===e)return!1;throw t}}async function M(e,t){if(!A(e))return!0;let r=Date.now();for(;Date.now()-r<t;)if(await new Promise(e=>setTimeout(e,50)),!A(e))return!0;return!A(e)}async function S(e,t){!D(e,t.expectedStartTime)||!E(e,"SIGTERM")||await M(e,t.termTimeoutMs)||E(e,"SIGKILL")&&await M(e,t.killTimeoutMs)}function _(e){return e?.HOME?.trim()||s.homedir()}function P(e,t={}){return"~"===e?_(t.env):e.startsWith("~/")?o.join(_(t.env),e.slice(2)):e}function k(e,t={}){let r=P(e,t);return o.isAbsolute(r)?r:o.resolve(t.cwd??process.cwd(),r)}function T(e){let t,r=(t=(e??"").trim())?k(t):o.join(P("~"),".agent-device");return{baseDir:r,infoPath:o.join(r,"daemon.json"),lockPath:o.join(r,"daemon.lock"),logPath:o.join(r,"daemon.log"),sessionsDir:o.join(r,"sessions")}}async function N(e){let{localPath:a,baseUrl:i,token:s}=e,d=n.statSync(a).isDirectory(),u=o.basename(a),c=new URL("upload",i.endsWith("/")?i:`${i}/`),m="https:"===c.protocol?r:t,p={"x-artifact-type":d?"app-bundle":"file","x-artifact-filename":u,"transfer-encoding":"chunked"};return s&&(p.authorization=`Bearer ${s}`,p["x-agent-device-token"]=s),new Promise((e,t)=>{let r=m.request({protocol:c.protocol,host:c.hostname,port:c.port,method:"POST",path:c.pathname+c.search,headers:p},r=>{let n="";r.setEncoding("utf8"),r.on("data",e=>{n+=e}),r.on("end",()=>{clearTimeout(i);try{let r=JSON.parse(n);if(!r.ok||!r.uploadId)return void t(new w("COMMAND_FAILED",`Upload failed: ${n}`));e(r.uploadId)}catch{t(new w("COMMAND_FAILED",`Invalid upload response: ${n}`))}})}),i=setTimeout(()=>{r.destroy(),t(new w("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(r.on("error",e=>{clearTimeout(i),t(new w("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),d){let e=l("tar",["cf","-","-C",o.dirname(a),o.basename(a)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(r),e.on("error",e=>{r.destroy(),t(new w("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(r.destroy(),t(new w("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=n.createReadStream(a);e.pipe(r),e.on("error",e=>{r.destroy(),t(new w("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}let R=function(e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if(!e)return 9e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):9e4}(),O=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_TIMEOUT_MS){if(!e)return 15e3;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):15e3}(),U=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_ATTEMPTS){if(!e)return 2;let t=Number(e);return Number.isFinite(t)?Math.min(5,Math.max(1,Math.floor(t))):2}(),C=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"];async function x(e){let t=e.meta?.requestId??f(),r=!!(e.meta?.debug||e.flags?.verbose),n=function(e){let t,r,n=e.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,o=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new w("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new w("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(e.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),a=e.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN,i=e.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,s="auto"===(t=(i??"").trim().toLowerCase())?"auto":"socket"===t?"socket":"http"===t?"http":"auto";if(o&&"socket"===s)throw new w("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:o});let l="http"===(r=(e.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===i?"dual":void 0)??"").trim().toLowerCase())?"http":"dual"===r?"dual":"socket";return{paths:T(n),transportPreference:s,serverMode:l,remoteBaseUrl:o,remoteAuthToken:a}}(e),o=await g("daemon_startup",async()=>await j(n),{requestId:t,session:e.session}),a=await L(e,o),i={...e,positionals:a.positionals,flags:a.flags,token:o.token,meta:{...e.meta??{},requestId:t,debug:r,cwd:e.meta?.cwd,tenantId:e.meta?.tenantId??e.flags?.tenant,runId:e.meta?.runId??e.flags?.runId,leaseId:e.meta?.leaseId??e.flags?.leaseId,sessionIsolation:e.meta?.sessionIsolation??e.flags?.sessionIsolation,lockPolicy:e.meta?.lockPolicy,lockPlatform:e.meta?.lockPlatform,...a.uploadedArtifactId?{uploadedArtifactId:a.uploadedArtifactId}:{},...a.clientArtifactPaths?{clientArtifactPaths:a.clientArtifactPaths}:{},...a.installSource?{installSource:a.installSource}:{}}};return h({level:"info",phase:"daemon_request_prepare",data:{requestId:t,command:e.command,session:e.session}}),await g("daemon_request",async()=>await ee(o,i,n.transportPreference),{requestId:t,command:e.command})}async function L(e,t){let r,a=[...e.positionals??[]],i=e.flags?{...e.flags}:void 0,s=e.meta?.installSource,l={};if(es(t)){let n=function(e,t){if("screenshot"===e.command){let r=F(e,"path",".png");return t[0]?{field:"path",localPath:r,positionalIndex:0,positionalPath:q("screenshot",".png")}:{field:"path",localPath:r,positionalIndex:0,flagPath:q("screenshot",".png")}}if("record"===e.command&&"start"===(t[0]??"").toLowerCase()){let t=F(e,"outPath",".mp4",1);return{field:"outPath",localPath:t,positionalIndex:1,positionalPath:q("recording",o.extname(t)||".mp4")}}return null}(e,a);n&&(void 0!==n.positionalPath&&(a[n.positionalIndex]=n.positionalPath),void 0!==n.flagPath&&((i??={}).out=n.flagPath),l[n.field]=n.localPath);let d=await $(e,t);d&&(s=d.installSource,r=d.uploadedArtifactId??r)}if(!es(t)||"install"!==e.command&&"reinstall"!==e.command||a.length<2)return{positionals:a,flags:i,installSource:s,uploadedArtifactId:r,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let d=a[1];if(d.startsWith("remote:"))return a[1]=d.slice(7),{positionals:a,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let u=o.isAbsolute(d)?d:o.resolve(e.meta?.cwd??process.cwd(),d);return n.existsSync(u)?{positionals:a,flags:i,installSource:s,uploadedArtifactId:r=await N({localPath:u,baseUrl:t.baseUrl,token:t.token}),...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}:{positionals:a,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}}async function $(e,t){let r=e.meta?.installSource;if("install_source"!==e.command||!r||"path"!==r.kind)return null;let a=r.path.trim();if(!a)return{installSource:r};if(a.startsWith("remote:"))return{installSource:{...r,path:a.slice(7)}};let i=o.isAbsolute(a)?a:o.resolve(e.meta?.cwd??process.cwd(),a);if(!n.existsSync(i))return{installSource:{...r,path:i}};let s=await N({localPath:i,baseUrl:t.baseUrl,token:t.token});return{installSource:{...r,path:i},uploadedArtifactId:s}}function F(e,t,r,n=0){let a=e.positionals?.[n]??e.flags?.out,i=`${"path"===t?"screenshot":"recording"}-${Date.now()}${r}`,s=a&&a.trim().length>0?a:i;return o.isAbsolute(s)?s:o.resolve(e.meta?.cwd??process.cwd(),s)}function q(e,t){let r=t.startsWith(".")?t:`.${t}`;return o.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${r}`)}async function j(e){let t;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await Y(t,"http"))return t;throw new w("COMMAND_FAILED","Remote daemon is unavailable",{daemonBaseUrl:e.remoteBaseUrl,hint:"Verify AGENT_DEVICE_DAEMON_BASE_URL points to a reachable daemon with GET /health and POST /rpc."})}let r=H(e.paths.infoPath),a=function(){try{let e=b();return JSON.parse(n.readFileSync(o.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),i=function(e,t=b()){try{let r=n.statSync(e),a=o.relative(t,e)||e;return`${a}:${r.size}:${Math.trunc(r.mtimeMs)}`}catch{return"unknown"}}((t=Z()).useSrc?t.srcPath:t.distPath,t.root),s=!!r&&await Y(r,e.transportPreference);if(r&&r.version===a&&r.codeSignature===i&&s)return r;r&&(r.version!==a||r.codeSignature!==i||!s)&&(await z(r),X(e.paths.infoPath)),function(e){let t=K(e);if(!t.hasLock||t.hasInfo)return;let r=J(e.lockPath);if(!r)return X(e.lockPath);D(r.pid,r.processStartTime)||X(e.lockPath)}(e.paths);let l=0;for(let t=1;t<=U;t+=1){await Q(e);let r=await B(O,e);if(r)return r;if(await V(e.paths)){l+=1;continue}let n=K(e.paths);if(!(t<U))break;if(!n.hasInfo&&!n.hasLock){await G(150);continue}}let d=K(e.paths);throw new w("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:O,startupAttempts:U,lockRecoveryCount:l,metadataState:d,hint:function(e,t=T(process.env.AGENT_DEVICE_STATE_DIR)){return e.hasLock&&!e.hasInfo?`Detected ${t.lockPath} without ${t.infoPath}. If no agent-device daemon process is running, delete ${t.lockPath} and retry.`:e.hasLock&&e.hasInfo?`Daemon metadata may be stale. If no agent-device daemon process is running, delete ${t.infoPath} and ${t.lockPath}, then retry.`:`Daemon metadata is missing or stale. Delete ${t.infoPath} if present and retry.`}(d,e.paths)})}async function B(e,t){let r=Date.now();for(;Date.now()-r<e;){let e=H(t.paths.infoPath);if(e&&await Y(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function G(e){await new Promise(t=>setTimeout(t,e))}async function V(e){let t=K(e);if(!t.hasLock||t.hasInfo)return!1;let r=J(e.lockPath);return r&&D(r.pid,r.processStartTime)&&await S(r.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:r.processStartTime}),X(e.lockPath),!0}async function z(e){await S(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function H(e){let t=W(e);if(!t||"object"!=typeof t)return null;let r="string"==typeof t.token&&t.token.length>0?t.token:null;if(!r)return null;let n=Number.isInteger(t.port)&&Number(t.port)>0,o=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;if(!n&&!o)return null;let a=t.transport,i="string"==typeof t.version?t.version:void 0,s="string"==typeof t.codeSignature?t.codeSignature:void 0,l="string"==typeof t.processStartTime?t.processStartTime:void 0,d=Number.isInteger(t.pid)&&Number(t.pid)>0;return{token:r,port:n?Number(t.port):void 0,httpPort:o?Number(t.httpPort):void 0,transport:"socket"===a||"http"===a||"dual"===a?a:void 0,pid:d?Number(t.pid):0,version:i,codeSignature:s,processStartTime:l}}function J(e){let t=W(e);return t&&"object"==typeof t&&Number.isInteger(t.pid)&&Number(t.pid)>0?{pid:Number(t.pid),processStartTime:"string"==typeof t.processStartTime?t.processStartTime:void 0,startedAt:"number"==typeof t.startedAt?t.startedAt:void 0}:null}function K(e){return{hasInfo:n.existsSync(e.infoPath),hasLock:n.existsSync(e.lockPath)}}function W(e){if(!n.existsSync(e))return null;try{return JSON.parse(n.readFileSync(e,"utf8"))}catch{return null}}function X(e){try{n.existsSync(e)&&n.unlinkSync(e)}catch{}}async function Y(n,o){var a;return"http"===et(n,o)?await function(e){let n=e.baseUrl?el(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!n)return Promise.resolve(!1);let o=new URL(n),a="https:"===o.protocol?r:t,i=e.baseUrl?3e3:500;return new Promise(e=>{let t=a.request({protocol:o.protocol,host:o.hostname,port:o.port,path:o.pathname+o.search,method:"GET",timeout:i},t=>{t.resume(),e((t.statusCode??500)<500)});t.on("timeout",()=>{t.destroy(),e(!1)}),t.on("error",()=>{e(!1)}),t.end()})}(n):await ((a=n.port)?new Promise(t=>{let r=e.createConnection({host:"127.0.0.1",port:a},()=>{r.destroy(),t(!0)});r.on("error",()=>{t(!1)})}):Promise.resolve(!1))}async function Q(e){let t=Z(),r=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],n={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};!function(e,t,r={}){l(e,t,{cwd:r.cwd,env:r.env,stdio:"ignore",detached:!0}).unref()}(process.execPath,r,{env:n})}function Z(){let e=b(),t=o.join(e,"dist","src","daemon.js"),r=o.join(e,"src","daemon.ts"),a=n.existsSync(t),i=n.existsSync(r);if(!a&&!i)throw new w("COMMAND_FAILED","Daemon entry not found",{distPath:t,srcPath:r});return{root:e,distPath:t,srcPath:r,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!a&&i}}async function ee(e,t,r){return"http"===et(e,r)?await eo(e,t):await en(e,t)}function et(e,t){if(e.baseUrl){if("socket"===t)throw new w("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t||"socket"===t){var r=e,n=t;if(er(r,n))return n;throw new w("COMMAND_FAILED","http"===n?"Daemon HTTP endpoint is unavailable":"Daemon socket endpoint is unavailable")}let o=("socket"===e.transport||"dual"===e.transport?["socket","http"]:["http","socket"]).find(t=>er(e,t));if(o)return o;throw new w("COMMAND_FAILED","Daemon metadata has no reachable transport")}function er(e,t){return"http"===t?!!e.httpPort:!!e.port}async function en(t,r){let n=t.port;if(!n)throw new w("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((o,a)=>{let i=e.createConnection({host:"127.0.0.1",port:n},()=>{i.write(`${JSON.stringify(r)}
3
- `)}),s=setTimeout(()=>{i.destroy();let e=ea(),n=ei(t,T(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR));h({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:R,requestId:r.meta?.requestId,command:r.command,timedOutRunnerPidsTerminated:e.terminated,timedOutRunnerCleanupError:e.error,daemonPidReset:t.pid,daemonPidForceKilled:n.forcedKill}}),a(new w("COMMAND_FAILED","Daemon request timed out",{timeoutMs:R,requestId:r.meta?.requestId,hint:"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},R),l="";i.setEncoding("utf8"),i.on("data",e=>{let t=(l+=e).indexOf("\n");if(-1===t)return;let n=l.slice(0,t).trim();if(n)try{let e=JSON.parse(n);i.end(),clearTimeout(s),o(e)}catch(e){clearTimeout(s),a(new w("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:n},e instanceof Error?e:void 0))}}),i.on("error",e=>{clearTimeout(s),h({level:"error",phase:"daemon_request_socket_error",data:{requestId:r.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),a(new w("COMMAND_FAILED","Failed to communicate with daemon",{requestId:r.meta?.requestId,hint:"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e))})})}async function eo(e,n){let o=e.baseUrl?new URL(el(e.baseUrl,"rpc")):e.httpPort?new URL(`http://127.0.0.1:${e.httpPort}/rpc`):null;if(!o)throw new w("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let a=JSON.stringify({jsonrpc:"2.0",id:n.meta?.requestId??f(),method:"agent_device.command",params:n}),i={"content-type":"application/json","content-length":Buffer.byteLength(a)};return e.baseUrl&&e.token&&(i.authorization=`Bearer ${e.token}`,i["x-agent-device-token"]=e.token),await new Promise((s,l)=>{let d=T(n.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),u=("https:"===o.protocol?r:t).request({protocol:o.protocol,host:o.hostname,port:o.port,method:"POST",path:o.pathname+o.search,headers:i},t=>{let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{clearTimeout(c);try{let t=JSON.parse(r);if(t.error){let e=t.error.data??{};l(new w(String(e.code??"COMMAND_FAILED"),String(e.message??t.error.message??"Daemon RPC request failed"),{..."object"==typeof e.details&&e.details?e.details:{},hint:"string"==typeof e.hint?e.hint:void 0,diagnosticId:"string"==typeof e.diagnosticId?e.diagnosticId:void 0,logPath:"string"==typeof e.logPath?e.logPath:void 0,requestId:n.meta?.requestId}));return}if(!t.result||"object"!=typeof t.result)return void l(new w("COMMAND_FAILED","Invalid daemon RPC response",{requestId:n.meta?.requestId}));if(e.baseUrl&&t.result.ok)return void ed(e,n,t.result).then(s).catch(l);s(t.result)}catch(e){clearTimeout(c),l(new w("COMMAND_FAILED","Invalid daemon response",{requestId:n.meta?.requestId,line:r},e instanceof Error?e:void 0))}})}),c=setTimeout(()=>{u.destroy();let t=es(e)?{terminated:0}:ea(),r=es(e)?{forcedKill:!1}:ei(e,d);h({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:R,requestId:n.meta?.requestId,command:n.command,timedOutRunnerPidsTerminated:t.terminated,timedOutRunnerCleanupError:t.error,daemonPidReset:es(e)?void 0:e.pid,daemonPidForceKilled:es(e)?void 0:r.forcedKill,daemonBaseUrl:e.baseUrl}}),l(new w("COMMAND_FAILED","Daemon request timed out",{timeoutMs:R,requestId:n.meta?.requestId,hint:es(e)?"Retry with --debug and verify the remote daemon URL, auth token, and remote host logs.":"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},R);u.on("error",t=>{clearTimeout(c),h({level:"error",phase:"daemon_request_socket_error",data:{requestId:n.meta?.requestId,message:t instanceof Error?t.message:String(t)}}),l(new w("COMMAND_FAILED","Failed to communicate with daemon",{requestId:n.meta?.requestId,hint:es(e)?"Retry command. If this persists, verify the remote daemon URL, auth token, and remote host reachability.":"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},t))}),u.write(a),u.end()})}function ea(){let e=0;try{for(let t of C){let r=I("pkill",["-f",t],{allowFailure:!0});0===r.exitCode&&(e+=1)}return{terminated:e}}catch(t){return{terminated:e,error:t instanceof Error?t.message:String(t)}}}function ei(e,t){let r=!1;try{D(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),r=!0)}catch{S(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{X(t.infoPath),X(t.lockPath)}return{forcedKill:r}}function es(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function el(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function ed(e,t,r){let n=Array.isArray(r.data?.artifacts)?r.data.artifacts:[];if(0===n.length||!e.baseUrl)return r;let a=r.data?{...r.data}:{},i=[];for(let r of n){if(!r||"object"!=typeof r||"string"!=typeof r.artifactId){i.push(r);continue}let n=function(e,t){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let r=e.fileName?.trim()||`${e.field}-${Date.now()}`;return o.resolve(t.meta?.cwd??process.cwd(),r)}(r,t);await eu({baseUrl:e.baseUrl,token:e.token,artifactId:r.artifactId,destinationPath:n,requestId:t.meta?.requestId}),a[r.field]=n,i.push({...r,localPath:n})}return a.artifacts=i,{ok:!0,data:a}}async function eu(e){var a,i;let s,l=new URL((a=e.baseUrl,i=e.artifactId,s=a.endsWith("/")?a:`${a}/`,new URL(`upload/${encodeURIComponent(i)}`,s).toString())),d="https:"===l.protocol?r:t;await n.promises.mkdir(o.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,r)=>{let o=!1,a=e.timeoutMs??R,i=a=>{if(!o){if(o=!0,clearTimeout(u),a)return void n.promises.rm(e.destinationPath,{force:!0}).finally(()=>r(a));t()}},s=d.request({protocol:l.protocol,host:l.hostname,port:l.port,method:"GET",path:l.pathname+l.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{i(new w("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:r}))});return}let r=n.createWriteStream(e.destinationPath);r.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("aborted",()=>{i(new w("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),r.on("finish",()=>{r.close(()=>i())}),t.pipe(r)}),u=setTimeout(()=>{s.destroy(new w("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:a}))},a);s.on("error",t=>{t instanceof w?i(t):i(new w("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:a},t instanceof Error?t:void 0))}),s.end()})}function ec(e){return e.replace(/\/+$/,"")}function em(e){return"string"==typeof e&&e.trim()?ec(e.trim()):""}function ep(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function ef(e,t,r){return k(e,{env:t,cwd:r})}function eh(e){try{return n.accessSync(e,n.constants.F_OK),!0}catch{return!1}}function eg(e,t,r){if(null==e||""===e)return t;let n=Number.parseInt(String(e),10);return Number.isInteger(n)?Math.max(n,r):t}function ew(e,t){let r;return{platform:t,bundleUrl:((r=new URL(`${ec(e)}/index.bundle`)).searchParams.set("platform",t),r.searchParams.set("dev","true"),r.searchParams.set("minify","false"),r.toString())}}function eI(e,t){return{platform:t,metroHost:ep(e?.metro_host),metroPort:e?.metro_port,bundleUrl:ep(e?.metro_bundle_url),launchUrl:ep(e?.launch_url)}}function ey(e){return`'${e.replace(/'/g,"'\"'\"'")}'`}async function eb(e){await new Promise(t=>setTimeout(t,e))}async function ev(e,t,r={}){try{let n=await fetch(e,{headers:r,signal:AbortSignal.timeout(t)});return{ok:n.ok,status:n.status,body:await n.text()}}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw Error(`Timed out fetching ${e} after ${t}ms`);throw r}}async function eA(e,t){try{let r=await ev(e,t);return r.ok&&r.body.includes("packager-status:running")}catch{return!1}}async function eD(e){var t,r,n;let o;try{o=await fetch(`${e.baseUrl}/api/metro/bridge`,{method:"POST",headers:(t=e.baseUrl,r=e.bearerToken,{Authorization:`Bearer ${r}`,"Content-Type":"application/json",...t.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({ios_runtime:e.runtime,timeout_ms:e.timeoutMs}),signal:AbortSignal.timeout(e.timeoutMs)})}catch(t){if(t instanceof Error&&"TimeoutError"===t.name)throw Error(`/api/metro/bridge timed out after ${e.timeoutMs}ms calling ${e.baseUrl}/api/metro/bridge`);throw t}let a=await o.text(),i=a?JSON.parse(a):{};if(!o.ok)throw Error(`/api/metro/bridge failed (${o.status}): ${JSON.stringify(i)}`);return{enabled:(n=i.data??i).enabled,baseUrl:n.base_url,statusUrl:n.status_url,bundleUrl:n.bundle_url,iosRuntime:eI(n.ios_runtime,"ios"),androidRuntime:eI(n.android_runtime,"android"),upstream:{bundleUrl:n.upstream.bundle_url,host:n.upstream.host,port:n.upstream.port,statusUrl:n.upstream.status_url},probe:{reachable:n.probe.reachable,statusCode:n.probe.status_code,latencyMs:n.probe.latency_ms,detail:n.probe.detail}}}async function eE(e,t,r){let n=Date.now()+t;for(;Date.now()<n;){let t=Math.min(r,Math.max(n-Date.now(),1));if(await eA(e,t))return!0;let o=Math.min(500,Math.max(n-Date.now(),0));o>0&&await eb(o)}return!1}async function eM(e={}){let t=e.env??process.env,r=process.cwd(),a=ef(e.projectRoot??r,t,r),i=function(e,t){if("auto"!==t)return t;let r=function(e){let t=o.join(e,"package.json");if(!eh(t))throw new w("INVALID_ARGS",`package.json not found at ${t}`);return JSON.parse(n.readFileSync(t,"utf8"))}(e);return"string"==typeof({...r.dependencies??{},...r.devDependencies??{}}).expo?"expo":"react-native"}(a,e.kind??"auto"),s=function(e,t){if(null==e||""===e)return 8081;let r=Number.parseInt(String(e),10);if(!Number.isInteger(r)||r<1||r>65535)throw new w("INVALID_ARGS",`Invalid Metro port: ${String(e)}. Use 1-65535.`);return r}(e.metroPort??8081,0),l=ep(e.listenHost)??"0.0.0.0",d=ep(e.statusHost)??"127.0.0.1",u=em(e.publicBaseUrl),c=eg(e.startupTimeoutMs,18e4,3e4),m=eg(e.probeTimeoutMs,1e4,1e3),p=e.reuseExisting??!0,f=e.installDependenciesIfNeeded??!0,h=e.runtimeFilePath?ef(e.runtimeFilePath,t,r):null,g=ef(e.logPath??o.join(a,".agent-device","metro.log"),t,r);if(!u)throw new w("INVALID_ARGS","metro prepare requires --public-base-url <url>.");let{proxyEnabled:y,proxyBaseUrl:b,proxyBearerToken:v}=function(e,t){if(e&&!t)throw new w("INVALID_ARGS","metro prepare requires proxy auth when --proxy-base-url is provided. Pass --bearer-token or set AGENT_DEVICE_PROXY_TOKEN.");if(!e&&t)throw new w("INVALID_ARGS","metro prepare requires --proxy-base-url when proxy auth is provided.");return{proxyEnabled:!!(e&&t),proxyBaseUrl:e,proxyBearerToken:t}}(em(e.proxyBaseUrl),ep(e.proxyBearerToken)??""),A=f?function(e,t){if(function(e){try{return n.statSync(e).isDirectory()}catch{return!1}}(o.join(e,"node_modules")))return{installed:!1};let r=eh(o.join(e,"pnpm-lock.yaml"))?{command:"pnpm",installArgs:["install"]}:eh(o.join(e,"yarn.lock"))?{command:"yarn",installArgs:["install"]}:{command:"npm",installArgs:["install"]};return I(r.command,r.installArgs,{cwd:e,env:t}),{installed:!0,packageManager:r.command}}(a,t):{installed:!1},D=`http://${d}:${s}/status`,E=!1,M=!1,S=0;if(p&&await eA(D,m))M=!0;else if(E=!0,S=function(e,t,r,a,i,s){let l="expo"===t?{command:"npx",installArgs:["expo","start","--host","lan","--port",String(r)]}:{command:"npx",installArgs:["react-native","start","--host",a,"--port",String(r)]};n.mkdirSync(o.dirname(i),{recursive:!0});let d=[ey(l.command),...l.installArgs.map(ey)].join(" "),u=I("/bin/sh",["-c",`nohup ${d} >> ${ey(i)} 2>&1 < /dev/null & echo $!`],{cwd:e,env:s}),c=Number.parseInt(u.stdout.trim(),10);if(!Number.isInteger(c)||c<=0)throw Error(`Failed to start Metro. Expected a child PID in stdout, got "${u.stdout.trim()}".`);return{pid:c}}(a,i,s,l,g,t).pid,!await eE(D,c,m))throw Error(`Metro did not become ready at ${D} within ${c}ms. Check ${g}.`);let _=ew(u,"ios"),P=ew(u,"android"),k=null,T=null;if(y)try{k=await eD({baseUrl:b,bearerToken:v,runtime:{metro_bundle_url:_.bundleUrl},timeoutMs:m})}catch(e){T=e instanceof Error?e.message:String(e)}if(y&&(!k||!1===k.probe.reachable)){var N,R;let e;throw Error((N=T,R=k,e=[`Metro bridge is required for this run but could not be configured via ${b}/api/metro/bridge.`],N&&e.push(`bridgeError=${N}`),R?.probe.reachable===!1&&e.push(`bridgeProbe=${R.probe.detail||`unreachable (status ${R.probe.statusCode||0})`}`),e.join(" ")))}let O=k?.iosRuntime??_,U=k?.androidRuntime??P,C={projectRoot:a,kind:i,dependenciesInstalled:A.installed,packageManager:A.packageManager??null,started:E,reused:M,pid:S,logPath:g,statusUrl:D,runtimeFilePath:h,iosRuntime:O,androidRuntime:U,bridge:k};return h&&(n.mkdirSync(o.dirname(h),{recursive:!0}),n.writeFileSync(h,JSON.stringify(C,null,2))),C}function eS(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function e_(e,t,r){return{deviceId:t,deviceName:r,..."android"===e?{serial:t}:"ios"===e?{udid:t}:{}}}function eP(e,t){let r=ex(e,"bundleId"),n=ex(e,"package");return{app:eC(e,"app"),appPath:eC(e,"appPath"),platform:eL(e,"platform"),appId:r??n,bundleId:r,package:n,identifiers:eS({session:t,bundleId:r,packageName:n})}}function ek(e){var t;let r=eO(e),n=eL(r,"platform"),o=eC(r,"id"),a=eC(r,"name");return{platform:n,target:e$(r,"target"),kind:eF(r,t="kind",eV,`Daemon response has invalid "${t}".`),id:o,name:a,booted:"boolean"==typeof r.booted?r.booted:void 0,identifiers:e_(n,o,a),ios:"ios"===n?{udid:o}:void 0,android:"android"===n?{serial:o}:void 0}}function eT(e){var t;let r=eO(e),n=eL(r,"platform"),o=eC(r,"id"),a=eC(r,"name"),i=e$(r,"target"),s=eC(r,"device"),l={session:a,...e_(n,o,s)};return{name:a,createdAt:eF(r,t="createdAt",eB,`Daemon response is missing numeric "${t}".`),device:{platform:n,target:i,id:o,name:s,identifiers:l,ios:"ios"===n?{udid:o,simulatorSetPath:eq(r,"ios_simulator_device_set",ej)}:void 0,android:"android"===n?{serial:o}:void 0},identifiers:l}}function eN(e,t){return t??e??"default"}function eR(e){let t={};for(let[r,n]of Object.entries(e))void 0!==n&&(t[r]=n);return t}function eO(e){if(!eU(e))throw new w("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function eU(e){return"object"==typeof e&&null!==e}function eC(e,t){return eF(e,t,ej,`Daemon response is missing "${t}".`)}function ex(e,t){return ej(e[t])}function eL(e,t){return eF(e,t,eG,`Daemon response has invalid "${t}".`)}function e$(e,t){return ez(e[t])??"mobile"}function eF(e,t,r,n){let o=r(e[t]);if(void 0===o)throw new w("COMMAND_FAILED",n,{response:e});return o}function eq(e,t,r){let n=e[t];return null===n?null:r(n)}function ej(e){return"string"==typeof e&&e.length>0?e:void 0}function eB(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function eG(e){return"ios"===e||"macos"===e||"android"===e?e:void 0}function eV(e){return"simulator"===e||"emulator"===e||"device"===e?e:void 0}function ez(e){return"tv"===e||"mobile"===e||"desktop"===e?e:void 0}function eH(e={},t={}){let r=t.transport??x,n=async(t,n=[],o={})=>{let a={...e,...o},i=await r({session:eN(e.session,o.session),command:t,positionals:n,flags:eR({stateDir:a.stateDir,daemonBaseUrl:a.daemonBaseUrl,daemonAuthToken:a.daemonAuthToken,daemonTransport:a.daemonTransport,daemonServerMode:a.daemonServerMode,tenant:a.tenant,sessionIsolation:a.sessionIsolation,runId:a.runId,leaseId:a.leaseId,platform:a.platform,target:a.target,device:a.device,udid:a.udid,serial:a.serial,iosSimulatorDeviceSet:a.iosSimulatorDeviceSet,androidDeviceAllowlist:a.androidDeviceAllowlist,runtime:a.simulatorRuntimeId,boot:a.boot,reuseExisting:a.reuseExisting,activity:a.activity,relaunch:a.relaunch,shutdown:a.shutdown,saveScript:a.saveScript,noRecord:a.noRecord,metroHost:a.metroHost,metroPort:a.metroPort,bundleUrl:a.bundleUrl,launchUrl:a.launchUrl,snapshotInteractiveOnly:a.interactiveOnly,snapshotCompact:a.compact,snapshotDepth:a.depth,snapshotScope:a.scope,snapshotRaw:a.raw,verbose:a.debug}),runtime:a.runtime,meta:eR({requestId:a.requestId,cwd:a.cwd,debug:a.debug,lockPolicy:a.lockPolicy,lockPlatform:a.lockPlatform,tenantId:a.tenant,runId:a.runId,leaseId:a.leaseId,sessionIsolation:a.sessionIsolation,installSource:a.installSource,retainMaterializedPaths:a.retainMaterializedPaths,materializedPathRetentionMs:a.materializedPathRetentionMs,materializationId:a.materializationId})});if(!i.ok)throw new w(i.error.code,i.error.message,{...i.error.details??{},hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath});return i.data??{}},o=async(e={})=>{let t=await n("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(eT)};return{devices:{list:async(e={})=>{let t=await n("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(ek)}},sessions:{list:async(e={})=>await o(e),close:async(t={})=>{let r=eN(e.session,t.session),o=(await n("close",[],t)).shutdown;return{session:r,shutdown:"object"==typeof o&&null!==o?o:void 0,identifiers:{session:r}}}},simulators:{ensure:async e=>{let{runtime:t,...r}=e,o=await n("ensure-simulator",[],{...r,simulatorRuntimeId:t}),a=eC(o,"udid"),i=eC(o,"device");return{udid:a,device:i,runtime:eC(o,"runtime"),created:!0===o.created,booted:!0===o.booted,iosSimulatorDeviceSet:eq(o,"ios_simulator_device_set",ej),identifiers:{deviceId:a,deviceName:i,udid:a}}}},apps:{install:async t=>eP(await n("install",[t.app,t.appPath],t),eN(e.session,t.session)),reinstall:async t=>eP(await n("reinstall",[t.app,t.appPath],t),eN(e.session,t.session)),installFromSource:async t=>(function(e,t){let r=ex(e,"bundleId"),n=ex(e,"packageName"),o=r??n??ex(e,"appId"),a=ex(e,"launchTarget")??n??r??o;if(!a)throw new w("COMMAND_FAILED",'Daemon response is missing "launchTarget".',{response:e});return{appName:ex(e,"appName"),appId:o,bundleId:r,packageName:n,launchTarget:a,installablePath:ex(e,"installablePath"),archivePath:ex(e,"archivePath"),materializationId:ex(e,"materializationId"),materializationExpiresAt:ex(e,"materializationExpiresAt"),identifiers:eS({session:t,bundleId:r,packageName:n,appId:o})}})(await n("install_source",[],{...t,installSource:t.source,retainMaterializedPaths:t.retainPaths,materializedPathRetentionMs:t.retentionMs}),eN(e.session,t.session)),open:async t=>{let r=eN(e.session,t.session),o=t.url?[t.app,t.url]:[t.app],a=await n("open",o,t),i=function(e){let t=e.platform,r=ex(e,"id"),n=ex(e,"device");if("ios"!==t&&"macos"!==t&&"android"!==t||!r||!n)return;let o=e$(e,"target"),a=e_(t,r,n);return{platform:t,target:o,id:r,name:n,identifiers:a,ios:"ios"===t?{udid:ex(e,"device_udid")??r,simulatorSetPath:eq(e,"ios_simulator_device_set",ej)}:void 0,android:"android"===t?{serial:ex(e,"serial")??r}:void 0}}(a),s=ex(a,"appBundleId");return{session:r,appName:ex(a,"appName"),appBundleId:s,appId:s,startup:function(e){if(eU(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:ex(e,"appTarget"),appBundleId:ex(e,"appBundleId")}}(a.startup),runtime:function(e){if(!eU(e))return;let t=e.platform,r=ex(e,"metroHost"),n="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:r,metroPort:n,bundleUrl:ex(e,"bundleUrl"),launchUrl:ex(e,"launchUrl")}}(a.runtime),device:i,identifiers:{session:r,deviceId:i?.id,deviceName:i?.name,udid:i?.ios?.udid,serial:i?.android?.serial,appId:s,appBundleId:s}}},close:async(t={})=>{let r=eN(e.session,t.session),o=(await n("close",t.app?[t.app]:[],t)).shutdown;return{session:r,closedApp:t.app,shutdown:"object"==typeof o&&null!==o?o:void 0,identifiers:{session:r}}}},materializations:{release:async e=>{var t;return{released:!0===(t=await n("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:eC(t,"materializationId"),identifiers:{}}}},metro:{prepare:async t=>await eM({projectRoot:t.projectRoot??e.cwd,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.bearerToken,metroPort:t.port,listenHost:t.listenHost,statusHost:t.statusHost,startupTimeoutMs:t.startupTimeoutMs,probeTimeoutMs:t.probeTimeoutMs,reuseExisting:t.reuseExisting,installDependenciesIfNeeded:t.installDependenciesIfNeeded,runtimeFilePath:t.runtimeFilePath,logPath:t.logPath})},capture:{snapshot:async(t={})=>{var r;let o=eN(e.session,t.session),a=await n("snapshot",[],t),i=ex(a,"appBundleId");return{nodes:Array.isArray(r=a.nodes)?r:[],truncated:!0===a.truncated,appName:ex(a,"appName"),appBundleId:i,identifiers:{session:o,appId:i,appBundleId:i}}},screenshot:async(t={})=>{let r=eN(e.session,t.session);return{path:eC(await n("screenshot",t.path?[t.path]:[],t),"path"),identifiers:{session:r}}}}}}export{eH as createAgentDeviceClient,w as AppError};
1
+ import e from"node:net";import t from"node:http";import r from"node:https";import n from"node:fs";import o from"node:path";import{AsyncLocalStorage as a}from"node:async_hooks";import i from"node:crypto";import s from"node:os";import{spawn as l,spawnSync as u}from"node:child_process";import{fileURLToPath as d}from"node:url";let c=new a,p=/(token|secret|password|authorization|cookie|api[_-]?key|access[_-]?key|private[_-]?key)/i,m=/(bearer\s+[a-z0-9._-]+|(?:api[_-]?key|token|secret|password)\s*[=:]\s*\S+)/i;function f(){return i.randomBytes(8).toString("hex")}function h(e){let t=c.getStore();if(!t)return;let r={ts:new Date().toISOString(),level:e.level??"info",phase:e.phase,session:t.session,requestId:t.requestId,command:t.command,durationMs:e.durationMs,data:e.data?function e(t,r,n){if(null==t)return t;if("string"==typeof t){var o=t,a=n;let e=o.trim();if(!e)return o;if(a&&p.test(a)||m.test(e))return"[REDACTED]";let r=function(e){try{let t=new URL(e);return t.search&&(t.search="?REDACTED"),(t.username||t.password)&&(t.username="REDACTED",t.password="REDACTED"),t.toString()}catch{return null}}(e);return r||(e.length>400?`${e.slice(0,200)}...<truncated>`:e)}if("object"!=typeof t)return t;if(r.has(t))return"[Circular]";if(r.add(t),Array.isArray(t))return t.map(t=>e(t,r));let i={};for(let[n,o]of Object.entries(t)){if(p.test(n)){i[n]="[REDACTED]";continue}i[n]=e(o,r,n)}return i}(e.data,new WeakSet):void 0};if(t.events.push(r),!t.debug)return;let o=`[agent-device][diag] ${JSON.stringify(r)}
2
+ `;try{t.logPath&&n.appendFile(t.logPath,o,()=>{}),t.traceLogPath&&n.appendFile(t.traceLogPath,o,()=>{}),t.logPath||t.traceLogPath||process.stderr.write(o)}catch{}}async function g(e,t,r){let n=Date.now();try{let o=await t();return h({level:"info",phase:e,durationMs:Date.now()-n,data:r}),o}catch(t){throw h({level:"error",phase:e,durationMs:Date.now()-n,data:{...r??{},error:t instanceof Error?t.message:String(t)}}),t}}class w extends Error{code;details;cause;constructor(e,t,r,n){super(t),this.code=e,this.details=r,this.cause=n}}function y(e,t,r={}){let n=u(e,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],encoding:r.binaryStdout?void 0:"utf8",input:r.stdin,timeout:v(r.timeoutMs)});if(n.error){let o=n.error.code;if("ETIMEDOUT"===o)throw new w("COMMAND_FAILED",`${e} timed out after ${v(r.timeoutMs)}ms`,{cmd:e,args:t,timeoutMs:v(r.timeoutMs)},n.error);if("ENOENT"===o)throw new w("TOOL_MISSING",`${e} not found in PATH`,{cmd:e},n.error);throw new w("COMMAND_FAILED",`Failed to run ${e}`,{cmd:e,args:t},n.error)}let o=r.binaryStdout?Buffer.isBuffer(n.stdout)?n.stdout:Buffer.from(n.stdout??""):void 0,a=r.binaryStdout?"":"string"==typeof n.stdout?n.stdout:(n.stdout??"").toString(),i="string"==typeof n.stderr?n.stderr:(n.stderr??"").toString(),s=n.status??1;if(0!==s&&!r.allowFailure)throw new w("COMMAND_FAILED",`${e} exited with code ${s}`,{cmd:e,args:t,stdout:a,stderr:i,exitCode:s,processExitError:!0});return{stdout:a,stderr:i,exitCode:s,stdoutBuffer:o}}function I(e,t,r={}){let n=l(e,t,{cwd:r.cwd,env:r.env,stdio:r.stdio??"ignore",detached:!0});return n.unref(),n.pid??0}function v(e){if(!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t}function b(){let e=o.dirname(d(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=o.join(t,"package.json");if(n.existsSync(e))return t;t=o.dirname(t)}return e}let A=[/(^|[/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[/\s"'=])src\/daemon\.ts($|[\s"'])/];function E(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function D(e,t){let r;if(!E(e))return!1;if(t){let r=function(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=y("ps",["-p",String(e),"-o","lstart="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}(e);if(!r||r!==t)return!1}let n=function(e){if(!Number.isInteger(e)||e<=0)return null;try{let t=y("ps",["-p",String(e),"-o","command="],{allowFailure:!0,timeoutMs:1e3});if(0!==t.exitCode)return null;let r=t.stdout.trim();return r.length>0?r:null}catch{return null}}(e);return!!n&&!!(r=n.toLowerCase().replaceAll("\\","/")).includes("agent-device")&&A.some(e=>e.test(r))}function S(e,t){try{return process.kill(e,t),!0}catch(t){let e=t.code;if("ESRCH"===e||"EPERM"===e)return!1;throw t}}async function M(e,t){if(!E(e))return!0;let r=Date.now();for(;Date.now()-r<t;)if(await new Promise(e=>setTimeout(e,50)),!E(e))return!0;return!E(e)}async function _(e,t){!D(e,t.expectedStartTime)||!S(e,"SIGTERM")||await M(e,t.termTimeoutMs)||S(e,"SIGKILL")&&await M(e,t.killTimeoutMs)}function P(e){return e?.HOME?.trim()||s.homedir()}function k(e,t={}){return"~"===e?P(t.env):e.startsWith("~/")?o.join(P(t.env),e.slice(2)):e}function T(e,t={}){let r=k(e,t);return o.isAbsolute(r)?r:o.resolve(t.cwd??process.cwd(),r)}function N(e){let t,r=(t=(e??"").trim())?T(t):o.join(k("~"),".agent-device");return{baseDir:r,infoPath:o.join(r,"daemon.json"),lockPath:o.join(r,"daemon.lock"),logPath:o.join(r,"daemon.log"),sessionsDir:o.join(r,"sessions")}}async function R(e){let{localPath:a,baseUrl:i,token:s}=e,u=n.statSync(a).isDirectory(),d=o.basename(a),c=new URL("upload",i.endsWith("/")?i:`${i}/`),p="https:"===c.protocol?r:t,m={"x-artifact-type":u?"app-bundle":"file","x-artifact-filename":d,"transfer-encoding":"chunked"};return s&&(m.authorization=`Bearer ${s}`,m["x-agent-device-token"]=s),new Promise((e,t)=>{let r=p.request({protocol:c.protocol,host:c.hostname,port:c.port,method:"POST",path:c.pathname+c.search,headers:m},r=>{let n="";r.setEncoding("utf8"),r.on("data",e=>{n+=e}),r.on("end",()=>{clearTimeout(i);try{let r=JSON.parse(n);if(!r.ok||!r.uploadId)return void t(new w("COMMAND_FAILED",`Upload failed: ${n}`));e(r.uploadId)}catch{t(new w("COMMAND_FAILED",`Invalid upload response: ${n}`))}})}),i=setTimeout(()=>{r.destroy(),t(new w("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(r.on("error",e=>{clearTimeout(i),t(new w("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),u){let e=l("tar",["cf","-","-C",o.dirname(a),o.basename(a)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(r),e.on("error",e=>{r.destroy(),t(new w("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(r.destroy(),t(new w("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=n.createReadStream(a);e.pipe(r),e.on("error",e=>{r.destroy(),t(new w("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}let U=ep(),O=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_TIMEOUT_MS){if(!e)return 15e3;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):15e3}(),x=function(e=process.env.AGENT_DEVICE_DAEMON_STARTUP_ATTEMPTS){if(!e)return 2;let t=Number(e);return Number.isFinite(t)?Math.min(5,Math.max(1,Math.floor(t))):2}(),C=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"];async function L(e){let t=e.meta?.requestId??f(),r=!!(e.meta?.debug||e.flags?.verbose),n=function(e){let t,r,n=e.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,o=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new w("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new w("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(e.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),a=e.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN,i=e.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,s="auto"===(t=(i??"").trim().toLowerCase())?"auto":"socket"===t?"socket":"http"===t?"http":"auto";if(o&&"socket"===s)throw new w("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:o});let l="http"===(r=(e.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===i?"dual":void 0)??"").trim().toLowerCase())?"http":"dual"===r?"dual":"socket";return{paths:N(n),transportPreference:s,serverMode:l,remoteBaseUrl:o,remoteAuthToken:a}}(e),o=function(e,t=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if("test"!==e)return ep(t)}(e.command),a=await g("daemon_startup",async()=>await B(n),{requestId:t,session:e.session}),i=await $(e,a),s={...e,positionals:i.positionals,flags:i.flags,token:a.token,meta:{...e.meta??{},requestId:t,debug:r,cwd:e.meta?.cwd,tenantId:e.meta?.tenantId??e.flags?.tenant,runId:e.meta?.runId??e.flags?.runId,leaseId:e.meta?.leaseId??e.flags?.leaseId,sessionIsolation:e.meta?.sessionIsolation??e.flags?.sessionIsolation,lockPolicy:e.meta?.lockPolicy,lockPlatform:e.meta?.lockPlatform,...i.uploadedArtifactId?{uploadedArtifactId:i.uploadedArtifactId}:{},...i.clientArtifactPaths?{clientArtifactPaths:i.clientArtifactPaths}:{},...i.installSource?{installSource:i.installSource}:{}}};return h({level:"info",phase:"daemon_request_prepare",data:{requestId:t,command:e.command,session:e.session}}),await g("daemon_request",async()=>await et(a,s,n.transportPreference,o),{requestId:t,command:e.command})}async function $(e,t){let r,a=[...e.positionals??[]],i=e.flags?{...e.flags}:void 0,s=e.meta?.installSource,l={};if(el(t)){let n=function(e,t){if("screenshot"===e.command){let r=j(e,"path",".png");return t[0]?{field:"path",localPath:r,positionalIndex:0,positionalPath:q("screenshot",".png")}:{field:"path",localPath:r,positionalIndex:0,flagPath:q("screenshot",".png")}}if("record"===e.command&&"start"===(t[0]??"").toLowerCase()){let t=j(e,"outPath",".mp4",1);return{field:"outPath",localPath:t,positionalIndex:1,positionalPath:q("recording",o.extname(t)||".mp4")}}return null}(e,a);n&&(void 0!==n.positionalPath&&(a[n.positionalIndex]=n.positionalPath),void 0!==n.flagPath&&((i??={}).out=n.flagPath),l[n.field]=n.localPath);let u=await F(e,t);u&&(s=u.installSource,r=u.uploadedArtifactId??r)}if(!el(t)||"install"!==e.command&&"reinstall"!==e.command||a.length<2)return{positionals:a,flags:i,installSource:s,uploadedArtifactId:r,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let u=a[1];if(u.startsWith("remote:"))return a[1]=u.slice(7),{positionals:a,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let d=o.isAbsolute(u)?u:o.resolve(e.meta?.cwd??process.cwd(),u);return n.existsSync(d)?{positionals:a,flags:i,installSource:s,uploadedArtifactId:r=await R({localPath:d,baseUrl:t.baseUrl,token:t.token}),...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}:{positionals:a,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}}async function F(e,t){let r=e.meta?.installSource;if("install_source"!==e.command||!r||"path"!==r.kind)return null;let a=r.path.trim();if(!a)return{installSource:r};if(a.startsWith("remote:"))return{installSource:{...r,path:a.slice(7)}};let i=o.isAbsolute(a)?a:o.resolve(e.meta?.cwd??process.cwd(),a);if(!n.existsSync(i))return{installSource:{...r,path:i}};let s=await R({localPath:i,baseUrl:t.baseUrl,token:t.token});return{installSource:{...r,path:i},uploadedArtifactId:s}}function j(e,t,r,n=0){let a=e.positionals?.[n]??e.flags?.out,i=`${"path"===t?"screenshot":"recording"}-${Date.now()}${r}`,s=a&&a.trim().length>0?a:i;return o.isAbsolute(s)?s:o.resolve(e.meta?.cwd??process.cwd(),s)}function q(e,t){let r=t.startsWith(".")?t:`.${t}`;return o.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${r}`)}async function B(e){let t;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await Q(t,"http"))return t;throw new w("COMMAND_FAILED","Remote daemon is unavailable",{daemonBaseUrl:e.remoteBaseUrl,hint:"Verify AGENT_DEVICE_DAEMON_BASE_URL points to a reachable daemon with GET /health and POST /rpc."})}let r=J(e.paths.infoPath),a=function(){try{let e=b();return JSON.parse(n.readFileSync(o.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),i=function(e,t=b()){try{let r=n.statSync(e),a=o.relative(t,e)||e;return`${a}:${r.size}:${Math.trunc(r.mtimeMs)}`}catch{return"unknown"}}((t=ee()).useSrc?t.srcPath:t.distPath,t.root),s=!!r&&await Q(r,e.transportPreference);if(r&&r.version===a&&r.codeSignature===i&&s)return r;r&&(r.version!==a||r.codeSignature!==i||!s)&&(await H(r),Y(e.paths.infoPath)),function(e){let t=K(e);if(!t.hasLock||t.hasInfo)return;let r=W(e.lockPath);if(!r)return Y(e.lockPath);D(r.pid,r.processStartTime)||Y(e.lockPath)}(e.paths);let l=0;for(let t=1;t<=x;t+=1){await Z(e);let r=await G(O,e);if(r)return r;if(await z(e.paths)){l+=1;continue}let n=K(e.paths);if(!(t<x))break;if(!n.hasInfo&&!n.hasLock){await V(150);continue}}let u=K(e.paths);throw new w("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:O,startupAttempts:x,lockRecoveryCount:l,metadataState:u,hint:function(e,t=N(process.env.AGENT_DEVICE_STATE_DIR)){return e.hasLock&&!e.hasInfo?`Detected ${t.lockPath} without ${t.infoPath}. If no agent-device daemon process is running, delete ${t.lockPath} and retry.`:e.hasLock&&e.hasInfo?`Daemon metadata may be stale. If no agent-device daemon process is running, delete ${t.infoPath} and ${t.lockPath}, then retry.`:`Daemon metadata is missing or stale. Delete ${t.infoPath} if present and retry.`}(u,e.paths)})}async function G(e,t){let r=Date.now();for(;Date.now()-r<e;){let e=J(t.paths.infoPath);if(e&&await Q(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function V(e){await new Promise(t=>setTimeout(t,e))}async function z(e){let t=K(e);if(!t.hasLock||t.hasInfo)return!1;let r=W(e.lockPath);return r&&D(r.pid,r.processStartTime)&&await _(r.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:r.processStartTime}),Y(e.lockPath),!0}async function H(e){await _(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function J(e){let t=X(e);if(!t||"object"!=typeof t)return null;let r="string"==typeof t.token&&t.token.length>0?t.token:null;if(!r)return null;let n=Number.isInteger(t.port)&&Number(t.port)>0,o=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;if(!n&&!o)return null;let a=t.transport,i="string"==typeof t.version?t.version:void 0,s="string"==typeof t.codeSignature?t.codeSignature:void 0,l="string"==typeof t.processStartTime?t.processStartTime:void 0,u=Number.isInteger(t.pid)&&Number(t.pid)>0;return{token:r,port:n?Number(t.port):void 0,httpPort:o?Number(t.httpPort):void 0,transport:"socket"===a||"http"===a||"dual"===a?a:void 0,pid:u?Number(t.pid):0,version:i,codeSignature:s,processStartTime:l}}function W(e){let t=X(e);return t&&"object"==typeof t&&Number.isInteger(t.pid)&&Number(t.pid)>0?{pid:Number(t.pid),processStartTime:"string"==typeof t.processStartTime?t.processStartTime:void 0,startedAt:"number"==typeof t.startedAt?t.startedAt:void 0}:null}function K(e){return{hasInfo:n.existsSync(e.infoPath),hasLock:n.existsSync(e.lockPath)}}function X(e){if(!n.existsSync(e))return null;try{return JSON.parse(n.readFileSync(e,"utf8"))}catch{return null}}function Y(e){try{n.existsSync(e)&&n.unlinkSync(e)}catch{}}async function Q(n,o){var a;return"http"===er(n,o)?await function(e){let n=e.baseUrl?eu(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!n)return Promise.resolve(!1);let o=new URL(n),a="https:"===o.protocol?r:t,i=e.baseUrl?3e3:500;return new Promise(e=>{let t=a.request({protocol:o.protocol,host:o.hostname,port:o.port,path:o.pathname+o.search,method:"GET",timeout:i},t=>{t.resume(),e((t.statusCode??500)<500)});t.on("timeout",()=>{t.destroy(),e(!1)}),t.on("error",()=>{e(!1)}),t.end()})}(n):await ((a=n.port)?new Promise(t=>{let r=e.createConnection({host:"127.0.0.1",port:a},()=>{r.destroy(),t(!0)});r.on("error",()=>{t(!1)})}):Promise.resolve(!1))}async function Z(e){let t=ee(),r=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],n={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};I(process.execPath,r,{env:n})}function ee(){let e=b(),t=o.join(e,"dist","src","daemon.js"),r=o.join(e,"src","daemon.ts"),a=n.existsSync(t),i=n.existsSync(r);if(!a&&!i)throw new w("COMMAND_FAILED","Daemon entry not found",{distPath:t,srcPath:r});return{root:e,distPath:t,srcPath:r,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!a&&i}}async function et(e,t,r,n){return"http"===er(e,r)?await es(e,t,n):await ei(e,t,n)}function er(e,t){if(e.baseUrl){if("socket"===t)throw new w("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t||"socket"===t){var r=e,n=t;if(en(r,n))return n;throw new w("COMMAND_FAILED","http"===n?"Daemon HTTP endpoint is unavailable":"Daemon socket endpoint is unavailable")}let o=("socket"===e.transport||"dual"===e.transport?["socket","http"]:["http","socket"]).find(t=>en(e,t));if(o)return o;throw new w("COMMAND_FAILED","Daemon metadata has no reachable transport")}function en(e,t){return"http"===t?!!e.httpPort:!!e.port}function eo(e,t,r,n,o,a){let i=o?{terminated:0}:function(){let e=0;try{for(let t of C){let r=y("pkill",["-f",t],{allowFailure:!0});0===r.exitCode&&(e+=1)}return{terminated:e}}catch(t){return{terminated:e,error:t instanceof Error?t.message:String(t)}}}(),s=o?{forcedKill:!1}:function(e,t){let r=!1;try{D(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),r=!0)}catch{_(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{Y(t.infoPath),Y(t.lockPath)}return{forcedKill:r}}(e,t);return h({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:a,requestId:r,command:n,timedOutRunnerPidsTerminated:i.terminated,timedOutRunnerCleanupError:i.error,daemonPidReset:o?void 0:e.pid,daemonPidForceKilled:o?void 0:s.forcedKill,daemonBaseUrl:e.baseUrl}}),new w("COMMAND_FAILED","Daemon request timed out",{timeoutMs:a,requestId:r,hint:o?"Retry with --debug and verify the remote daemon URL, auth token, and remote host logs.":"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."})}function ea(e,t,r){return h({level:"error",phase:"daemon_request_socket_error",data:{requestId:t,message:e instanceof Error?e.message:String(e)}}),new w("COMMAND_FAILED","Failed to communicate with daemon",{requestId:t,hint:r?"Retry command. If this persists, verify the remote daemon URL, auth token, and remote host reachability.":"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e instanceof Error?e:void 0)}async function ei(t,r,n){let o=t.port;if(!o)throw new w("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((a,i)=>{let s=e.createConnection({host:"127.0.0.1",port:o},()=>{s.write(`${JSON.stringify(r)}
3
+ `)}),l=N(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),u="number"==typeof n?setTimeout(()=>{s.destroy(),i(eo(t,l,r.meta?.requestId,r.command,!1,n))},n):void 0,d="";s.setEncoding("utf8"),s.on("data",e=>{let t=(d+=e).indexOf("\n");if(-1===t)return;let n=d.slice(0,t).trim();if(n)try{let e=JSON.parse(n);s.end(),u&&clearTimeout(u),a(e)}catch(e){u&&clearTimeout(u),i(new w("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:n},e instanceof Error?e:void 0))}}),s.on("error",e=>{u&&clearTimeout(u),i(ea(e,r.meta?.requestId,!1))})})}async function es(e,n,o){let a=e.baseUrl?new URL(eu(e.baseUrl,"rpc")):e.httpPort?new URL(`http://127.0.0.1:${e.httpPort}/rpc`):null;if(!a)throw new w("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let i=JSON.stringify({jsonrpc:"2.0",id:n.meta?.requestId??f(),method:"agent_device.command",params:n}),s={"content-type":"application/json","content-length":Buffer.byteLength(i)};return e.baseUrl&&e.token&&(s.authorization=`Bearer ${e.token}`,s["x-agent-device-token"]=e.token),await new Promise((l,u)=>{let d=N(n.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),c=("https:"===a.protocol?r:t).request({protocol:a.protocol,host:a.hostname,port:a.port,method:"POST",path:a.pathname+a.search,headers:s},t=>{let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{m&&clearTimeout(m);try{let t=JSON.parse(r);if(t.error){let e=t.error.data??{};u(new w(String(e.code??"COMMAND_FAILED"),String(e.message??t.error.message??"Daemon RPC request failed"),{..."object"==typeof e.details&&e.details?e.details:{},hint:"string"==typeof e.hint?e.hint:void 0,diagnosticId:"string"==typeof e.diagnosticId?e.diagnosticId:void 0,logPath:"string"==typeof e.logPath?e.logPath:void 0,requestId:n.meta?.requestId}));return}if(!t.result||"object"!=typeof t.result)return void u(new w("COMMAND_FAILED","Invalid daemon RPC response",{requestId:n.meta?.requestId}));if(e.baseUrl&&t.result.ok)return void ed(e,n,t.result).then(l).catch(u);l(t.result)}catch(e){m&&clearTimeout(m),u(new w("COMMAND_FAILED","Invalid daemon response",{requestId:n.meta?.requestId,line:r},e instanceof Error?e:void 0))}})}),p=el(e),m="number"==typeof o?setTimeout(()=>{c.destroy(),u(eo(e,d,n.meta?.requestId,n.command,p,o))},o):void 0;c.on("error",e=>{m&&clearTimeout(m),u(ea(e,n.meta?.requestId,p))}),c.write(i),c.end()})}function el(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function eu(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function ed(e,t,r){let n=Array.isArray(r.data?.artifacts)?r.data.artifacts:[];if(0===n.length||!e.baseUrl)return r;let a=r.data?{...r.data}:{},i=[];for(let r of n){if(!r||"object"!=typeof r||"string"!=typeof r.artifactId){i.push(r);continue}let n=function(e,t){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let r=e.fileName?.trim()||`${e.field}-${Date.now()}`;return o.resolve(t.meta?.cwd??process.cwd(),r)}(r,t);await ec({baseUrl:e.baseUrl,token:e.token,artifactId:r.artifactId,destinationPath:n,requestId:t.meta?.requestId}),a[r.field]=n,i.push({...r,localPath:n})}return a.artifacts=i,{ok:!0,data:a}}async function ec(e){var a,i;let s,l=new URL((a=e.baseUrl,i=e.artifactId,s=a.endsWith("/")?a:`${a}/`,new URL(`upload/${encodeURIComponent(i)}`,s).toString())),u="https:"===l.protocol?r:t;await n.promises.mkdir(o.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,r)=>{let o=!1,a=e.timeoutMs??U,i=a=>{if(!o){if(o=!0,clearTimeout(d),a)return void n.promises.rm(e.destinationPath,{force:!0}).finally(()=>r(a));t()}},s=u.request({protocol:l.protocol,host:l.hostname,port:l.port,method:"GET",path:l.pathname+l.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{i(new w("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:r}))});return}let r=n.createWriteStream(e.destinationPath);r.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("aborted",()=>{i(new w("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),r.on("finish",()=>{r.close(()=>i())}),t.pipe(r)}),d=setTimeout(()=>{s.destroy(new w("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:a}))},a);s.on("error",t=>{t instanceof w?i(t):i(new w("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:a},t instanceof Error?t:void 0))}),s.end()})}function ep(e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if(!e)return 9e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):9e4}function em(e){return e.replace(/\/+$/,"")}function ef(e){return"string"==typeof e&&e.trim()?em(e.trim()):""}function eh(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function eg(e,t,r){return T(e,{env:t,cwd:r})}function ew(e){try{return n.accessSync(e,n.constants.F_OK),!0}catch{return!1}}function ey(e,t,r){if(null==e||""===e)return t;let n=Number.parseInt(String(e),10);return Number.isInteger(n)?Math.max(n,r):t}function eI(e,t){let r;return{platform:t,bundleUrl:((r=new URL(`${em(e)}/index.bundle`)).searchParams.set("platform",t),r.searchParams.set("dev","true"),r.searchParams.set("minify","false"),r.toString())}}function ev(e,t){return{platform:t,metroHost:eh(e?.metro_host),metroPort:e?.metro_port,bundleUrl:eh(e?.metro_bundle_url),launchUrl:eh(e?.launch_url)}}async function eb(e){await new Promise(t=>setTimeout(t,e))}async function eA(e,t,r={}){try{let n=await fetch(e,{headers:r,signal:AbortSignal.timeout(t)});return{ok:n.ok,status:n.status,body:await n.text()}}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw Error(`Timed out fetching ${e} after ${t}ms`);throw r}}async function eE(e,t){try{let r=await eA(e,t);return r.ok&&r.body.includes("packager-status:running")}catch{return!1}}async function eD(e){var t,r,n;let o;try{o=await fetch(`${e.baseUrl}/api/metro/bridge`,{method:"POST",headers:(t=e.baseUrl,r=e.bearerToken,{Authorization:`Bearer ${r}`,"Content-Type":"application/json",...t.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({ios_runtime:e.runtime,timeout_ms:e.timeoutMs}),signal:AbortSignal.timeout(e.timeoutMs)})}catch(t){if(t instanceof Error&&"TimeoutError"===t.name)throw Error(`/api/metro/bridge timed out after ${e.timeoutMs}ms calling ${e.baseUrl}/api/metro/bridge`);throw t}let a=await o.text(),i=a?JSON.parse(a):{};if(!o.ok)throw Error(`/api/metro/bridge failed (${o.status}): ${JSON.stringify(i)}`);return{enabled:(n=i.data??i).enabled,baseUrl:n.base_url,statusUrl:n.status_url,bundleUrl:n.bundle_url,iosRuntime:ev(n.ios_runtime,"ios"),androidRuntime:ev(n.android_runtime,"android"),upstream:{bundleUrl:n.upstream.bundle_url,host:n.upstream.host,port:n.upstream.port,statusUrl:n.upstream.status_url},probe:{reachable:n.probe.reachable,statusCode:n.probe.status_code,latencyMs:n.probe.latency_ms,detail:n.probe.detail}}}async function eS(e,t,r){let n=Date.now()+t;for(;Date.now()<n;){let t=Math.min(r,Math.max(n-Date.now(),1));if(await eE(e,t))return!0;let o=Math.min(500,Math.max(n-Date.now(),0));o>0&&await eb(o)}return!1}async function eM(e={}){let t=e.env??process.env,r=process.cwd(),a=eg(e.projectRoot??r,t,r),i=function(e,t){if("auto"!==t)return t;let r=function(e){let t=o.join(e,"package.json");if(!ew(t))throw new w("INVALID_ARGS",`package.json not found at ${t}`);return JSON.parse(n.readFileSync(t,"utf8"))}(e);return"string"==typeof({...r.dependencies??{},...r.devDependencies??{}}).expo?"expo":"react-native"}(a,e.kind??"auto"),s=function(e){if(null==e||""===e)return 8081;let t=Number.parseInt(String(e),10);if(!Number.isInteger(t)||t<1||t>65535)throw new w("INVALID_ARGS",`Invalid Metro port: ${String(e)}. Use 1-65535.`);return t}(e.metroPort??8081),l=eh(e.listenHost)??"0.0.0.0",u=eh(e.statusHost)??"127.0.0.1",d=ef(e.publicBaseUrl),c=ey(e.startupTimeoutMs,18e4,3e4),p=ey(e.probeTimeoutMs,1e4,1e3),m=e.reuseExisting??!0,f=e.installDependenciesIfNeeded??!0,h=e.runtimeFilePath?eg(e.runtimeFilePath,t,r):null,g=eg(e.logPath??o.join(a,".agent-device","metro.log"),t,r);if(!d)throw new w("INVALID_ARGS","metro prepare requires --public-base-url <url>.");let{proxyEnabled:v,proxyBaseUrl:b,proxyBearerToken:A}=function(e,t){if(e&&!t)throw new w("INVALID_ARGS","metro prepare requires proxy auth when --proxy-base-url is provided. Pass --bearer-token or set AGENT_DEVICE_PROXY_TOKEN.");if(!e&&t)throw new w("INVALID_ARGS","metro prepare requires --proxy-base-url when proxy auth is provided.");return{proxyEnabled:!!(e&&t),proxyBaseUrl:e,proxyBearerToken:t}}(ef(e.proxyBaseUrl),eh(e.proxyBearerToken)??""),E=f?function(e,t){if(function(e){try{return n.statSync(e).isDirectory()}catch{return!1}}(o.join(e,"node_modules")))return{installed:!1};let r=ew(o.join(e,"pnpm-lock.yaml"))?{command:"pnpm",installArgs:["install"]}:ew(o.join(e,"yarn.lock"))?{command:"yarn",installArgs:["install"]}:{command:"npm",installArgs:["install"]};return y(r.command,r.installArgs,{cwd:e,env:t}),{installed:!0,packageManager:r.command}}(a,t):{installed:!1},D=`http://${u}:${s}/status`,S=!1,M=!1,_=0;if(m&&await eE(D,p))M=!0;else if(S=!0,_=function(e,t,r,a,i,s){let l="expo"===t?{command:"npx",installArgs:["expo","start","--host","lan","--port",String(r)]}:{command:"npx",installArgs:["react-native","start","--host",a,"--port",String(r)]};n.mkdirSync(o.dirname(i),{recursive:!0});let u=n.openSync(i,"a"),d=0;try{d=I(l.command,l.installArgs,{cwd:e,env:s,stdio:["ignore",u,u]})}finally{n.closeSync(u)}if(!Number.isInteger(d)||d<=0)throw Error("Failed to start Metro. Expected a detached child PID.");return{pid:d}}(a,i,s,l,g,t).pid,!await eS(D,c,p))throw Error(`Metro did not become ready at ${D} within ${c}ms. Check ${g}.`);let P=eI(d,"ios"),k=eI(d,"android"),T=null,N=null;if(v)try{T=await eD({baseUrl:b,bearerToken:A,runtime:{metro_bundle_url:P.bundleUrl},timeoutMs:p})}catch(e){N=e instanceof Error?e.message:String(e)}if(v&&(!T||!1===T.probe.reachable)){var R,U;let e;throw Error((R=N,U=T,e=[`Metro bridge is required for this run but could not be configured via ${b}/api/metro/bridge.`],R&&e.push(`bridgeError=${R}`),U?.probe.reachable===!1&&e.push(`bridgeProbe=${U.probe.detail||`unreachable (status ${U.probe.statusCode||0})`}`),e.join(" ")))}let O=T?.iosRuntime??P,x=T?.androidRuntime??k,C={projectRoot:a,kind:i,dependenciesInstalled:E.installed,packageManager:E.packageManager??null,started:S,reused:M,pid:_,logPath:g,statusUrl:D,runtimeFilePath:h,iosRuntime:O,androidRuntime:x,bridge:T};return h&&(n.mkdirSync(o.dirname(h),{recursive:!0}),n.writeFileSync(h,JSON.stringify(C,null,2))),C}function e_(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function eP(e,t,r){return{deviceId:t,deviceName:r,..."android"===e?{serial:t}:"ios"===e?{udid:t}:{}}}function ek(e,t,r,n){let o=r(e[t]);if(void 0===o)throw new w("COMMAND_FAILED",n,{response:e});return o}function eT(e,t){return ek(e,t,eC,`Daemon response is missing "${t}".`)}function eN(e,t){return eC(e[t])}function eR(e,t){var r;let n;return r=eC,null===(n=e[t])?null:r(n)}function eU(e,t){return ek(e,t,e$,`Daemon response has invalid "${t}".`)}function eO(e,t){return function(e){return"tv"===e||"mobile"===e||"desktop"===e?e:void 0}(e[t])??"mobile"}function ex(e,t){let r=e[t];if(!eq(r))return;let n="number"==typeof r.x?r.x:void 0,o="number"==typeof r.y?r.y:void 0,a="number"==typeof r.width?r.width:void 0,i="number"==typeof r.height?r.height:void 0;if(void 0!==n&&void 0!==o&&void 0!==a&&void 0!==i)return{x:n,y:o,width:a,height:i}}function eC(e){return"string"==typeof e&&e.length>0?e:void 0}function eL(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function e$(e){return"ios"===e||"macos"===e||"android"===e?e:void 0}function eF(e){return"simulator"===e||"emulator"===e||"device"===e?e:void 0}function ej(e){if(!eq(e))throw new w("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function eq(e){return"object"==typeof e&&null!==e}function eB(e){let t={};for(let[r,n]of Object.entries(e))void 0!==n&&(t[r]=n);return t}function eG(e,t){let r=eN(e,"bundleId"),n=eN(e,"package");return{app:eT(e,"app"),appPath:eT(e,"appPath"),platform:eU(e,"platform"),appId:r??n,bundleId:r,package:n,identifiers:e_({session:t,bundleId:r,packageName:n})}}function eV(e){let t=ej(e),r=eU(t,"platform"),n=eT(t,"id"),o=eT(t,"name");return{platform:r,target:eO(t,"target"),kind:ek(t,"kind",eF,'Daemon response has invalid "kind".'),id:n,name:o,booted:"boolean"==typeof t.booted?t.booted:void 0,identifiers:eP(r,n,o),ios:"ios"===r?{udid:n}:void 0,android:"android"===r?{serial:n}:void 0}}function ez(e){let t=ej(e),r=eU(t,"platform"),n=eT(t,"id"),o=eT(t,"name"),a=eO(t,"target"),i=eT(t,"device"),s={session:o,...eP(r,n,i)};return{name:o,createdAt:ek(t,"createdAt",eL,'Daemon response is missing numeric "createdAt".'),device:{platform:r,target:a,id:n,name:i,identifiers:s,ios:"ios"===r?{udid:n,simulatorSetPath:eR(t,"ios_simulator_device_set")}:void 0,android:"android"===r?{serial:n}:void 0},identifiers:s}}function eH(e,t){return t??e??"default"}function eJ(e={},t={}){let r=t.transport??L,n=async(t,n=[],o={})=>{let a={...e,...o},i=await r({session:eH(e.session,o.session),command:t,positionals:n,flags:eB({stateDir:a.stateDir,daemonBaseUrl:a.daemonBaseUrl,daemonAuthToken:a.daemonAuthToken,daemonTransport:a.daemonTransport,daemonServerMode:a.daemonServerMode,tenant:a.tenant,sessionIsolation:a.sessionIsolation,runId:a.runId,leaseId:a.leaseId,platform:a.platform,target:a.target,device:a.device,udid:a.udid,serial:a.serial,iosSimulatorDeviceSet:a.iosSimulatorDeviceSet,androidDeviceAllowlist:a.androidDeviceAllowlist,runtime:a.simulatorRuntimeId,boot:a.boot,reuseExisting:a.reuseExisting,surface:a.surface,activity:a.activity,relaunch:a.relaunch,shutdown:a.shutdown,saveScript:a.saveScript,noRecord:a.noRecord,metroHost:a.metroHost,metroPort:a.metroPort,bundleUrl:a.bundleUrl,launchUrl:a.launchUrl,snapshotInteractiveOnly:a.interactiveOnly,snapshotCompact:a.compact,snapshotDepth:a.depth,snapshotScope:a.scope,snapshotRaw:a.raw,overlayRefs:a.overlayRefs,verbose:a.debug}),runtime:a.runtime,meta:eB({requestId:a.requestId,cwd:a.cwd,debug:a.debug,lockPolicy:a.lockPolicy,lockPlatform:a.lockPlatform,tenantId:a.tenant,runId:a.runId,leaseId:a.leaseId,sessionIsolation:a.sessionIsolation,installSource:a.installSource,retainMaterializedPaths:a.retainMaterializedPaths,materializedPathRetentionMs:a.materializedPathRetentionMs,materializationId:a.materializationId})});if(!i.ok)throw new w(i.error.code,i.error.message,{...i.error.details??{},hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath});return i.data??{}},o=async(e={})=>{let t=await n("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(ez)};return{devices:{list:async(e={})=>{let t=await n("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(eV)}},sessions:{list:async(e={})=>await o(e),close:async(t={})=>{let r=eH(e.session,t.session),o=(await n("close",[],t)).shutdown;return{session:r,shutdown:"object"==typeof o&&null!==o?o:void 0,identifiers:{session:r}}}},simulators:{ensure:async e=>{let{runtime:t,...r}=e,o=await n("ensure-simulator",[],{...r,simulatorRuntimeId:t}),a=eT(o,"udid"),i=eT(o,"device");return{udid:a,device:i,runtime:eT(o,"runtime"),created:!0===o.created,booted:!0===o.booted,iosSimulatorDeviceSet:eR(o,"ios_simulator_device_set"),identifiers:{deviceId:a,deviceName:i,udid:a}}}},apps:{install:async t=>eG(await n("install",[t.app,t.appPath],t),eH(e.session,t.session)),reinstall:async t=>eG(await n("reinstall",[t.app,t.appPath],t),eH(e.session,t.session)),installFromSource:async t=>(function(e,t){let r=eN(e,"bundleId"),n=eN(e,"packageName"),o=r??n??eN(e,"appId"),a=eN(e,"launchTarget")??n??r??o;if(!a)throw new w("COMMAND_FAILED",'Daemon response is missing "launchTarget".',{response:e});return{appName:eN(e,"appName"),appId:o,bundleId:r,packageName:n,launchTarget:a,installablePath:eN(e,"installablePath"),archivePath:eN(e,"archivePath"),materializationId:eN(e,"materializationId"),materializationExpiresAt:eN(e,"materializationExpiresAt"),identifiers:e_({session:t,bundleId:r,packageName:n,appId:o})}})(await n("install_source",[],{...t,installSource:t.source,retainMaterializedPaths:t.retainPaths,materializedPathRetentionMs:t.retentionMs}),eH(e.session,t.session)),open:async t=>{let r=eH(e.session,t.session),o=t.url?[t.app,t.url]:[t.app],a=await n("open",o,t),i=function(e){let t=e.platform,r=eN(e,"id"),n=eN(e,"device");if("ios"!==t&&"macos"!==t&&"android"!==t||!r||!n)return;let o=eO(e,"target"),a=eP(t,r,n);return{platform:t,target:o,id:r,name:n,identifiers:a,ios:"ios"===t?{udid:eN(e,"device_udid")??r,simulatorSetPath:eR(e,"ios_simulator_device_set")}:void 0,android:"android"===t?{serial:eN(e,"serial")??r}:void 0}}(a),s=eN(a,"appBundleId");return{session:r,appName:eN(a,"appName"),appBundleId:s,appId:s,startup:function(e){if(eq(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:eN(e,"appTarget"),appBundleId:eN(e,"appBundleId")}}(a.startup),runtime:function(e){if(!eq(e))return;let t=e.platform,r=eN(e,"metroHost"),n="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:r,metroPort:n,bundleUrl:eN(e,"bundleUrl"),launchUrl:eN(e,"launchUrl")}}(a.runtime),device:i,identifiers:{session:r,deviceId:i?.id,deviceName:i?.name,udid:i?.ios?.udid,serial:i?.android?.serial,appId:s,appBundleId:s}}},close:async(t={})=>{let r=eH(e.session,t.session),o=(await n("close",t.app?[t.app]:[],t)).shutdown;return{session:r,closedApp:t.app,shutdown:"object"==typeof o&&null!==o?o:void 0,identifiers:{session:r}}}},materializations:{release:async e=>{var t;return{released:!0===(t=await n("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:eT(t,"materializationId"),identifiers:{}}}},metro:{prepare:async t=>await eM({projectRoot:t.projectRoot??e.cwd,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.bearerToken,metroPort:t.port,listenHost:t.listenHost,statusHost:t.statusHost,startupTimeoutMs:t.startupTimeoutMs,probeTimeoutMs:t.probeTimeoutMs,reuseExisting:t.reuseExisting,installDependenciesIfNeeded:t.installDependenciesIfNeeded,runtimeFilePath:t.runtimeFilePath,logPath:t.logPath})},capture:{snapshot:async(t={})=>{var r;let o=eH(e.session,t.session),a=await n("snapshot",[],t),i=eN(a,"appBundleId");return{nodes:Array.isArray(r=a.nodes)?r:[],truncated:!0===a.truncated,appName:eN(a,"appName"),appBundleId:i,identifiers:{session:o,appId:i,appBundleId:i}}},screenshot:async(t={})=>{let r=eH(e.session,t.session),o=await n("screenshot",t.path?[t.path]:[],t);return{path:eT(o,"path"),overlayRefs:function(e){let t=e.overlayRefs;if(!Array.isArray(t))return;let r=[];for(let e of t){if(!eq(e))continue;let t=eN(e,"ref"),n=ex(e,"rect"),o=ex(e,"overlayRect"),a=function(e,t){let r=e[t];if(!eq(r))return;let n="number"==typeof r.x?r.x:void 0,o="number"==typeof r.y?r.y:void 0;if(void 0!==n&&void 0!==o)return{x:n,y:o}}(e,"center");t&&n&&o&&a&&r.push({ref:t,label:eN(e,"label"),rect:n,overlayRect:o,center:a})}return r}(o),identifiers:{session:r}}}}}}export{eJ as createAgentDeviceClient,w as AppError};
@@ -528,15 +528,26 @@ extension RunnerTests {
528
528
  needsPostSnapshotInteractionDelay = true
529
529
  return Response(ok: true, data: snapshotFast(app: activeApp, options: options))
530
530
  case .screenshot:
531
- // If a target app bundle ID is provided, activate it first so the screenshot
532
- // captures the target app rather than the AgentDeviceRunner itself.
531
+ let screenshot: XCUIScreenshot
532
+ #if os(macOS)
533
+ // macOS keeps the app-targeted capture behavior for window-level screenshots.
533
534
  if let bundleId = command.appBundleId, !bundleId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
534
535
  let targetApp = XCUIApplication(bundleIdentifier: bundleId)
535
536
  targetApp.activate()
537
+ activeApp = targetApp
536
538
  // Brief wait for the app transition animation to complete
537
539
  Thread.sleep(forTimeInterval: 0.5)
538
540
  }
539
- let screenshot = XCUIScreen.main.screenshot()
541
+ if command.fullscreen == true {
542
+ screenshot = XCUIScreen.main.screenshot()
543
+ } else if let bundleId = command.appBundleId, !bundleId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
544
+ screenshot = screenshotRoot(app: activeApp).screenshot()
545
+ } else {
546
+ screenshot = XCUIScreen.main.screenshot()
547
+ }
548
+ #else
549
+ screenshot = XCUIScreen.main.screenshot()
550
+ #endif
540
551
  guard let pngData = runnerPngData(for: screenshot.image) else {
541
552
  return Response(ok: false, error: ErrorPayload(message: "Failed to encode screenshot as PNG"))
542
553
  }
@@ -38,6 +38,16 @@ extension RunnerTests {
38
38
  return image
39
39
  }
40
40
 
41
+ func screenshotRoot(app: XCUIApplication) -> XCUIElement {
42
+ #if os(macOS)
43
+ let windows = app.windows.allElementsBoundByIndex
44
+ if let window = windows.first(where: { $0.exists && !$0.frame.isNull && !$0.frame.isEmpty }) {
45
+ return window
46
+ }
47
+ #endif
48
+ return app
49
+ }
50
+
41
51
  func stopRecordingIfNeeded() {
42
52
  guard let recorder = activeRecording else { return }
43
53
  do {
@@ -55,6 +55,7 @@ struct Command: Codable {
55
55
  let depth: Int?
56
56
  let scope: String?
57
57
  let raw: Bool?
58
+ let fullscreen: Bool?
58
59
  }
59
60
 
60
61
  struct Response: Codable {
@@ -21,27 +21,39 @@ extension RunnerTests {
21
21
  status: 413,
22
22
  response: Response(ok: false, error: ErrorPayload(message: "request too large"))
23
23
  )
24
- connection.send(content: response, completion: .contentProcessed { [weak self] _ in
25
- connection.cancel()
24
+ self.sendResponse(response, over: connection) { [weak self] in
26
25
  self?.finish()
27
- })
26
+ }
28
27
  return
29
28
  }
30
29
  let combined = buffer + data
31
30
  if let body = self.parseRequest(data: combined) {
32
31
  let result = self.handleRequestBody(body)
33
- connection.send(content: result.data, completion: .contentProcessed { _ in
34
- connection.cancel()
32
+ self.sendResponse(result.data, over: connection) { [weak self] in
35
33
  if result.shouldFinish {
36
- self.finish()
34
+ self?.finish()
37
35
  }
38
- })
36
+ }
39
37
  } else {
40
38
  self.receiveRequest(connection: connection, buffer: combined)
41
39
  }
42
40
  }
43
41
  }
44
42
 
43
+ private func sendResponse(
44
+ _ response: Data,
45
+ over connection: NWConnection,
46
+ afterSend: @escaping () -> Void = {}
47
+ ) {
48
+ connection.send(content: response, isComplete: true, completion: .contentProcessed { error in
49
+ if let error {
50
+ NSLog("AGENT_DEVICE_RUNNER_SEND_FAILED=%@", String(describing: error))
51
+ }
52
+ connection.cancel()
53
+ afterSend()
54
+ })
55
+ }
56
+
45
57
  private func parseRequest(data: Data) -> Data? {
46
58
  guard let headerEnd = data.range(of: Data("\r\n\r\n".utf8)) else {
47
59
  return nil