agent-device 0.11.7 → 0.11.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/src/995.js +3 -0
- package/dist/src/bin.js +39 -39
- package/dist/src/daemon.js +46 -45
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +3 -3
- package/package.json +12 -3
- package/src/platforms/linux/atspi-dump.py +285 -0
- package/dist/src/168.js +0 -3
package/dist/src/index.d.ts
CHANGED
|
@@ -246,7 +246,7 @@ declare type CliFlags = {
|
|
|
246
246
|
sessionLock?: 'reject' | 'strip';
|
|
247
247
|
sessionLocked?: boolean;
|
|
248
248
|
sessionLockConflicts?: 'reject' | 'strip';
|
|
249
|
-
platform?: 'ios' | 'macos' | 'android' | 'apple';
|
|
249
|
+
platform?: 'ios' | 'macos' | 'android' | 'linux' | 'apple';
|
|
250
250
|
target?: 'mobile' | 'tv' | 'desktop';
|
|
251
251
|
device?: string;
|
|
252
252
|
udid?: string;
|
|
@@ -498,7 +498,7 @@ declare type MetroRuntimeHints = {
|
|
|
498
498
|
launchUrl?: string;
|
|
499
499
|
};
|
|
500
500
|
|
|
501
|
-
declare type Platform = ApplePlatform | 'android';
|
|
501
|
+
declare type Platform = ApplePlatform | 'android' | 'linux';
|
|
502
502
|
|
|
503
503
|
declare type PlatformSelector = Platform | 'apple';
|
|
504
504
|
|
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 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"),s="object"==typeof a.visibility&&null!==a.visibility?a.visibility:void 0;return{nodes:Array.isArray(r=a.nodes)?r:[],truncated:!0===a.truncated,appName:eN(a,"appName"),appBundleId:i,...s?{visibility:s}:{},warnings:Array.isArray(a.warnings)?a.warnings.filter(e=>"string"==typeof e):void 0,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};
|
|
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"node:fs/promises";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}}let y=/^[A-Za-z0-9][A-Za-z0-9._+-]*$/;function I(e,t,r={}){let n=b(e),o=u(n,t,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"],encoding:r.binaryStdout?void 0:"utf8",input:r.stdin,timeout:A(r.timeoutMs),shell:!1});if(o.error){let a=o.error.code;if("ETIMEDOUT"===a)throw new w("COMMAND_FAILED",`${n} timed out after ${A(r.timeoutMs)}ms`,{cmd:e,args:t,timeoutMs:A(r.timeoutMs)},o.error);if("ENOENT"===a)throw new w("TOOL_MISSING",`${n} not found in PATH`,{cmd:e},o.error);throw new w("COMMAND_FAILED",`Failed to run ${n}`,{cmd:e,args:t},o.error)}let a=r.binaryStdout?Buffer.isBuffer(o.stdout)?o.stdout:Buffer.from(o.stdout??""):void 0,i=r.binaryStdout?"":"string"==typeof o.stdout?o.stdout:(o.stdout??"").toString(),s="string"==typeof o.stderr?o.stderr:(o.stderr??"").toString(),l=o.status??1;if(0!==l&&!r.allowFailure)throw new w("COMMAND_FAILED",`${n} exited with code ${l}`,{cmd:e,args:t,stdout:i,stderr:s,exitCode:l,processExitError:!0});return{stdout:i,stderr:s,exitCode:l,stdoutBuffer:a}}function v(e,t,r={}){let n=l(b(e),t,{cwd:r.cwd,env:r.env,stdio:r.stdio??"ignore",detached:!0,shell:!1});return n.unref(),n.pid??0}function b(e){var t;let r,n=(t={allowRelativePath:!0},!(r=e.trim())||r.includes("\0")?null:o.isAbsolute(r)?r:r.includes("/")||r.includes("\\")?t.allowRelativePath?r:null:y.test(r)?r:null);if(!n)throw new w("INVALID_ARGS",`Invalid executable command: ${JSON.stringify(e)}`,{cmd:e});return n}function A(e){if(!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t}function E(){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 D=[/(^|[/\s"'=])dist\/src\/daemon\.js($|[\s"'])/,/(^|[/\s"'=])src\/daemon\.ts($|[\s"'])/];function S(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch(e){return"EPERM"===e.code}}function M(e,t){let r;if(!S(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")&&D.some(e=>e.test(r))}function _(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 P(e,t){if(!S(e))return!0;let r=Date.now();for(;Date.now()-r<t;)if(await new Promise(e=>setTimeout(e,50)),!S(e))return!0;return!S(e)}async function k(e,t){!M(e,t.expectedStartTime)||!_(e,"SIGTERM")||await P(e,t.termTimeoutMs)||_(e,"SIGKILL")&&await P(e,t.killTimeoutMs)}function T(e){return e?.HOME?.trim()||s.homedir()}function N(e,t={}){return"~"===e?T(t.env):e.startsWith("~/")?o.join(T(t.env),e.slice(2)):e}function R(e,t={}){let r=N(e,t);return o.isAbsolute(r)?r:o.resolve(t.cwd??process.cwd(),r)}function x(e){let t,r=(t=(e??"").trim())?R(t):o.join(N("~"),".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 U(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 O=/(?:^|[^\w$.])(?:import|export)\s+(?:type\s+)?(?:[^'"`]*?\s+from\s+)?['"]([^'"]+)['"]/gm,C=/import\(\s*['"]([^'"]+)['"]\s*\)/gm,L=[".ts",".tsx",".js",".jsx",".mjs",".cjs"];function $(e,t,r){t.lastIndex=0;let n=null;for(;null!==(n=t.exec(e));){let e=n[1]?.trim();e?.startsWith(".")&&r.add(e)}}function F(e){try{return n.statSync(e).isFile()?e:null}catch{return null}}let j=eI(),q=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}(),B=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}(),G=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"];async function V(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:x(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 eI(t)}(e.command),a=await g("daemon_startup",async()=>await K(n),{requestId:t,session:e.session}),i=await z(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 el(a,s,n.transportPreference,o),{requestId:t,command:e.command})}async function z(e,t){let r,a=[...e.positionals??[]],i=e.flags?{...e.flags}:void 0,s=e.meta?.installSource,l={};if(eh(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:W("screenshot",".png")}:{field:"path",localPath:r,positionalIndex:0,flagPath:W("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:W("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 H(e,t);u&&(s=u.installSource,r=u.uploadedArtifactId??r)}if(!eh(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 U({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 H(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 U({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 W(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 K(e){let t;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await ea(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=ee(e.paths.infoPath),a=function(){try{let e=E();return JSON.parse(n.readFileSync(o.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),s=function(e,t=E()){try{let r=o.resolve(t),a=[o.resolve(e)],s=new Set,l=[];for(;a.length>0;){let e=a.pop();if(!e||s.has(e))continue;s.add(e);let t=n.statSync(e);if(!t.isFile())continue;let i=o.relative(r,e)||e;l.push(`${i}:${t.size}:${Math.trunc(t.mtimeMs)}`);let u=n.readFileSync(e,"utf8");for(let t of function(e){let t=new Set;return $(e,O,t),$(e,C,t),[...t]}(u)){let r=function(e,t){let r=o.resolve(o.dirname(e),t),n=F(r);if(n)return n;for(let e of L){let t=F(`${r}${e}`);if(t)return t}for(let e of L){let t=F(o.join(r,`index${e}`));if(t)return t}return null}(e,t);r&&a.push(r)}}let u=l.sort().join("|"),d=i.createHash("sha1").update(u).digest("hex");return`graph:${l.length}:${d}`}catch{return"unknown"}}((t=es()).useSrc?t.srcPath:t.distPath,t.root),l=!!r&&await ea(r,e.transportPreference);if(r&&r.version===a&&r.codeSignature===s&&l)return r;r&&(r.version!==a||r.codeSignature!==s||!l)&&(await Q(r),eo(e.paths.infoPath)),function(e){let t=er(e);if(!t.hasLock||t.hasInfo)return;let r=et(e.lockPath);if(!r)return eo(e.lockPath);M(r.pid,r.processStartTime)||eo(e.lockPath)}(e.paths);let u=0;for(let t=1;t<=B;t+=1){await ei(e);let r=await Z(q,e);if(r)return r;if(await Y(e.paths)){u+=1;continue}let n=er(e.paths);if(!(t<B))break;if(!n.hasInfo&&!n.hasLock){await X(150);continue}}let d=er(e.paths);throw new w("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:q,startupAttempts:B,lockRecoveryCount:u,metadataState:d,hint:function(e,t=x(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 Z(e,t){let r=Date.now();for(;Date.now()-r<e;){let e=ee(t.paths.infoPath);if(e&&await ea(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function X(e){await new Promise(t=>setTimeout(t,e))}async function Y(e){let t=er(e);if(!t.hasLock||t.hasInfo)return!1;let r=et(e.lockPath);return r&&M(r.pid,r.processStartTime)&&await k(r.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:r.processStartTime}),eo(e.lockPath),!0}async function Q(e){await k(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function ee(e){let t=en(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 et(e){let t=en(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 er(e){return{hasInfo:n.existsSync(e.infoPath),hasLock:n.existsSync(e.lockPath)}}function en(e){if(!n.existsSync(e))return null;try{return JSON.parse(n.readFileSync(e,"utf8"))}catch{return null}}function eo(e){try{n.existsSync(e)&&n.unlinkSync(e)}catch{}}async function ea(n,o){var a;return"http"===eu(n,o)?await function(e){let n=e.baseUrl?eg(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 ei(e){let t=es(),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};v(process.execPath,r,{env:n})}function es(){let e=E(),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 el(e,t,r,n){return"http"===eu(e,r)?await ef(e,t,n):await em(e,t,n)}function eu(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(ed(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=>ed(e,t));if(o)return o;throw new w("COMMAND_FAILED","Daemon metadata has no reachable transport")}function ed(e,t){return"http"===t?!!e.httpPort:!!e.port}function ec(e,t,r,n,o,a){let i=o?{terminated:0}:function(){let e=0;try{for(let t of G){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)}}}(),s=o?{forcedKill:!1}:function(e,t){let r=!1;try{M(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),r=!0)}catch{k(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{eo(t.infoPath),eo(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 ep(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 em(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=x(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),u="number"==typeof n?setTimeout(()=>{s.destroy(),i(ec(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(ep(e,r.meta?.requestId,!1))})})}async function ef(e,n,o){let a=e.baseUrl?new URL(eg(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=x(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 ew(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=eh(e),m="number"==typeof o?setTimeout(()=>{c.destroy(),u(ec(e,d,n.meta?.requestId,n.command,p,o))},o):void 0;c.on("error",e=>{m&&clearTimeout(m),u(ep(e,n.meta?.requestId,p))}),c.write(i),c.end()})}function eh(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function eg(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function ew(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 ey({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 ey(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??j,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 eI(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 ev(e){return e.replace(/\/+$/,"")}function eb(e){return"string"==typeof e&&e.trim()?ev(e.trim()):""}function eA(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function eE(e,t,r){return R(e,{env:t,cwd:r})}function eD(e){try{return n.accessSync(e,n.constants.F_OK),!0}catch{return!1}}function eS(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 eM(e,t){let r;return{platform:t,bundleUrl:((r=new URL(`${ev(e)}/index.bundle`)).searchParams.set("platform",t),r.searchParams.set("dev","true"),r.searchParams.set("minify","false"),r.toString())}}function e_(e,t){return{platform:t,metroHost:eA(e?.metro_host),metroPort:e?.metro_port,bundleUrl:eA(e?.metro_bundle_url),launchUrl:eA(e?.launch_url)}}async function eP(e){await new Promise(t=>setTimeout(t,e))}async function ek(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 eT(e,t){try{let r=await ek(e,t);return r.ok&&r.body.includes("packager-status:running")}catch{return!1}}async function eN(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:e_(n.ios_runtime,"ios"),androidRuntime:e_(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 eR(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 eT(e,t))return!0;let o=Math.min(500,Math.max(n-Date.now(),0));o>0&&await eP(o)}return!1}async function ex(e={}){let t=e.env??process.env,r=process.cwd(),a=eE(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(!eD(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=eA(e.listenHost)??"0.0.0.0",u=eA(e.statusHost)??"127.0.0.1",d=eb(e.publicBaseUrl),c=eS(e.startupTimeoutMs,18e4,3e4),p=eS(e.probeTimeoutMs,1e4,1e3),m=e.reuseExisting??!0,f=e.installDependenciesIfNeeded??!0,h=e.runtimeFilePath?eE(e.runtimeFilePath,t,r):null,g=eE(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:y,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}}(eb(e.proxyBaseUrl),eA(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=eD(o.join(e,"pnpm-lock.yaml"))?{command:"pnpm",installArgs:["install"]}:eD(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://${u}:${s}/status`,S=!1,M=!1,_=0;if(m&&await eT(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=v(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 eR(D,c,p))throw Error(`Metro did not become ready at ${D} within ${c}ms. Check ${g}.`);let P=eM(d,"ios"),k=eM(d,"android"),T=null,N=null;if(y)try{T=await eN({baseUrl:b,bearerToken:A,runtime:{metro_bundle_url:P.bundleUrl},timeoutMs:p})}catch(e){N=e instanceof Error?e.message:String(e)}if(y&&(!T||!1===T.probe.reachable)){var R,x;let e;throw Error((R=N,x=T,e=[`Metro bridge is required for this run but could not be configured via ${b}/api/metro/bridge.`],R&&e.push(`bridgeError=${R}`),x?.probe.reachable===!1&&e.push(`bridgeProbe=${x.probe.detail||`unreachable (status ${x.probe.statusCode||0})`}`),e.join(" ")))}let U=T?.iosRuntime??P,O=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:U,androidRuntime:O,bridge:T};return h&&(n.mkdirSync(o.dirname(h),{recursive:!0}),n.writeFileSync(h,JSON.stringify(C,null,2))),C}function eU(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function eO(e,t,r){return{deviceId:t,deviceName:r,..."android"===e?{serial:t}:"ios"===e?{udid:t}:{}}}function eC(e,t,r,n){let o=r(e[t]);if(void 0===o)throw new w("COMMAND_FAILED",n,{response:e});return o}function eL(e,t){return eC(e,t,eG,`Daemon response is missing "${t}".`)}function e$(e,t){return eG(e[t])}function eF(e,t){var r;let n;return r=eG,null===(n=e[t])?null:r(n)}function ej(e,t){return eC(e,t,ez,`Daemon response has invalid "${t}".`)}function eq(e,t){return function(e){return"tv"===e||"mobile"===e||"desktop"===e?e:void 0}(e[t])??"mobile"}function eB(e,t){let r=e[t];if(!eW(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 eG(e){return"string"==typeof e&&e.length>0?e:void 0}function eV(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function ez(e){return"ios"===e||"macos"===e||"android"===e?e:void 0}function eH(e){return"simulator"===e||"emulator"===e||"device"===e?e:void 0}function eJ(e){if(!eW(e))throw new w("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function eW(e){return"object"==typeof e&&null!==e}function eK(e){let t={};for(let[r,n]of Object.entries(e))void 0!==n&&(t[r]=n);return t}function eZ(e,t){let r=e$(e,"bundleId"),n=e$(e,"package");return{app:eL(e,"app"),appPath:eL(e,"appPath"),platform:ej(e,"platform"),appId:r??n,bundleId:r,package:n,identifiers:eU({session:t,bundleId:r,packageName:n})}}function eX(e){let t=eJ(e),r=ej(t,"platform"),n=eL(t,"id"),o=eL(t,"name");return{platform:r,target:eq(t,"target"),kind:eC(t,"kind",eH,'Daemon response has invalid "kind".'),id:n,name:o,booted:"boolean"==typeof t.booted?t.booted:void 0,identifiers:eO(r,n,o),ios:"ios"===r?{udid:n}:void 0,android:"android"===r?{serial:n}:void 0}}function eY(e){let t=eJ(e),r=ej(t,"platform"),n=eL(t,"id"),o=eL(t,"name"),a=eq(t,"target"),i=eL(t,"device"),s={session:o,...eO(r,n,i)};return{name:o,createdAt:eC(t,"createdAt",eV,'Daemon response is missing numeric "createdAt".'),device:{platform:r,target:a,id:n,name:i,identifiers:s,ios:"ios"===r?{udid:n,simulatorSetPath:eF(t,"ios_simulator_device_set")}:void 0,android:"android"===r?{serial:n}:void 0},identifiers:s}}function eQ(e,t){return t??e??"default"}function e0(e={},t={}){let r=t.transport??V,n=async(t,n=[],o={})=>{let a={...e,...o},i=await r({session:eQ(e.session,o.session),command:t,positionals:n,flags:eK({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:eK({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(eY)};return{devices:{list:async(e={})=>{let t=await n("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(eX)}},sessions:{list:async(e={})=>await o(e),close:async(t={})=>{let r=eQ(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=eL(o,"udid"),i=eL(o,"device");return{udid:a,device:i,runtime:eL(o,"runtime"),created:!0===o.created,booted:!0===o.booted,iosSimulatorDeviceSet:eF(o,"ios_simulator_device_set"),identifiers:{deviceId:a,deviceName:i,udid:a}}}},apps:{install:async t=>eZ(await n("install",[t.app,t.appPath],t),eQ(e.session,t.session)),reinstall:async t=>eZ(await n("reinstall",[t.app,t.appPath],t),eQ(e.session,t.session)),installFromSource:async t=>(function(e,t){let r=e$(e,"bundleId"),n=e$(e,"packageName"),o=r??n??e$(e,"appId"),a=e$(e,"launchTarget")??n??r??o;if(!a)throw new w("COMMAND_FAILED",'Daemon response is missing "launchTarget".',{response:e});return{appName:e$(e,"appName"),appId:o,bundleId:r,packageName:n,launchTarget:a,installablePath:e$(e,"installablePath"),archivePath:e$(e,"archivePath"),materializationId:e$(e,"materializationId"),materializationExpiresAt:e$(e,"materializationExpiresAt"),identifiers:eU({session:t,bundleId:r,packageName:n,appId:o})}})(await n("install_source",[],{...t,installSource:t.source,retainMaterializedPaths:t.retainPaths,materializedPathRetentionMs:t.retentionMs}),eQ(e.session,t.session)),open:async t=>{let r=eQ(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=e$(e,"id"),n=e$(e,"device");if("ios"!==t&&"macos"!==t&&"android"!==t||!r||!n)return;let o=eq(e,"target"),a=eO(t,r,n);return{platform:t,target:o,id:r,name:n,identifiers:a,ios:"ios"===t?{udid:e$(e,"device_udid")??r,simulatorSetPath:eF(e,"ios_simulator_device_set")}:void 0,android:"android"===t?{serial:e$(e,"serial")??r}:void 0}}(a),s=e$(a,"appBundleId");return{session:r,appName:e$(a,"appName"),appBundleId:s,appId:s,startup:function(e){if(eW(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:e$(e,"appTarget"),appBundleId:e$(e,"appBundleId")}}(a.startup),runtime:function(e){if(!eW(e))return;let t=e.platform,r=e$(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:e$(e,"bundleUrl"),launchUrl:e$(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=eQ(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:eL(t,"materializationId"),identifiers:{}}}},metro:{prepare:async t=>await ex({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=eQ(e.session,t.session),a=await n("snapshot",[],t),i=e$(a,"appBundleId"),s="object"==typeof a.visibility&&null!==a.visibility?a.visibility:void 0;return{nodes:Array.isArray(r=a.nodes)?r:[],truncated:!0===a.truncated,appName:e$(a,"appName"),appBundleId:i,...s?{visibility:s}:{},warnings:Array.isArray(a.warnings)?a.warnings.filter(e=>"string"==typeof e):void 0,identifiers:{session:o,appId:i,appBundleId:i}}},screenshot:async(t={})=>{let r=eQ(e.session,t.session),o=await n("screenshot",t.path?[t.path]:[],t);return{path:eL(o,"path"),overlayRefs:function(e){let t=e.overlayRefs;if(!Array.isArray(t))return;let r=[];for(let e of t){if(!eW(e))continue;let t=e$(e,"ref"),n=eB(e,"rect"),o=eB(e,"overlayRect"),a=function(e,t){let r=e[t];if(!eW(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:e$(e,"label"),rect:n,overlayRect:o,center:a})}return r}(o),identifiers:{session:r}}}}}}export{e0 as createAgentDeviceClient,w as AppError};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-device",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.9",
|
|
4
4
|
"description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Callstack",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"test:replay:ios": "node --experimental-strip-types src/bin.ts test test/integration/replays/ios/simulator",
|
|
46
46
|
"test:replay:ios-device": "node --experimental-strip-types src/bin.ts test test/integration/replays/ios/device",
|
|
47
47
|
"test:replay:android": "node --experimental-strip-types src/bin.ts test test/integration/replays/android",
|
|
48
|
-
"test:replay:macos": "node --experimental-strip-types src/bin.ts test test/integration/replays/macos"
|
|
48
|
+
"test:replay:macos": "node --experimental-strip-types src/bin.ts test test/integration/replays/macos",
|
|
49
|
+
"test:replay:linux": "node --experimental-strip-types src/bin.ts test test/integration/replays/linux"
|
|
49
50
|
},
|
|
50
51
|
"files": [
|
|
51
52
|
"bin",
|
|
@@ -57,10 +58,16 @@
|
|
|
57
58
|
"!ios-runner/**/*.xcuserstate",
|
|
58
59
|
"macos-helper",
|
|
59
60
|
"!macos-helper/**/.build",
|
|
61
|
+
"src/platforms/linux/atspi-dump.py",
|
|
60
62
|
"skills",
|
|
61
63
|
"README.md",
|
|
62
64
|
"LICENSE"
|
|
63
65
|
],
|
|
66
|
+
"pnpm": {
|
|
67
|
+
"overrides": {
|
|
68
|
+
"lodash-es": "4.18.1"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
64
71
|
"keywords": [
|
|
65
72
|
"agent",
|
|
66
73
|
"device",
|
|
@@ -72,16 +79,18 @@
|
|
|
72
79
|
"android"
|
|
73
80
|
],
|
|
74
81
|
"dependencies": {
|
|
82
|
+
"fast-xml-parser": "^5.5.10",
|
|
75
83
|
"pngjs": "^7.0.0"
|
|
76
84
|
},
|
|
77
85
|
"devDependencies": {
|
|
78
|
-
"@microsoft/api-extractor": "^7.
|
|
86
|
+
"@microsoft/api-extractor": "^7.58.1",
|
|
79
87
|
"@rslib/core": "0.20.1",
|
|
80
88
|
"@types/node": "^22.0.0",
|
|
81
89
|
"@types/pngjs": "^6.0.5",
|
|
82
90
|
"oxfmt": "^0.42.0",
|
|
83
91
|
"oxlint": "^1.57.0",
|
|
84
92
|
"typescript": "^6.0.2",
|
|
93
|
+
"vite": "^8.0.7",
|
|
85
94
|
"vitest": "^4.1.2"
|
|
86
95
|
}
|
|
87
96
|
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AT-SPI2 accessibility tree dumper.
|
|
4
|
+
|
|
5
|
+
Traverses the AT-SPI2 accessibility tree and outputs JSON to stdout.
|
|
6
|
+
Used by agent-device's Linux platform support as a subprocess.
|
|
7
|
+
|
|
8
|
+
Requires: python3-gi, gir1.2-atspi-2.0
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import gi
|
|
15
|
+
gi.require_version("Atspi", "2.0")
|
|
16
|
+
from gi.repository import Atspi # noqa: E402
|
|
17
|
+
|
|
18
|
+
MAX_NODES = 1500
|
|
19
|
+
MAX_DEPTH = 12
|
|
20
|
+
MAX_DESKTOP_APPS = 24
|
|
21
|
+
|
|
22
|
+
VALID_SURFACES = ("desktop", "frontmost-app")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_rect(accessible):
|
|
26
|
+
try:
|
|
27
|
+
component = accessible.get_component_iface()
|
|
28
|
+
if not component:
|
|
29
|
+
return None
|
|
30
|
+
extents = component.get_extents(Atspi.CoordType.SCREEN)
|
|
31
|
+
if not extents:
|
|
32
|
+
return None
|
|
33
|
+
if extents.width <= 0 or extents.height <= 0:
|
|
34
|
+
return None
|
|
35
|
+
return {
|
|
36
|
+
"x": extents.x,
|
|
37
|
+
"y": extents.y,
|
|
38
|
+
"width": extents.width,
|
|
39
|
+
"height": extents.height,
|
|
40
|
+
}
|
|
41
|
+
except Exception:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_text_value(accessible):
|
|
46
|
+
try:
|
|
47
|
+
text_iface = accessible.get_text_iface()
|
|
48
|
+
if not text_iface:
|
|
49
|
+
return None
|
|
50
|
+
count = text_iface.get_character_count()
|
|
51
|
+
if count <= 0:
|
|
52
|
+
return None
|
|
53
|
+
value = text_iface.get_text(0, count)
|
|
54
|
+
return value if value else None
|
|
55
|
+
except Exception:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_numeric_value(accessible):
|
|
60
|
+
try:
|
|
61
|
+
value_iface = accessible.get_value_iface()
|
|
62
|
+
if not value_iface:
|
|
63
|
+
return None
|
|
64
|
+
current = value_iface.get_current_value()
|
|
65
|
+
if current is None:
|
|
66
|
+
return None
|
|
67
|
+
return str(current)
|
|
68
|
+
except Exception:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def has_state(state_set, state_type):
|
|
73
|
+
try:
|
|
74
|
+
return state_set.contains(state_type)
|
|
75
|
+
except Exception:
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def traverse_node(accessible, depth, parent_index, ctx, app_info, window_title=None):
|
|
80
|
+
if len(ctx["nodes"]) >= ctx["max_nodes"] or depth > ctx["max_depth"] or not accessible:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
role_name = accessible.get_role_name() or "unknown"
|
|
85
|
+
except Exception:
|
|
86
|
+
role_name = "unknown"
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
name = accessible.get_name() or ""
|
|
90
|
+
except Exception:
|
|
91
|
+
name = ""
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
description = accessible.get_description() or ""
|
|
95
|
+
except Exception:
|
|
96
|
+
description = ""
|
|
97
|
+
|
|
98
|
+
label = name or description or None
|
|
99
|
+
rect = get_rect(accessible)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
state_set = accessible.get_state_set()
|
|
103
|
+
except Exception:
|
|
104
|
+
state_set = None
|
|
105
|
+
|
|
106
|
+
enabled = has_state(state_set, Atspi.StateType.ENABLED) if state_set else None
|
|
107
|
+
selected = has_state(state_set, Atspi.StateType.SELECTED) if state_set else None
|
|
108
|
+
visible = has_state(state_set, Atspi.StateType.VISIBLE) if state_set else True
|
|
109
|
+
showing = has_state(state_set, Atspi.StateType.SHOWING) if state_set else True
|
|
110
|
+
hittable = (enabled is not False) and visible and showing and (rect is not None)
|
|
111
|
+
|
|
112
|
+
current_window_title = window_title
|
|
113
|
+
if current_window_title is None and role_name in ("frame", "window", "dialog"):
|
|
114
|
+
current_window_title = label
|
|
115
|
+
|
|
116
|
+
nodes = ctx["nodes"]
|
|
117
|
+
node_index = len(nodes)
|
|
118
|
+
value = get_text_value(accessible) or get_numeric_value(accessible)
|
|
119
|
+
|
|
120
|
+
node = {
|
|
121
|
+
"index": node_index,
|
|
122
|
+
"role": role_name,
|
|
123
|
+
"label": label,
|
|
124
|
+
"value": value,
|
|
125
|
+
"rect": rect,
|
|
126
|
+
"enabled": enabled,
|
|
127
|
+
"selected": selected,
|
|
128
|
+
"hittable": hittable,
|
|
129
|
+
"depth": depth,
|
|
130
|
+
"parentIndex": parent_index,
|
|
131
|
+
"pid": app_info.get("pid"),
|
|
132
|
+
"appName": app_info.get("appName"),
|
|
133
|
+
"windowTitle": current_window_title,
|
|
134
|
+
}
|
|
135
|
+
nodes.append(node)
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
child_count = accessible.get_child_count()
|
|
139
|
+
except Exception:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
for i in range(child_count):
|
|
143
|
+
if len(nodes) >= ctx["max_nodes"]:
|
|
144
|
+
break
|
|
145
|
+
try:
|
|
146
|
+
child = accessible.get_child_at_index(i)
|
|
147
|
+
if child:
|
|
148
|
+
traverse_node(
|
|
149
|
+
child, depth + 1, node_index, ctx, app_info,
|
|
150
|
+
current_window_title
|
|
151
|
+
)
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def find_focused_application(desktop, app_count):
|
|
157
|
+
for i in range(app_count):
|
|
158
|
+
try:
|
|
159
|
+
app = desktop.get_child_at_index(i)
|
|
160
|
+
if not app:
|
|
161
|
+
continue
|
|
162
|
+
child_count = app.get_child_count()
|
|
163
|
+
for j in range(child_count):
|
|
164
|
+
try:
|
|
165
|
+
win = app.get_child_at_index(j)
|
|
166
|
+
if not win:
|
|
167
|
+
continue
|
|
168
|
+
state_set = win.get_state_set()
|
|
169
|
+
if state_set and has_state(state_set, Atspi.StateType.ACTIVE):
|
|
170
|
+
return app
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
except Exception:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
# Fallback: first app with children
|
|
177
|
+
for i in range(app_count):
|
|
178
|
+
try:
|
|
179
|
+
app = desktop.get_child_at_index(i)
|
|
180
|
+
if app and app.get_child_count() > 0:
|
|
181
|
+
return app
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_app_info(app):
|
|
188
|
+
try:
|
|
189
|
+
app_name = app.get_name() or None
|
|
190
|
+
except Exception:
|
|
191
|
+
app_name = None
|
|
192
|
+
try:
|
|
193
|
+
pid = app.get_process_id()
|
|
194
|
+
except Exception:
|
|
195
|
+
pid = None
|
|
196
|
+
return {"appName": app_name, "pid": pid}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def capture(surface, max_nodes=MAX_NODES, max_depth=MAX_DEPTH, max_apps=MAX_DESKTOP_APPS):
|
|
200
|
+
desktop = Atspi.get_desktop(0)
|
|
201
|
+
if not desktop:
|
|
202
|
+
return {"error": "Could not get desktop accessible. Is the accessibility bus running?"}
|
|
203
|
+
|
|
204
|
+
app_count = desktop.get_child_count()
|
|
205
|
+
ctx = {"nodes": [], "max_nodes": max_nodes, "max_depth": max_depth}
|
|
206
|
+
|
|
207
|
+
if surface == "frontmost-app":
|
|
208
|
+
focused = find_focused_application(desktop, app_count)
|
|
209
|
+
if focused:
|
|
210
|
+
traverse_node(focused, 0, None, ctx, get_app_info(focused))
|
|
211
|
+
else:
|
|
212
|
+
apps_to_traverse = min(app_count, max_apps)
|
|
213
|
+
for i in range(apps_to_traverse):
|
|
214
|
+
if len(ctx["nodes"]) >= max_nodes:
|
|
215
|
+
break
|
|
216
|
+
try:
|
|
217
|
+
app = desktop.get_child_at_index(i)
|
|
218
|
+
if not app or app.get_child_count() == 0:
|
|
219
|
+
continue
|
|
220
|
+
traverse_node(app, 0, None, ctx, get_app_info(app))
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
nodes = ctx["nodes"]
|
|
225
|
+
return {
|
|
226
|
+
"nodes": nodes,
|
|
227
|
+
"truncated": len(nodes) >= max_nodes,
|
|
228
|
+
"surface": surface,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def parse_int_arg(value, name):
|
|
233
|
+
try:
|
|
234
|
+
n = int(value)
|
|
235
|
+
if n < 0:
|
|
236
|
+
raise ValueError(f"negative value")
|
|
237
|
+
return n
|
|
238
|
+
except ValueError as e:
|
|
239
|
+
json.dump({"error": f"Invalid value for {name}: '{value}' ({e})"}, sys.stdout)
|
|
240
|
+
sys.exit(1)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def main():
|
|
244
|
+
try:
|
|
245
|
+
surface = "desktop"
|
|
246
|
+
max_nodes = MAX_NODES
|
|
247
|
+
max_depth = MAX_DEPTH
|
|
248
|
+
max_apps = MAX_DESKTOP_APPS
|
|
249
|
+
|
|
250
|
+
args = sys.argv[1:]
|
|
251
|
+
i = 0
|
|
252
|
+
while i < len(args):
|
|
253
|
+
if args[i] == "--surface" and i + 1 < len(args):
|
|
254
|
+
surface = args[i + 1]
|
|
255
|
+
i += 2
|
|
256
|
+
elif args[i] == "--max-nodes" and i + 1 < len(args):
|
|
257
|
+
max_nodes = parse_int_arg(args[i + 1], "--max-nodes")
|
|
258
|
+
i += 2
|
|
259
|
+
elif args[i] == "--max-depth" and i + 1 < len(args):
|
|
260
|
+
max_depth = parse_int_arg(args[i + 1], "--max-depth")
|
|
261
|
+
i += 2
|
|
262
|
+
elif args[i] == "--max-apps" and i + 1 < len(args):
|
|
263
|
+
max_apps = parse_int_arg(args[i + 1], "--max-apps")
|
|
264
|
+
i += 2
|
|
265
|
+
else:
|
|
266
|
+
i += 1
|
|
267
|
+
|
|
268
|
+
if surface not in VALID_SURFACES:
|
|
269
|
+
json.dump(
|
|
270
|
+
{"error": f"Unknown surface '{surface}'. Valid: {', '.join(VALID_SURFACES)}"},
|
|
271
|
+
sys.stdout,
|
|
272
|
+
)
|
|
273
|
+
sys.exit(1)
|
|
274
|
+
|
|
275
|
+
result = capture(surface, max_nodes, max_depth, max_apps)
|
|
276
|
+
json.dump(result, sys.stdout, ensure_ascii=False)
|
|
277
|
+
except SystemExit:
|
|
278
|
+
raise
|
|
279
|
+
except Exception as e:
|
|
280
|
+
json.dump({"error": f"Unexpected error: {e}"}, sys.stdout)
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
main()
|