agent-device 0.12.2 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import e from"node:net";import t from"node:http";import a from"node:https";import r from"node:fs";import o from"node:path";import{fileURLToPath as n}from"node:url";import{AsyncLocalStorage as i}from"node:async_hooks";import s,{createHash as l}from"node:crypto";import"node:os";import{spawn as d}from"node:child_process";import{redactDiagnosticData as c,AppError as u}from"./152.js";import{resolveUserPath as p,expandUserHomePath as m}from"./267.js";import{runCmdDetached as f,runCmdSync as h}from"./818.js";import{prepareMetroRuntime as y,isAgentDeviceDaemonProcess as w,stopProcessForTakeover as v}from"./974.js";import{tryParseSelectorChain as g}from"./940.js";import{REMOTE_CONFIG_FIELD_SPECS as I,resolveRemoteConfigProfile as A,REMOTE_OPEN_PROFILE_KEYS as b}from"./924.js";function S(){let e=o.dirname(n(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=o.join(t,"package.json");if(r.existsSync(e))return t;t=o.dirname(t)}return e}let _=new i;function M(){return s.randomBytes(8).toString("hex")}function P(e){let t=_.getStore();if(!t)return;let a={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?c(e.data):void 0};if(t.events.push(a),!t.debug)return;let o=`[agent-device][diag] ${JSON.stringify(a)}
2
- `;try{t.logPath&&r.appendFile(t.logPath,o,()=>{}),t.traceLogPath&&r.appendFile(t.traceLogPath,o,()=>{}),t.logPath||t.traceLogPath||process.stderr.write(o)}catch{}}async function D(e,t,a){let r=Date.now();try{let o=await t();return P({level:"info",phase:e,durationMs:Date.now()-r,data:a}),o}catch(t){throw P({level:"error",phase:e,durationMs:Date.now()-r,data:{...a??{},error:t instanceof Error?t.message:String(t)}}),t}}function E(e){let t,a=(t=(e??"").trim())?p(t):o.join(m("~"),".agent-device");return{baseDir:a,infoPath:o.join(a,"daemon.json"),lockPath:o.join(a,"daemon.lock"),logPath:o.join(a,"daemon.log"),sessionsDir:o.join(a,"sessions")}}let k="sha256";async function T(e){let{localPath:n,baseUrl:i,token:s}=e,l=r.statSync(n),c=l.isDirectory(),p=o.basename(n),m=c?"app-bundle":"file",f=i.endsWith("/")?i:`${i}/`,h=c?void 0:await x(n);if(h){let e=await N({normalizedBase:f,token:s,hash:h,filename:p,sizeBytes:l.size,artifactType:m});if(e)return e}let y=new URL("upload",f),w="https:"===y.protocol?a:t,v={"x-artifact-type":m,"x-artifact-filename":p,"transfer-encoding":"chunked"};return h&&(v["x-artifact-hash"]=h,v["x-artifact-hash-algorithm"]=k),s&&(v.authorization=`Bearer ${s}`,v["x-agent-device-token"]=s),new Promise((e,t)=>{let a=w.request({protocol:y.protocol,host:y.hostname,port:y.port,method:"POST",path:y.pathname+y.search,headers:v},a=>{let r="";a.setEncoding("utf8"),a.on("data",e=>{r+=e}),a.on("end",()=>{clearTimeout(i);try{let a=JSON.parse(r);if(!a.ok||!a.uploadId)return void t(new u("COMMAND_FAILED",`Upload failed: ${r}`));e(a.uploadId)}catch{t(new u("COMMAND_FAILED",`Invalid upload response: ${r}`))}})}),i=setTimeout(()=>{a.destroy(),t(new u("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(a.on("error",e=>{clearTimeout(i),t(new u("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),c){let e=d("tar",["cf","-","-C",o.dirname(n),o.basename(n)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(a),e.on("error",e=>{a.destroy(),t(new u("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(a.destroy(),t(new u("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=r.createReadStream(n);e.pipe(a),e.on("error",e=>{a.destroy(),t(new u("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}async function N(e){var t;let a=new URL("upload/preflight",e.normalizedBase),r={"content-type":"application/json"};e.token&&(r.authorization=`Bearer ${e.token}`,r["x-agent-device-token"]=e.token);let o=await fetch(a,{method:"POST",headers:r,signal:AbortSignal.timeout(3e4),body:JSON.stringify({hash:e.hash,hashAlgorithm:k,fileName:e.filename,sizeBytes:e.sizeBytes,artifactType:e.artifactType})}).catch(()=>void 0);if(!o?.ok)return;let n=await o.json().catch(()=>void 0);return(t=n)&&"object"==typeof t&&!0===t.ok&&!0===t.cacheHit&&"string"==typeof t.uploadId?n.uploadId:void 0}async function x(e){let t=l(k);return await new Promise((a,o)=>{r.createReadStream(e).on("data",e=>t.update(e)).on("error",e=>{o(new u("COMMAND_FAILED","Failed to read local artifact",{},e))}).on("end",a)}),t.digest("hex")}let C=/(?:^|[^\w$.])(?:import|export)\s+(?:type\s+)?(?:[^'"`]*?\s+from\s+)?['"]([^'"]+)['"]/gm,U=/import\(\s*['"]([^'"]+)['"]\s*\)/gm,R=[".ts",".tsx",".js",".jsx",".mjs",".cjs"];function O(e,t,a){t.lastIndex=0;let r=null;for(;null!==(r=t.exec(e));){let e=r[1]?.trim();e?.startsWith(".")&&a.add(e)}}function L(e){try{return r.statSync(e).isFile()?e:null}catch{return null}}let F=ev(),$=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}(),j=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}(),q=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"],B=new e.BlockList;async function z(t){let a=t.meta?.requestId??M(),r=!!(t.meta?.debug||t.flags?.verbose),o=function(t){let a,r,o,n=t.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,i=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new u("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new u("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(t.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),s=t.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN;var l=i,d=s;if(!(!l||"localhost"===(a=new URL(l).hostname.trim().toLowerCase().replace(/^\[(.*)\]$/,"$1"))||(e.isIPv4(a)?B.check(a,"ipv4"):!!e.isIPv6(a)&&B.check(a,"ipv6")))&&("string"!=typeof d||!(d.trim().length>0)))throw new u("INVALID_ARGS","Remote daemon base URL for non-loopback hosts requires daemon authentication",{daemonBaseUrl:l,hint:"Provide --daemon-auth-token or AGENT_DEVICE_DAEMON_AUTH_TOKEN when using a non-loopback remote daemon URL."});let c=t.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,p="auto"===(r=(c??"").trim().toLowerCase())?"auto":"socket"===r?"socket":"http"===r?"http":"auto";if(i&&"socket"===p)throw new u("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:i});let m="http"===(o=(t.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===c?"dual":void 0)??"").trim().toLowerCase())?"http":"dual"===o?"dual":"socket";return{paths:E(n),transportPreference:p,serverMode:m,remoteBaseUrl:i,remoteAuthToken:s}}(t),n=function(e,t=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if("test"!==e)return ev(t)}(t.command),i=await D("daemon_startup",async()=>await J(o),{requestId:a,session:t.session}),s=await G(t,i),l={...t,positionals:s.positionals,flags:s.flags,token:i.token,meta:{...t.meta??{},requestId:a,debug:r,cwd:t.meta?.cwd,tenantId:t.meta?.tenantId??t.flags?.tenant,runId:t.meta?.runId??t.flags?.runId,leaseId:t.meta?.leaseId??t.flags?.leaseId,sessionIsolation:t.meta?.sessionIsolation??t.flags?.sessionIsolation,lockPolicy:t.meta?.lockPolicy,lockPlatform:t.meta?.lockPlatform,...s.uploadedArtifactId?{uploadedArtifactId:s.uploadedArtifactId}:{},...s.clientArtifactPaths?{clientArtifactPaths:s.clientArtifactPaths}:{},...s.installSource?{installSource:s.installSource}:{}}};return P({level:"info",phase:"daemon_request_prepare",data:{requestId:a,command:t.command,session:t.session}}),await D("daemon_request",async()=>await es(i,l,o.transportPreference,n),{requestId:a,command:t.command})}async function G(e,t){let a,n=[...e.positionals??[]],i=e.flags?{...e.flags}:void 0,s=e.meta?.installSource,l={};if(ef(t)){let r=function(e,t){if("screenshot"===e.command){let a=V(e,"path",".png");return t[0]?{field:"path",localPath:a,positionalIndex:0,positionalPath:H("screenshot",".png")}:{field:"path",localPath:a,positionalIndex:0,flagPath:H("screenshot",".png")}}if("record"===e.command&&"start"===(t[0]??"").toLowerCase()){let t=V(e,"outPath",".mp4",1);return{field:"outPath",localPath:t,positionalIndex:1,positionalPath:H("recording",o.extname(t)||".mp4")}}return null}(e,n);r&&(void 0!==r.positionalPath&&(n[r.positionalIndex]=r.positionalPath),void 0!==r.flagPath&&((i??={}).out=r.flagPath),l[r.field]=r.localPath);let d=await K(e,t);d&&(s=d.installSource,a=d.uploadedArtifactId??a)}if(!ef(t)||"install"!==e.command&&"reinstall"!==e.command||n.length<2)return{positionals:n,flags:i,installSource:s,uploadedArtifactId:a,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let d=n[1];if(d.startsWith("remote:"))return n[1]=d.slice(7),{positionals:n,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let c=o.isAbsolute(d)?d:o.resolve(e.meta?.cwd??process.cwd(),d);return r.existsSync(c)?{positionals:n,flags:i,installSource:s,uploadedArtifactId:a=await T({localPath:c,baseUrl:t.baseUrl,token:t.token}),...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}:{positionals:n,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}}async function K(e,t){let a=e.meta?.installSource;if("install_source"!==e.command||!a||"path"!==a.kind)return null;let n=a.path.trim();if(!n)return{installSource:a};if(n.startsWith("remote:"))return{installSource:{...a,path:n.slice(7)}};let i=o.isAbsolute(n)?n:o.resolve(e.meta?.cwd??process.cwd(),n);if(!r.existsSync(i))return{installSource:{...a,path:i}};let s=await T({localPath:i,baseUrl:t.baseUrl,token:t.token});return{installSource:{...a,path:i},uploadedArtifactId:s}}function V(e,t,a,r=0){let n=e.positionals?.[r]??e.flags?.out,i=`${"path"===t?"screenshot":"recording"}-${Date.now()}${a}`,s=n&&n.trim().length>0?n:i;return o.isAbsolute(s)?s:o.resolve(e.meta?.cwd??process.cwd(),s)}function H(e,t){let a=t.startsWith(".")?t:`.${t}`;return o.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${a}`)}async function J(e){let t;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await eo(t,"http"))return t;throw new u("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 a=Z(e.paths.infoPath),n=function(){try{let e=S();return JSON.parse(r.readFileSync(o.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),i=function(e,t=S()){try{let a=o.resolve(t),n=[o.resolve(e)],i=new Set,l=[];for(;n.length>0;){let e=n.pop();if(!e||i.has(e))continue;i.add(e);let t=r.statSync(e);if(!t.isFile())continue;let s=o.relative(a,e)||e;l.push(`${s}:${t.size}:${Math.trunc(t.mtimeMs)}`);let d=r.readFileSync(e,"utf8");for(let t of function(e){let t=new Set;return O(e,C,t),O(e,U,t),[...t]}(d)){let a=function(e,t){let a=o.resolve(o.dirname(e),t),r=L(a);if(r)return r;for(let e of R){let t=L(`${a}${e}`);if(t)return t}for(let e of R){let t=L(o.join(a,`index${e}`));if(t)return t}return null}(e,t);a&&n.push(a)}}let d=l.sort().join("|"),c=s.createHash("sha1").update(d).digest("hex");return`graph:${l.length}:${c}`}catch{return"unknown"}}((t=ei()).useSrc?t.srcPath:t.distPath,t.root),l=!!a&&await eo(a,e.transportPreference);if(a&&a.version===n&&a.codeSignature===i&&l)return a;a&&(a.version!==n||a.codeSignature!==i||!l)&&(await Y(a),er(e.paths.infoPath)),function(e){let t=et(e);if(!t.hasLock||t.hasInfo)return;let a=ee(e.lockPath);if(!a)return er(e.lockPath);w(a.pid,a.processStartTime)||er(e.lockPath)}(e.paths);let d=0;for(let t=1;t<=j;t+=1){await en(e);let a=await W($,e);if(a)return a;if(await X(e.paths)){d+=1;continue}let r=et(e.paths);if(!(t<j))break;if(!r.hasInfo&&!r.hasLock){await Q(150);continue}}let c=et(e.paths);throw new u("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:$,startupAttempts:j,lockRecoveryCount:d,metadataState:c,hint:function(e,t=E(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.`}(c,e.paths)})}async function W(e,t){let a=Date.now();for(;Date.now()-a<e;){let e=Z(t.paths.infoPath);if(e&&await eo(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function Q(e){await new Promise(t=>setTimeout(t,e))}async function X(e){let t=et(e);if(!t.hasLock||t.hasInfo)return!1;let a=ee(e.lockPath);return a&&w(a.pid,a.processStartTime)&&await v(a.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:a.processStartTime}),er(e.lockPath),!0}async function Y(e){await v(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function Z(e){let t=ea(e);if(!t||"object"!=typeof t)return null;let a="string"==typeof t.token&&t.token.length>0?t.token:null;if(!a)return null;let r=Number.isInteger(t.port)&&Number(t.port)>0,o=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;if(!r&&!o)return null;let n=t.transport,i="string"==typeof t.version?t.version:void 0,s="string"==typeof t.codeSignature?t.codeSignature:void 0,l="string"==typeof t.processStartTime?t.processStartTime:void 0,d=Number.isInteger(t.pid)&&Number(t.pid)>0;return{token:a,port:r?Number(t.port):void 0,httpPort:o?Number(t.httpPort):void 0,transport:"socket"===n||"http"===n||"dual"===n?n:void 0,pid:d?Number(t.pid):0,version:i,codeSignature:s,processStartTime:l}}function ee(e){let t=ea(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}B.addSubnet("127.0.0.0",8,"ipv4"),B.addAddress("::1","ipv6"),B.addSubnet("::ffff:127.0.0.0",104,"ipv6");function et(e){return{hasInfo:r.existsSync(e.infoPath),hasLock:r.existsSync(e.lockPath)}}function ea(e){if(!r.existsSync(e))return null;try{return JSON.parse(r.readFileSync(e,"utf8"))}catch{return null}}function er(e){try{r.existsSync(e)&&r.unlinkSync(e)}catch{}}async function eo(r,o){var n;return"http"===el(r,o)?await function(e){let r=e.baseUrl?eh(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!r)return Promise.resolve(!1);let o=new URL(r),n="https:"===o.protocol?a:t,i=e.baseUrl?3e3:500;return new Promise(e=>{let t=n.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()})}(r):await ((n=r.port)?new Promise(t=>{let a=e.createConnection({host:"127.0.0.1",port:n},()=>{a.destroy(),t(!0)});a.on("error",()=>{t(!1)})}):Promise.resolve(!1))}async function en(e){let t=ei(),a=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],r={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};f(process.execPath,a,{env:r})}function ei(){let e=S(),t=o.join(e,"dist","src","daemon.js"),a=o.join(e,"src","daemon.ts"),n=r.existsSync(t),i=r.existsSync(a);if(!n&&!i)throw new u("COMMAND_FAILED","Daemon entry not found",{distPath:t,srcPath:a});return{root:e,distPath:t,srcPath:a,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!n&&i}}async function es(e,t,a,r){return"http"===el(e,a)?await em(e,t,r):await ep(e,t,r)}function el(e,t){if(e.baseUrl){if("socket"===t)throw new u("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t||"socket"===t){var a=e,r=t;if(ed(a,r))return r;throw new u("COMMAND_FAILED","http"===r?"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 u("COMMAND_FAILED","Daemon metadata has no reachable transport")}function ed(e,t){return"http"===t?!!e.httpPort:!!e.port}function ec(e,t,a,r,o,n){let i=o?{terminated:0}:function(){let e=0;try{for(let t of q){let a=h("pkill",["-f",t],{allowFailure:!0});0===a.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 a=!1;try{w(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),a=!0)}catch{v(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{er(t.infoPath),er(t.lockPath)}return{forcedKill:a}}(e,t);return P({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:n,requestId:a,command:r,timedOutRunnerPidsTerminated:i.terminated,timedOutRunnerCleanupError:i.error,daemonPidReset:o?void 0:e.pid,daemonPidForceKilled:o?void 0:s.forcedKill,daemonBaseUrl:e.baseUrl}}),new u("COMMAND_FAILED","Daemon request timed out",{timeoutMs:n,requestId:a,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 eu(e,t,a){return P({level:"error",phase:"daemon_request_socket_error",data:{requestId:t,message:e instanceof Error?e.message:String(e)}}),new u("COMMAND_FAILED","Failed to communicate with daemon",{requestId:t,hint:a?"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 ep(t,a,r){let o=t.port;if(!o)throw new u("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((n,i)=>{let s=e.createConnection({host:"127.0.0.1",port:o},()=>{s.write(`${JSON.stringify(a)}
3
- `)}),l=E(a.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),d="number"==typeof r?setTimeout(()=>{s.destroy(),i(ec(t,l,a.meta?.requestId,a.command,!1,r))},r):void 0,c="";s.setEncoding("utf8"),s.on("data",e=>{let t=(c+=e).indexOf("\n");if(-1===t)return;let r=c.slice(0,t).trim();if(r)try{let e=JSON.parse(r);s.end(),d&&clearTimeout(d),n(e)}catch(e){d&&clearTimeout(d),i(new u("COMMAND_FAILED","Invalid daemon response",{requestId:a.meta?.requestId,line:r},e instanceof Error?e:void 0))}}),s.on("error",e=>{d&&clearTimeout(d),i(eu(e,a.meta?.requestId,!1))})})}async function em(e,r,o){let n=e.baseUrl?new URL(eh(e.baseUrl,"rpc")):e.httpPort?new URL(`http://127.0.0.1:${e.httpPort}/rpc`):null;if(!n)throw new u("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let i=JSON.stringify({jsonrpc:"2.0",id:r.meta?.requestId??M(),method:"agent_device.command",params:r}),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,d)=>{let c=E(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),p=("https:"===n.protocol?a:t).request({protocol:n.protocol,host:n.hostname,port:n.port,method:"POST",path:n.pathname+n.search,headers:s},t=>{let a="";t.setEncoding("utf8"),t.on("data",e=>{a+=e}),t.on("end",()=>{f&&clearTimeout(f);try{let t=JSON.parse(a);if(t.error){let e=t.error.data??{};d(new u(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:r.meta?.requestId}));return}if(!t.result||"object"!=typeof t.result)return void d(new u("COMMAND_FAILED","Invalid daemon RPC response",{requestId:r.meta?.requestId}));if(e.baseUrl&&t.result.ok)return void ey(e,r,t.result).then(l).catch(d);l(t.result)}catch(e){f&&clearTimeout(f),d(new u("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:a},e instanceof Error?e:void 0))}})}),m=ef(e),f="number"==typeof o?setTimeout(()=>{p.destroy(),d(ec(e,c,r.meta?.requestId,r.command,m,o))},o):void 0;p.on("error",e=>{f&&clearTimeout(f),d(eu(e,r.meta?.requestId,m))}),p.write(i),p.end()})}function ef(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function eh(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function ey(e,t,a){let r=Array.isArray(a.data?.artifacts)?a.data.artifacts:[];if(0===r.length||!e.baseUrl)return a;let n=a.data?{...a.data}:{},i=[];for(let a of r){if(!a||"object"!=typeof a||"string"!=typeof a.artifactId){i.push(a);continue}let r=function(e,t){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let a=e.fileName?.trim()||`${e.field}-${Date.now()}`;return o.resolve(t.meta?.cwd??process.cwd(),a)}(a,t);await ew({baseUrl:e.baseUrl,token:e.token,artifactId:a.artifactId,destinationPath:r,requestId:t.meta?.requestId}),n[a.field]=r,i.push({...a,localPath:r})}return n.artifacts=i,{ok:!0,data:n}}async function ew(e){var n,i;let s,l=new URL((n=e.baseUrl,i=e.artifactId,s=n.endsWith("/")?n:`${n}/`,new URL(`upload/${encodeURIComponent(i)}`,s).toString())),d="https:"===l.protocol?a:t;await r.promises.mkdir(o.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,a)=>{let o=!1,n=e.timeoutMs??F,i=n=>{if(!o){if(o=!0,clearTimeout(c),n)return void r.promises.rm(e.destinationPath,{force:!0}).finally(()=>a(n));t()}},s=d.request({protocol:l.protocol,host:l.hostname,port:l.port,method:"GET",path:l.pathname+l.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let a="";t.setEncoding("utf8"),t.on("data",e=>{a+=e}),t.on("end",()=>{i(new u("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:a}))});return}let a=r.createWriteStream(e.destinationPath);a.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 u("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),a.on("finish",()=>{a.close(()=>i())}),t.pipe(a)}),c=setTimeout(()=>{s.destroy(new u("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n}))},n);s.on("error",t=>{t instanceof u?i(t):i(new u("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n},t instanceof Error?t:void 0))}),s.end()})}function ev(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}let eg="clipboard",eI="wait",eA=I.map(e=>e.key);function eb(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function eS(e,t,a){return{deviceId:t,deviceName:a,..."android"===e?{serial:t}:"ios"===e?{udid:t}:{}}}function e_(e,t,a,r){let o=a(e[t]);if(void 0===o)throw new u("COMMAND_FAILED",r,{response:e});return o}[...b];function eM(e,t){return e_(e,t,eN,`Daemon response is missing "${t}".`)}function eP(e,t){return eN(e[t])}function eD(e,t){var a;let r;return a=eN,null===(r=e[t])?null:a(r)}function eE(e,t){return e_(e,t,eC,`Daemon response has invalid "${t}".`)}function ek(e,t){return function(e){return"tv"===e||"mobile"===e||"desktop"===e?e:void 0}(e[t])??"mobile"}function eT(e,t){let a=e[t];if(!eO(a))return;let r="number"==typeof a.x?a.x:void 0,o="number"==typeof a.y?a.y:void 0,n="number"==typeof a.width?a.width:void 0,i="number"==typeof a.height?a.height:void 0;if(void 0!==r&&void 0!==o&&void 0!==n&&void 0!==i)return{x:r,y:o,width:n,height:i}}function eN(e){return"string"==typeof e&&e.length>0?e:void 0}function ex(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function eC(e){return"ios"===e||"macos"===e||"android"===e?e:void 0}function eU(e){return"simulator"===e||"emulator"===e||"device"===e?e:void 0}function eR(e){if(!eO(e))throw new u("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function eO(e){return"object"==typeof e&&null!==e}function eL(e){let t={};for(let[a,r]of Object.entries(e))void 0!==r&&(t[a]=r);return t}function eF(e,t){let a=eP(e,"bundleId"),r=eP(e,"package");return{app:eM(e,"app"),appPath:eM(e,"appPath"),platform:eE(e,"platform"),appId:a??r,bundleId:a,package:r,identifiers:eb({session:t,bundleId:a,packageName:r})}}function e$(e){let t=eR(e),a=eE(t,"platform"),r=eM(t,"id"),o=eM(t,"name");return{platform:a,target:ek(t,"target"),kind:e_(t,"kind",eU,'Daemon response has invalid "kind".'),id:r,name:o,booted:"boolean"==typeof t.booted?t.booted:void 0,identifiers:eS(a,r,o),ios:"ios"===a?{udid:r}:void 0,android:"android"===a?{serial:r}:void 0}}function ej(e){let t=eR(e),a=eE(t,"platform"),r=eM(t,"id"),o=eM(t,"name"),n=ek(t,"target"),i=eM(t,"device"),s={session:o,...eS(a,r,i)};return{name:o,createdAt:e_(t,"createdAt",ex,'Daemon response is missing numeric "createdAt".'),device:{platform:a,target:n,id:r,name:i,identifiers:s,ios:"ios"===a?{udid:r,simulatorSetPath:eD(t,"ios_simulator_device_set")}:void 0,android:"android"===a?{serial:r}:void 0},identifiers:s}}function eq(e){return e??"default"}function eB(e={},t={}){var a;let r,o=t.transport??z,n=eH(e),i=async(t,a=[],r={})=>{let i=eV(e,n,r),s=await o({session:eq(i.session),command:t,positionals:a,flags:eL({stateDir:i.stateDir,daemonBaseUrl:i.daemonBaseUrl,daemonAuthToken:i.daemonAuthToken,daemonTransport:i.daemonTransport,daemonServerMode:i.daemonServerMode,tenant:i.tenant,sessionIsolation:i.sessionIsolation,runId:i.runId,leaseId:i.leaseId,platform:i.platform,target:i.target,device:i.device,udid:i.udid,serial:i.serial,iosSimulatorDeviceSet:i.iosSimulatorDeviceSet,androidDeviceAllowlist:i.androidDeviceAllowlist,runtime:i.simulatorRuntimeId,boot:i.boot,reuseExisting:i.reuseExisting,surface:i.surface,activity:i.activity,relaunch:i.relaunch,shutdown:i.shutdown,saveScript:i.saveScript,noRecord:i.noRecord,backMode:i.backMode,metroHost:i.metroHost,metroPort:i.metroPort,bundleUrl:i.bundleUrl,launchUrl:i.launchUrl,snapshotInteractiveOnly:i.interactiveOnly,snapshotCompact:i.compact,snapshotDepth:i.depth,snapshotScope:i.scope,snapshotRaw:i.raw,screenshotFullscreen:i.screenshotFullscreen,overlayRefs:i.overlayRefs,appsFilter:i.appsFilter,out:i.out,count:i.count,fps:i.fps,hideTouches:i.hideTouches,intervalMs:i.intervalMs,delayMs:i.delayMs,holdMs:i.holdMs,jitterPx:i.jitterPx,pixels:i.pixels,doubleTap:i.doubleTap,clickButton:i.clickButton,pauseMs:i.pauseMs,pattern:i.pattern,headless:i.headless,restart:i.restart,replayUpdate:i.replayUpdate,failFast:i.failFast,timeoutMs:i.timeoutMs,retries:i.retries,artifactsDir:i.artifactsDir,reportJunit:i.reportJunit,findFirst:i.findFirst,findLast:i.findLast,networkInclude:i.networkInclude,batchOnError:i.batchOnError,batchMaxSteps:i.batchMaxSteps,batchSteps:i.batchSteps,verbose:i.debug}),runtime:i.runtime,meta:eL({requestId:i.requestId,cwd:i.cwd,debug:i.debug,lockPolicy:i.lockPolicy,lockPlatform:i.lockPlatform,tenantId:i.tenant,runId:i.runId,leaseId:i.leaseId,sessionIsolation:i.sessionIsolation,installSource:i.installSource,retainMaterializedPaths:i.retainMaterializedPaths,materializedPathRetentionMs:i.materializedPathRetentionMs,materializationId:i.materializationId})});return s.ok||function(e){throw new u(e.code,e.message,{...e.details??{},hint:e.hint,diagnosticId:e.diagnosticId,logPath:e.logPath})}(s.error),s.data??{}},s=async(e={})=>{let t=await i("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(ej)},l=async(e,t=[],a={})=>await i(e,t,a),d=(t={})=>eq(eV(e,n,t).session);return{command:(a=async e=>await i(e.command,e.positionals,e.options),r=async e=>await a(e),{wait:async e=>await r(function(e){if(1!==[void 0!==e.durationMs?"durationMs":void 0,void 0!==e.text?"text":void 0,void 0!==e.ref?"ref":void 0,void 0!==e.selector?"selector":void 0].filter(Boolean).length)throw new u("INVALID_ARGS","wait command requires exactly one of durationMs, text, ref, or selector.");if(void 0!==e.durationMs)return{command:eI,positionals:[String(e.durationMs)],options:e};let t=void 0!==e.timeoutMs?[String(e.timeoutMs)]:[];if(void 0!==e.text)return{command:eI,positionals:["text",e.text,...t],options:e};if(void 0!==e.ref)return{command:eI,positionals:[e.ref,...t],options:e};let a=e.selector;return function(e){if(!g(e))throw new u("INVALID_ARGS",`Invalid wait selector: ${e}`)}(a),{command:eI,positionals:[a,...t],options:e}}(e)),alert:async(e={})=>{var t;return await r({command:"alert",positionals:[(t=e).action??"get",...void 0!==t.timeoutMs?[String(t.timeoutMs)]:[]],options:t})},appState:async(e={})=>await r({command:"appstate",positionals:[],options:e}),back:async(e={})=>await r({command:"back",positionals:[],options:{...e,backMode:e.mode}}),home:async(e={})=>await r({command:"home",positionals:[],options:e}),rotate:async e=>await r({command:"rotate",positionals:[e.orientation],options:e}),appSwitcher:async(e={})=>await r({command:"app-switcher",positionals:[],options:e}),keyboard:async(e={})=>await r({command:"keyboard",positionals:e.action?[e.action]:[],options:e}),clipboard:async e=>{var t;return await r("read"===(t=e).action?{command:eg,positionals:["read"],options:t}:{command:eg,positionals:["write",t.text],options:t})}}),devices:{list:async(e={})=>{let t=await i("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(e$)},boot:async(e={})=>await l("boot",[],e)},sessions:{list:async(e={})=>await s(e),close:async(e={})=>{let t=d(e),a=(await i("close",[],e)).shutdown;return{session:t,shutdown:"object"==typeof a&&null!==a?a:void 0,identifiers:{session:t}}}},simulators:{ensure:async e=>{let{runtime:t,...a}=e,r=await i("ensure-simulator",[],{...a,simulatorRuntimeId:t}),o=eM(r,"udid"),n=eM(r,"device");return{udid:o,device:n,runtime:eM(r,"runtime"),created:!0===r.created,booted:!0===r.booted,iosSimulatorDeviceSet:eD(r,"ios_simulator_device_set"),identifiers:{deviceId:o,deviceName:n,udid:o}}}},apps:{install:async e=>eF(await i("install",[e.app,e.appPath],e),d(e)),reinstall:async e=>eF(await i("reinstall",[e.app,e.appPath],e),d(e)),installFromSource:async e=>(function(e,t){let a=eP(e,"bundleId"),r=eP(e,"packageName"),o=a??r??eP(e,"appId"),n=eP(e,"launchTarget")??r??a??o;if(!n)throw new u("COMMAND_FAILED",'Daemon response is missing "launchTarget".',{response:e});return{appName:eP(e,"appName"),appId:o,bundleId:a,packageName:r,launchTarget:n,installablePath:eP(e,"installablePath"),archivePath:eP(e,"archivePath"),materializationId:eP(e,"materializationId"),materializationExpiresAt:eP(e,"materializationExpiresAt"),identifiers:eb({session:t,bundleId:a,packageName:r,appId:o})}})(await i("install_source",[],{...e,installSource:e.source,retainMaterializedPaths:e.retainPaths,materializedPathRetentionMs:e.retentionMs}),d(e)),list:async(e={})=>{let t=await i("apps",[],e);return Array.isArray(t.apps)?t.apps.filter(e=>"string"==typeof e):[]},open:async e=>{let t=d(e),a=e.app?e.url?[e.app,e.url]:[e.app]:[],r=await i("open",a,e),o=function(e){let t=e.platform,a=eP(e,"id"),r=eP(e,"device");if("ios"!==t&&"macos"!==t&&"android"!==t||!a||!r)return;let o=ek(e,"target"),n=eS(t,a,r);return{platform:t,target:o,id:a,name:r,identifiers:n,ios:"ios"===t?{udid:eP(e,"device_udid")??a,simulatorSetPath:eD(e,"ios_simulator_device_set")}:void 0,android:"android"===t?{serial:eP(e,"serial")??a}:void 0}}(r),n=eP(r,"appBundleId");return{session:t,appName:eP(r,"appName"),appBundleId:n,appId:n,startup:function(e){if(eO(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:eP(e,"appTarget"),appBundleId:eP(e,"appBundleId")}}(r.startup),runtime:function(e){if(!eO(e))return;let t=e.platform,a=eP(e,"metroHost"),r="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:a,metroPort:r,bundleUrl:eP(e,"bundleUrl"),launchUrl:eP(e,"launchUrl")}}(r.runtime),device:o,identifiers:{session:t,deviceId:o?.id,deviceName:o?.name,udid:o?.ios?.udid,serial:o?.android?.serial,appId:n,appBundleId:n}}},close:async(e={})=>{let t=d(e),a=(await i("close",e.app?[e.app]:[],e)).shutdown;return{session:t,closedApp:e.app,shutdown:"object"==typeof a&&null!==a?a:void 0,identifiers:{session:t}}},push:async e=>{var t;return await l("push",[e.app,"string"==typeof(t=e.payload)?t:JSON.stringify(t)],e)},triggerEvent:async e=>{var t;return await l("trigger-app-event",[(t=e).event,...t.payload?[JSON.stringify(t.payload)]:[]],e)}},materializations:{release:async e=>{var t;return{released:!0===(t=await i("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:eM(t,"materializationId"),identifiers:{}}}},metro:{prepare:async t=>await y({projectRoot:t.projectRoot??e.cwd,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.bearerToken,launchUrl:t.launchUrl,companionProfileKey:t.companionProfileKey,companionConsumerKey:t.companionConsumerKey,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(e={})=>{var t;let a=d(e),r=await i("snapshot",[],e),o=eP(r,"appBundleId"),n="object"==typeof r.visibility&&null!==r.visibility?r.visibility:void 0;return{nodes:Array.isArray(t=r.nodes)?t:[],truncated:!0===r.truncated,appName:eP(r,"appName"),appBundleId:o,...n?{visibility:n}:{},warnings:Array.isArray(r.warnings)?r.warnings.filter(e=>"string"==typeof e):void 0,identifiers:{session:a,appId:o,appBundleId:o}}},screenshot:async(e={})=>{let t=d(e),a=await i("screenshot",e.path?[e.path]:[],{...e,screenshotFullscreen:e.fullscreen});return{path:eM(a,"path"),overlayRefs:function(e){let t=e.overlayRefs;if(!Array.isArray(t))return;let a=[];for(let e of t){if(!eO(e))continue;let t=eP(e,"ref"),r=eT(e,"rect"),o=eT(e,"overlayRect"),n=function(e,t){let a=e[t];if(!eO(a))return;let r="number"==typeof a.x?a.x:void 0,o="number"==typeof a.y?a.y:void 0;if(void 0!==r&&void 0!==o)return{x:r,y:o}}(e,"center");t&&r&&o&&n&&a.push({ref:t,label:eP(e,"label"),rect:r,overlayRect:o,center:n})}return a}(a),identifiers:{session:t}}},diff:async e=>await l("diff",[e.kind],{...e,interactiveOnly:e.interactiveOnly,compact:e.compact,depth:e.depth,scope:e.scope,raw:e.raw})},interactions:{click:async e=>await l("click",ez(e),{...e,clickButton:e.button}),press:async e=>await l("press",ez(e),e),longPress:async e=>await l("longpress",[String(e.x),String(e.y),...eK(e.durationMs)],e),swipe:async e=>await l("swipe",[String(e.from.x),String(e.from.y),String(e.to.x),String(e.to.y),...eK(e.durationMs)],e),focus:async e=>await l("focus",[String(e.x),String(e.y)],e),type:async e=>await l("type",[e.text],e),fill:async e=>await l("fill",[...ez(e),e.text],e),scroll:async e=>await l("scroll",[e.direction,...eK(e.amount)],e),pinch:async e=>await l("pinch",[String(e.scale),...eK(e.x),...eK(e.y)],e),get:async e=>{var t;return await l("get",[e.format,...void 0!==(t=e).ref?[t.ref,...eG(t.label)]:[t.selector]],e)},is:async e=>await l("is",[e.predicate,e.selector,..."text"===e.predicate?[e.value]:[]],e),find:async e=>await l("find",function(e){let t=e.locator&&"any"!==e.locator?[e.locator,e.query]:[e.query];switch(e.action){case void 0:case"click":case"focus":case"exists":return e.action?[...t,e.action]:t;case"getText":return[...t,"get","text"];case"getAttrs":return[...t,"get","attrs"];case"wait":return[...t,"wait",...eK(e.timeoutMs)];case"fill":case"type":return[...t,e.action,e.value]}}(e),{...e,findFirst:e.first,findLast:e.last})},replay:{run:async e=>await l("replay",[e.path],{...e,replayUpdate:e.update}),test:async e=>await l("test",e.paths,{...e,replayUpdate:e.update})},batch:{run:async e=>await l("batch",[],{...e,batchSteps:e.steps,batchOnError:e.onError,batchMaxSteps:e.maxSteps})},observability:{perf:async(e={})=>await l("perf",[],e),logs:async(e={})=>{var t;return await l("logs",[(t=e).action??"path",...eG(t.message)],e)},network:async(e={})=>{var t;return await l("network",[...(t=e).action?[t.action]:[],...eK(t.limit)],{...e,networkInclude:e.include})}},recording:{record:async e=>await l("record",[e.action,...eG(e.path)],e),trace:async e=>await l("trace",[e.action,...eG(e.path)],e)},settings:{update:async e=>await l("settings",[e.setting,e.state,..."permission"in e?[e.permission]:[],..."mode"in e&&e.mode?[e.mode]:[]],e)}}}function ez(e){return void 0!==e.ref?[e.ref,...eG(e.label)]:void 0!==e.selector?[e.selector]:[String(e.x),String(e.y)]}function eG(e){return void 0===e?[]:[e]}function eK(e){return void 0===e?[]:[String(e)]}function eV(e,t,a){return a.remoteConfig&&a.remoteConfig!==e.remoteConfig?{...eH({...e,...a}),...e,...a}:{...t,...e,...a}}function eH(e){var t;if(!e.remoteConfig)return{};let{runtime:a,...r}=(t={remoteConfig:e.remoteConfig,cwd:e.cwd??process.cwd(),env:process.env}).remoteConfig?{...function(e){let t={};for(let a of eA){let r=e[a];void 0!==r&&(t[a]=r)}return t}(A({configPath:t.remoteConfig,cwd:t.cwd,env:t.env}).profile),remoteConfig:t.remoteConfig}:{};return r}export{AppError}from"./152.js";export{centerOfRect}from"./57.js";export{eB as createAgentDeviceClient};
1
+ import e from"node:net";import t from"node:http";import a from"node:https";import r from"node:fs";import n from"node:path";import{fileURLToPath as o}from"node:url";import{AsyncLocalStorage as i}from"node:async_hooks";import s,{createHash as l}from"node:crypto";import"node:os";import{spawn as d}from"node:child_process";import{redactDiagnosticData as c,AppError as u}from"./152.js";import{resolveUserPath as p,expandUserHomePath as m}from"./267.js";import{runCmdDetached as f,runCmdSync as h}from"./818.js";import{prepareMetroRuntime as y,isAgentDeviceDaemonProcess as w,stopProcessForTakeover as v}from"./974.js";import{tryParseSelectorChain as g}from"./940.js";function I(){let e=n.dirname(o(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=n.join(t,"package.json");if(r.existsSync(e))return t;t=n.dirname(t)}return e}let b=new i;function A(){return s.randomBytes(8).toString("hex")}function _(e){let t=b.getStore();if(!t)return;let a={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?c(e.data):void 0};if(t.events.push(a),!t.debug)return;let n=`[agent-device][diag] ${JSON.stringify(a)}
2
+ `;try{t.logPath&&r.appendFile(t.logPath,n,()=>{}),t.traceLogPath&&r.appendFile(t.traceLogPath,n,()=>{}),t.logPath||t.traceLogPath||process.stderr.write(n)}catch{}}async function S(e,t,a){let r=Date.now();try{let n=await t();return _({level:"info",phase:e,durationMs:Date.now()-r,data:a}),n}catch(t){throw _({level:"error",phase:e,durationMs:Date.now()-r,data:{...a??{},error:t instanceof Error?t.message:String(t)}}),t}}function M(e){let t,a=(t=(e??"").trim())?p(t):n.join(m("~"),".agent-device");return{baseDir:a,infoPath:n.join(a,"daemon.json"),lockPath:n.join(a,"daemon.lock"),logPath:n.join(a,"daemon.log"),sessionsDir:n.join(a,"sessions")}}let P="sha256";async function k(e){let{localPath:o,baseUrl:i,token:s}=e,l=r.statSync(o),c=l.isDirectory(),p=n.basename(o),m=c?"app-bundle":"file",f=i.endsWith("/")?i:`${i}/`,h=c?void 0:await E(o);if(h){let e=await D({normalizedBase:f,token:s,hash:h,filename:p,sizeBytes:l.size,artifactType:m});if(e)return e}let y=new URL("upload",f),w="https:"===y.protocol?a:t,v={"x-artifact-type":m,"x-artifact-filename":p,"transfer-encoding":"chunked"};return h&&(v["x-artifact-hash"]=h,v["x-artifact-hash-algorithm"]=P),s&&(v.authorization=`Bearer ${s}`,v["x-agent-device-token"]=s),new Promise((e,t)=>{let a=w.request({protocol:y.protocol,host:y.hostname,port:y.port,method:"POST",path:y.pathname+y.search,headers:v},a=>{let r="";a.setEncoding("utf8"),a.on("data",e=>{r+=e}),a.on("end",()=>{clearTimeout(i);try{let a=JSON.parse(r);if(!a.ok||!a.uploadId)return void t(new u("COMMAND_FAILED",`Upload failed: ${r}`));e(a.uploadId)}catch{t(new u("COMMAND_FAILED",`Invalid upload response: ${r}`))}})}),i=setTimeout(()=>{a.destroy(),t(new u("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(a.on("error",e=>{clearTimeout(i),t(new u("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),c){let e=d("tar",["cf","-","-C",n.dirname(o),n.basename(o)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(a),e.on("error",e=>{a.destroy(),t(new u("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(a.destroy(),t(new u("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=r.createReadStream(o);e.pipe(a),e.on("error",e=>{a.destroy(),t(new u("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}async function D(e){var t;let a=new URL("upload/preflight",e.normalizedBase),r={"content-type":"application/json"};e.token&&(r.authorization=`Bearer ${e.token}`,r["x-agent-device-token"]=e.token);let n=await fetch(a,{method:"POST",headers:r,signal:AbortSignal.timeout(3e4),body:JSON.stringify({hash:e.hash,hashAlgorithm:P,fileName:e.filename,sizeBytes:e.sizeBytes,artifactType:e.artifactType})}).catch(()=>void 0);if(!n?.ok)return;let o=await n.json().catch(()=>void 0);return(t=o)&&"object"==typeof t&&!0===t.ok&&!0===t.cacheHit&&"string"==typeof t.uploadId?o.uploadId:void 0}async function E(e){let t=l(P);return await new Promise((a,n)=>{r.createReadStream(e).on("data",e=>t.update(e)).on("error",e=>{n(new u("COMMAND_FAILED","Failed to read local artifact",{},e))}).on("end",a)}),t.digest("hex")}let T=/(?:^|[^\w$.])(?:import|export)\s+(?:type\s+)?(?:[^'"`]*?\s+from\s+)?['"]([^'"]+)['"]/gm,N=/import\(\s*['"]([^'"]+)['"]\s*\)/gm,x=[".ts",".tsx",".js",".jsx",".mjs",".cjs"];function U(e,t,a){t.lastIndex=0;let r=null;for(;null!==(r=t.exec(e));){let e=r[1]?.trim();e?.startsWith(".")&&a.add(e)}}function R(e){try{return r.statSync(e).isFile()?e:null}catch{return null}}let O=eh(),C=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}(),L=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}(),F=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"],j=new e.BlockList;async function $(t){let a=t.meta?.requestId??A(),r=!!(t.meta?.debug||t.flags?.verbose),n=function(t){let a,r,n,o=t.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,i=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new u("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new u("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(t.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),s=t.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN;var l=i,d=s;if(!(!l||"localhost"===(a=new URL(l).hostname.trim().toLowerCase().replace(/^\[(.*)\]$/,"$1"))||(e.isIPv4(a)?j.check(a,"ipv4"):!!e.isIPv6(a)&&j.check(a,"ipv6")))&&("string"!=typeof d||!(d.trim().length>0)))throw new u("INVALID_ARGS","Remote daemon base URL for non-loopback hosts requires daemon authentication",{daemonBaseUrl:l,hint:"Provide --daemon-auth-token or AGENT_DEVICE_DAEMON_AUTH_TOKEN when using a non-loopback remote daemon URL."});let c=t.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,p="auto"===(r=(c??"").trim().toLowerCase())?"auto":"socket"===r?"socket":"http"===r?"http":"auto";if(i&&"socket"===p)throw new u("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:i});let m="http"===(n=(t.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===c?"dual":void 0)??"").trim().toLowerCase())?"http":"dual"===n?"dual":"socket";return{paths:M(o),transportPreference:p,serverMode:m,remoteBaseUrl:i,remoteAuthToken:s}}(t),o=function(e,t=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if("test"!==e)return eh(t)}(t.command),i=await S("daemon_startup",async()=>await V(n),{requestId:a,session:t.session}),s=await B(t,i),l={...t,positionals:s.positionals,flags:s.flags,token:i.token,meta:{...t.meta??{},requestId:a,debug:r,cwd:t.meta?.cwd,tenantId:t.meta?.tenantId??t.flags?.tenant,runId:t.meta?.runId??t.flags?.runId,leaseId:t.meta?.leaseId??t.flags?.leaseId,sessionIsolation:t.meta?.sessionIsolation??t.flags?.sessionIsolation,lockPolicy:t.meta?.lockPolicy,lockPlatform:t.meta?.lockPlatform,...s.uploadedArtifactId?{uploadedArtifactId:s.uploadedArtifactId}:{},...s.clientArtifactPaths?{clientArtifactPaths:s.clientArtifactPaths}:{},...s.installSource?{installSource:s.installSource}:{}}};return _({level:"info",phase:"daemon_request_prepare",data:{requestId:a,command:t.command,session:t.session}}),await S("daemon_request",async()=>await en(i,l,n.transportPreference,o),{requestId:a,command:t.command})}async function B(e,t){let a,o=[...e.positionals??[]],i=e.flags?{...e.flags}:void 0,s=e.meta?.installSource,l={};if(eu(t)){let r=function(e,t){if("screenshot"===e.command){let a=z(e,"path",".png");return t[0]?{field:"path",localPath:a,positionalIndex:0,positionalPath:G("screenshot",".png")}:{field:"path",localPath:a,positionalIndex:0,flagPath:G("screenshot",".png")}}if("record"===e.command&&"start"===(t[0]??"").toLowerCase()){let t=z(e,"outPath",".mp4",1);return{field:"outPath",localPath:t,positionalIndex:1,positionalPath:G("recording",n.extname(t)||".mp4")}}return null}(e,o);r&&(void 0!==r.positionalPath&&(o[r.positionalIndex]=r.positionalPath),void 0!==r.flagPath&&((i??={}).out=r.flagPath),l[r.field]=r.localPath);let d=await q(e,t);d&&(s=d.installSource,a=d.uploadedArtifactId??a)}if(!eu(t)||"install"!==e.command&&"reinstall"!==e.command||o.length<2)return{positionals:o,flags:i,installSource:s,uploadedArtifactId:a,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let d=o[1];if(d.startsWith("remote:"))return o[1]=d.slice(7),{positionals:o,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}};let c=n.isAbsolute(d)?d:n.resolve(e.meta?.cwd??process.cwd(),d);return r.existsSync(c)?{positionals:o,flags:i,installSource:s,uploadedArtifactId:a=await k({localPath:c,baseUrl:t.baseUrl,token:t.token}),...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}:{positionals:o,flags:i,...Object.keys(l).length>0?{clientArtifactPaths:l}:{}}}async function q(e,t){let a=e.meta?.installSource;if("install_source"!==e.command||!a||"path"!==a.kind)return null;let o=a.path.trim();if(!o)return{installSource:a};if(o.startsWith("remote:"))return{installSource:{...a,path:o.slice(7)}};let i=n.isAbsolute(o)?o:n.resolve(e.meta?.cwd??process.cwd(),o);if(!r.existsSync(i))return{installSource:{...a,path:i}};let s=await k({localPath:i,baseUrl:t.baseUrl,token:t.token});return{installSource:{...a,path:i},uploadedArtifactId:s}}function z(e,t,a,r=0){let o=e.positionals?.[r]??e.flags?.out,i=`${"path"===t?"screenshot":"recording"}-${Date.now()}${a}`,s=o&&o.trim().length>0?o:i;return n.isAbsolute(s)?s:n.resolve(e.meta?.cwd??process.cwd(),s)}function G(e,t){let a=t.startsWith(".")?t:`.${t}`;return n.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${a}`)}async function V(e){let t;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await et(t,"http"))return t;throw new u("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 a=Q(e.paths.infoPath),o=function(){try{let e=I();return JSON.parse(r.readFileSync(n.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),i=function(e,t=I()){try{let a=n.resolve(t),o=[n.resolve(e)],i=new Set,l=[];for(;o.length>0;){let e=o.pop();if(!e||i.has(e))continue;i.add(e);let t=r.statSync(e);if(!t.isFile())continue;let s=n.relative(a,e)||e;l.push(`${s}:${t.size}:${Math.trunc(t.mtimeMs)}`);let d=r.readFileSync(e,"utf8");for(let t of function(e){let t=new Set;return U(e,T,t),U(e,N,t),[...t]}(d)){let a=function(e,t){let a=n.resolve(n.dirname(e),t),r=R(a);if(r)return r;for(let e of x){let t=R(`${a}${e}`);if(t)return t}for(let e of x){let t=R(n.join(a,`index${e}`));if(t)return t}return null}(e,t);a&&o.push(a)}}let d=l.sort().join("|"),c=s.createHash("sha1").update(d).digest("hex");return`graph:${l.length}:${c}`}catch{return"unknown"}}((t=er()).useSrc?t.srcPath:t.distPath,t.root),l=!!a&&await et(a,e.transportPreference);if(a&&a.version===o&&a.codeSignature===i&&l)return a;a&&(a.version!==o||a.codeSignature!==i||!l)&&(await W(a),ee(e.paths.infoPath)),function(e){let t=Y(e);if(!t.hasLock||t.hasInfo)return;let a=X(e.lockPath);if(!a)return ee(e.lockPath);w(a.pid,a.processStartTime)||ee(e.lockPath)}(e.paths);let d=0;for(let t=1;t<=L;t+=1){await ea(e);let a=await K(C,e);if(a)return a;if(await J(e.paths)){d+=1;continue}let r=Y(e.paths);if(!(t<L))break;if(!r.hasInfo&&!r.hasLock){await H(150);continue}}let c=Y(e.paths);throw new u("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:C,startupAttempts:L,lockRecoveryCount:d,metadataState:c,hint:function(e,t=M(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.`}(c,e.paths)})}async function K(e,t){let a=Date.now();for(;Date.now()-a<e;){let e=Q(t.paths.infoPath);if(e&&await et(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function H(e){await new Promise(t=>setTimeout(t,e))}async function J(e){let t=Y(e);if(!t.hasLock||t.hasInfo)return!1;let a=X(e.lockPath);return a&&w(a.pid,a.processStartTime)&&await v(a.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:a.processStartTime}),ee(e.lockPath),!0}async function W(e){await v(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function Q(e){let t=Z(e);if(!t||"object"!=typeof t)return null;let a="string"==typeof t.token&&t.token.length>0?t.token:null;if(!a)return null;let r=Number.isInteger(t.port)&&Number(t.port)>0,n=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;if(!r&&!n)return null;let o=t.transport,i="string"==typeof t.version?t.version:void 0,s="string"==typeof t.codeSignature?t.codeSignature:void 0,l="string"==typeof t.processStartTime?t.processStartTime:void 0,d=Number.isInteger(t.pid)&&Number(t.pid)>0;return{token:a,port:r?Number(t.port):void 0,httpPort:n?Number(t.httpPort):void 0,transport:"socket"===o||"http"===o||"dual"===o?o:void 0,pid:d?Number(t.pid):0,version:i,codeSignature:s,processStartTime:l}}function X(e){let t=Z(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}j.addSubnet("127.0.0.0",8,"ipv4"),j.addAddress("::1","ipv6"),j.addSubnet("::ffff:127.0.0.0",104,"ipv6");function Y(e){return{hasInfo:r.existsSync(e.infoPath),hasLock:r.existsSync(e.lockPath)}}function Z(e){if(!r.existsSync(e))return null;try{return JSON.parse(r.readFileSync(e,"utf8"))}catch{return null}}function ee(e){try{r.existsSync(e)&&r.unlinkSync(e)}catch{}}async function et(r,n){var o;return"http"===eo(r,n)?await function(e){let r=e.baseUrl?ep(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!r)return Promise.resolve(!1);let n=new URL(r),o="https:"===n.protocol?a:t,i=e.baseUrl?3e3:500;return new Promise(e=>{let t=o.request({protocol:n.protocol,host:n.hostname,port:n.port,path:n.pathname+n.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()})}(r):await ((o=r.port)?new Promise(t=>{let a=e.createConnection({host:"127.0.0.1",port:o},()=>{a.destroy(),t(!0)});a.on("error",()=>{t(!1)})}):Promise.resolve(!1))}async function ea(e){let t=er(),a=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],r={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};f(process.execPath,a,{env:r})}function er(){let e=I(),t=n.join(e,"dist","src","daemon.js"),a=n.join(e,"src","daemon.ts"),o=r.existsSync(t),i=r.existsSync(a);if(!o&&!i)throw new u("COMMAND_FAILED","Daemon entry not found",{distPath:t,srcPath:a});return{root:e,distPath:t,srcPath:a,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!o&&i}}async function en(e,t,a,r){return"http"===eo(e,a)?await ec(e,t,r):await ed(e,t,r)}function eo(e,t){if(e.baseUrl){if("socket"===t)throw new u("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t||"socket"===t){var a=e,r=t;if(ei(a,r))return r;throw new u("COMMAND_FAILED","http"===r?"Daemon HTTP endpoint is unavailable":"Daemon socket endpoint is unavailable")}let n=("socket"===e.transport||"dual"===e.transport?["socket","http"]:["http","socket"]).find(t=>ei(e,t));if(n)return n;throw new u("COMMAND_FAILED","Daemon metadata has no reachable transport")}function ei(e,t){return"http"===t?!!e.httpPort:!!e.port}function es(e,t,a,r,n,o){let i=n?{terminated:0}:function(){let e=0;try{for(let t of F){let a=h("pkill",["-f",t],{allowFailure:!0});0===a.exitCode&&(e+=1)}return{terminated:e}}catch(t){return{terminated:e,error:t instanceof Error?t.message:String(t)}}}(),s=n?{forcedKill:!1}:function(e,t){let a=!1;try{w(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),a=!0)}catch{v(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{ee(t.infoPath),ee(t.lockPath)}return{forcedKill:a}}(e,t);return _({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:o,requestId:a,command:r,timedOutRunnerPidsTerminated:i.terminated,timedOutRunnerCleanupError:i.error,daemonPidReset:n?void 0:e.pid,daemonPidForceKilled:n?void 0:s.forcedKill,daemonBaseUrl:e.baseUrl}}),new u("COMMAND_FAILED","Daemon request timed out",{timeoutMs:o,requestId:a,hint:n?"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 el(e,t,a){return _({level:"error",phase:"daemon_request_socket_error",data:{requestId:t,message:e instanceof Error?e.message:String(e)}}),new u("COMMAND_FAILED","Failed to communicate with daemon",{requestId:t,hint:a?"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 ed(t,a,r){let n=t.port;if(!n)throw new u("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((o,i)=>{let s=e.createConnection({host:"127.0.0.1",port:n},()=>{s.write(`${JSON.stringify(a)}
3
+ `)}),l=M(a.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),d="number"==typeof r?setTimeout(()=>{s.destroy(),i(es(t,l,a.meta?.requestId,a.command,!1,r))},r):void 0,c="";s.setEncoding("utf8"),s.on("data",e=>{let t=(c+=e).indexOf("\n");if(-1===t)return;let r=c.slice(0,t).trim();if(r)try{let e=JSON.parse(r);s.end(),d&&clearTimeout(d),o(e)}catch(e){d&&clearTimeout(d),i(new u("COMMAND_FAILED","Invalid daemon response",{requestId:a.meta?.requestId,line:r},e instanceof Error?e:void 0))}}),s.on("error",e=>{d&&clearTimeout(d),i(el(e,a.meta?.requestId,!1))})})}async function ec(e,r,n){var o,i,s;let l,d=e.baseUrl?new URL(ep(e.baseUrl,"rpc")):e.httpPort?new URL(`http://127.0.0.1:${e.httpPort}/rpc`):null;if(!d)throw new u("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let c=JSON.stringify((o=r,i={includeTokenParam:!e.baseUrl},l=o.meta?.requestId??A(),"lease_allocate"!==(s=o.command)&&"lease_heartbeat"!==s&&"lease_release"!==s?{jsonrpc:"2.0",id:l,method:"agent_device.command",params:o}:{jsonrpc:"2.0",id:l,method:function(e){switch(e){case"lease_allocate":return"agent_device.lease.allocate";case"lease_heartbeat":return"agent_device.lease.heartbeat";case"lease_release":return"agent_device.lease.release"}}(o.command),params:function(e,t,a){let r={...a.includeTokenParam?{token:e.token}:{},session:e.session,tenantId:e.meta?.tenantId,runId:e.meta?.runId};switch(t){case"lease_allocate":return{...r,ttlMs:e.meta?.leaseTtlMs,backend:e.meta?.leaseBackend};case"lease_heartbeat":return{...r,leaseId:e.meta?.leaseId,ttlMs:e.meta?.leaseTtlMs};case"lease_release":return{...r,leaseId:e.meta?.leaseId}}}(o,o.command,i)})),p={"content-type":"application/json","content-length":Buffer.byteLength(c)};return e.baseUrl&&e.token&&(p.authorization=`Bearer ${e.token}`,p["x-agent-device-token"]=e.token),await new Promise((o,i)=>{let s=M(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),l=("https:"===d.protocol?a:t).request({protocol:d.protocol,host:d.hostname,port:d.port,method:"POST",path:d.pathname+d.search,headers:p},t=>{let a="";t.setEncoding("utf8"),t.on("data",e=>{a+=e}),t.on("end",()=>{f&&clearTimeout(f);try{let t=JSON.parse(a);if(t.error){let e=t.error.data??{};i(new u(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:r.meta?.requestId}));return}if(!t.result||"object"!=typeof t.result)return void i(new u("COMMAND_FAILED","Invalid daemon RPC response",{requestId:r.meta?.requestId}));if(e.baseUrl&&t.result.ok)return void em(e,r,t.result).then(o).catch(i);o(t.result)}catch(e){f&&clearTimeout(f),i(new u("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:a},e instanceof Error?e:void 0))}})}),m=eu(e),f="number"==typeof n?setTimeout(()=>{l.destroy(),i(es(e,s,r.meta?.requestId,r.command,m,n))},n):void 0;l.on("error",e=>{f&&clearTimeout(f),i(el(e,r.meta?.requestId,m))}),l.write(c),l.end()})}function eu(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function ep(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function em(e,t,a){let r=Array.isArray(a.data?.artifacts)?a.data.artifacts:[];if(0===r.length||!e.baseUrl)return a;let o=a.data?{...a.data}:{},i=[];for(let a of r){if(!a||"object"!=typeof a||"string"!=typeof a.artifactId){i.push(a);continue}let r=function(e,t){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let a=e.fileName?.trim()||`${e.field}-${Date.now()}`;return n.resolve(t.meta?.cwd??process.cwd(),a)}(a,t);await ef({baseUrl:e.baseUrl,token:e.token,artifactId:a.artifactId,destinationPath:r,requestId:t.meta?.requestId}),o[a.field]=r,i.push({...a,localPath:r})}return o.artifacts=i,{ok:!0,data:o}}async function ef(e){var o,i;let s,l=new URL((o=e.baseUrl,i=e.artifactId,s=o.endsWith("/")?o:`${o}/`,new URL(`upload/${encodeURIComponent(i)}`,s).toString())),d="https:"===l.protocol?a:t;await r.promises.mkdir(n.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,a)=>{let n=!1,o=e.timeoutMs??O,i=o=>{if(!n){if(n=!0,clearTimeout(c),o)return void r.promises.rm(e.destinationPath,{force:!0}).finally(()=>a(o));t()}},s=d.request({protocol:l.protocol,host:l.hostname,port:l.port,method:"GET",path:l.pathname+l.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let a="";t.setEncoding("utf8"),t.on("data",e=>{a+=e}),t.on("end",()=>{i(new u("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:a}))});return}let a=r.createWriteStream(e.destinationPath);a.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 u("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),a.on("finish",()=>{a.close(()=>i())}),t.pipe(a)}),c=setTimeout(()=>{s.destroy(new u("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:o}))},o);s.on("error",t=>{t instanceof u?i(t):i(new u("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:o},t instanceof Error?t:void 0))}),s.end()})}function eh(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}let ey="clipboard",ew="wait";function ev(e){let t=e.appId??e.bundleId??e.packageName;return{session:e.session,appId:t,appBundleId:e.bundleId,package:e.packageName}}function eg(e,t,a){return{deviceId:t,deviceName:a,..."android"===e?{serial:t}:"ios"===e?{udid:t}:{}}}function eI(e,t,a,r){let n=a(e[t]);if(void 0===n)throw new u("COMMAND_FAILED",r,{response:e});return n}function eb(e,t){return eI(e,t,ek,`Daemon response is missing "${t}".`)}function eA(e,t){return ek(e[t])}function e_(e,t){var a;let r;return a=ek,null===(r=e[t])?null:a(r)}function eS(e,t){return eI(e,t,eE,`Daemon response has invalid "${t}".`)}function eM(e,t){return function(e){return"tv"===e||"mobile"===e||"desktop"===e?e:void 0}(e[t])??"mobile"}function eP(e,t){let a=e[t];if(!ex(a))return;let r="number"==typeof a.x?a.x:void 0,n="number"==typeof a.y?a.y:void 0,o="number"==typeof a.width?a.width:void 0,i="number"==typeof a.height?a.height:void 0;if(void 0!==r&&void 0!==n&&void 0!==o&&void 0!==i)return{x:r,y:n,width:o,height:i}}function ek(e){return"string"==typeof e&&e.length>0?e:void 0}function eD(e){return"number"==typeof e&&Number.isFinite(e)?e:void 0}function eE(e){return"ios"===e||"macos"===e||"android"===e?e:void 0}function eT(e){return"simulator"===e||"emulator"===e||"device"===e?e:void 0}function eN(e){if(!ex(e))throw new u("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function ex(e){return"object"==typeof e&&null!==e}function eU(e){let t={};for(let[a,r]of Object.entries(e))void 0!==r&&(t[a]=r);return t}function eR(e,t){let a=eA(e,"bundleId"),r=eA(e,"package");return{app:eb(e,"app"),appPath:eb(e,"appPath"),platform:eS(e,"platform"),appId:a??r,bundleId:a,package:r,identifiers:ev({session:t,bundleId:a,packageName:r})}}function eO(e){let t=eN(e),a=eS(t,"platform"),r=eb(t,"id"),n=eb(t,"name");return{platform:a,target:eM(t,"target"),kind:eI(t,"kind",eT,'Daemon response has invalid "kind".'),id:r,name:n,booted:"boolean"==typeof t.booted?t.booted:void 0,identifiers:eg(a,r,n),ios:"ios"===a?{udid:r}:void 0,android:"android"===a?{serial:r}:void 0}}function eC(e){let t=eN(e),a=eS(t,"platform"),r=eb(t,"id"),n=eb(t,"name"),o=eM(t,"target"),i=eb(t,"device"),s={session:n,...eg(a,r,i)};return{name:n,createdAt:eI(t,"createdAt",eD,'Daemon response is missing numeric "createdAt".'),device:{platform:a,target:o,id:r,name:i,identifiers:s,ios:"ios"===a?{udid:r,simulatorSetPath:e_(t,"ios_simulator_device_set")}:void 0,android:"android"===a?{serial:r}:void 0},identifiers:s}}function eL(e){return e??"default"}function eF(e={},t={}){var a;let r,n=t.transport??$,o=async(t,a=[],r={})=>{var o,i;let s=(o=e,i=r,{...o,...i}),l=await n({session:eL(s.session),command:t,positionals:a,flags:eU({stateDir:s.stateDir,daemonBaseUrl:s.daemonBaseUrl,daemonAuthToken:s.daemonAuthToken,daemonTransport:s.daemonTransport,daemonServerMode:s.daemonServerMode,tenant:s.tenant,sessionIsolation:s.sessionIsolation,runId:s.runId,leaseId:s.leaseId,leaseBackend:s.leaseBackend,platform:s.platform,target:s.target,device:s.device,udid:s.udid,serial:s.serial,iosSimulatorDeviceSet:s.iosSimulatorDeviceSet,androidDeviceAllowlist:s.androidDeviceAllowlist,runtime:s.simulatorRuntimeId,boot:s.boot,reuseExisting:s.reuseExisting,surface:s.surface,activity:s.activity,relaunch:s.relaunch,shutdown:s.shutdown,saveScript:s.saveScript,noRecord:s.noRecord,backMode:s.backMode,metroHost:s.metroHost,metroPort:s.metroPort,bundleUrl:s.bundleUrl,launchUrl:s.launchUrl,snapshotInteractiveOnly:s.interactiveOnly,snapshotCompact:s.compact,snapshotDepth:s.depth,snapshotScope:s.scope,snapshotRaw:s.raw,screenshotFullscreen:s.screenshotFullscreen,overlayRefs:s.overlayRefs,appsFilter:s.appsFilter,out:s.out,count:s.count,fps:s.fps,hideTouches:s.hideTouches,intervalMs:s.intervalMs,delayMs:s.delayMs,holdMs:s.holdMs,jitterPx:s.jitterPx,pixels:s.pixels,doubleTap:s.doubleTap,clickButton:s.clickButton,pauseMs:s.pauseMs,pattern:s.pattern,headless:s.headless,restart:s.restart,replayUpdate:s.replayUpdate,failFast:s.failFast,timeoutMs:s.timeoutMs,retries:s.retries,artifactsDir:s.artifactsDir,reportJunit:s.reportJunit,findFirst:s.findFirst,findLast:s.findLast,networkInclude:s.networkInclude,batchOnError:s.batchOnError,batchMaxSteps:s.batchMaxSteps,batchSteps:s.batchSteps,verbose:s.debug}),runtime:s.runtime,meta:eU({requestId:s.requestId,cwd:s.cwd,debug:s.debug,lockPolicy:s.lockPolicy,lockPlatform:s.lockPlatform,tenantId:s.tenant,runId:s.runId,leaseId:s.leaseId,leaseBackend:s.leaseBackend,leaseTtlMs:s.leaseTtlMs,sessionIsolation:s.sessionIsolation,installSource:s.installSource,retainMaterializedPaths:s.retainMaterializedPaths,materializedPathRetentionMs:s.materializedPathRetentionMs,materializationId:s.materializationId})});return l.ok||function(e){throw new u(e.code,e.message,{...e.details??{},hint:e.hint,diagnosticId:e.diagnosticId,logPath:e.logPath})}(l.error),l.data??{}},i=async(e={})=>{let t=await o("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(eC)},s=async(e,t=[],a={})=>await o(e,t,a),l=(t={})=>{var a,r;return eL((a=e,r=t,{...a,...r}).session)};return{command:(a=async e=>await o(e.command,e.positionals,e.options),r=async e=>await a(e),{wait:async e=>await r(function(e){if(1!==[void 0!==e.durationMs?"durationMs":void 0,void 0!==e.text?"text":void 0,void 0!==e.ref?"ref":void 0,void 0!==e.selector?"selector":void 0].filter(Boolean).length)throw new u("INVALID_ARGS","wait command requires exactly one of durationMs, text, ref, or selector.");if(void 0!==e.durationMs)return{command:ew,positionals:[String(e.durationMs)],options:e};let t=void 0!==e.timeoutMs?[String(e.timeoutMs)]:[];if(void 0!==e.text)return{command:ew,positionals:["text",e.text,...t],options:e};if(void 0!==e.ref)return{command:ew,positionals:[e.ref,...t],options:e};let a=e.selector;return function(e){if(!g(e))throw new u("INVALID_ARGS",`Invalid wait selector: ${e}`)}(a),{command:ew,positionals:[a,...t],options:e}}(e)),alert:async(e={})=>{var t;return await r({command:"alert",positionals:[(t=e).action??"get",...void 0!==t.timeoutMs?[String(t.timeoutMs)]:[]],options:t})},appState:async(e={})=>await r({command:"appstate",positionals:[],options:e}),back:async(e={})=>await r({command:"back",positionals:[],options:{...e,backMode:e.mode}}),home:async(e={})=>await r({command:"home",positionals:[],options:e}),rotate:async e=>await r({command:"rotate",positionals:[e.orientation],options:e}),appSwitcher:async(e={})=>await r({command:"app-switcher",positionals:[],options:e}),keyboard:async(e={})=>await r({command:"keyboard",positionals:e.action?[e.action]:[],options:e}),clipboard:async e=>{var t;return await r("read"===(t=e).action?{command:ey,positionals:["read"],options:t}:{command:ey,positionals:["write",t.text],options:t})}}),devices:{list:async(e={})=>{let t=await o("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(eO)},boot:async(e={})=>await s("boot",[],e)},sessions:{list:async(e={})=>await i(e),close:async(e={})=>{let t=l(e),a=(await o("close",[],e)).shutdown;return{session:t,shutdown:"object"==typeof a&&null!==a?a:void 0,identifiers:{session:t}}}},simulators:{ensure:async e=>{let{runtime:t,...a}=e,r=await o("ensure-simulator",[],{...a,simulatorRuntimeId:t}),n=eb(r,"udid"),i=eb(r,"device");return{udid:n,device:i,runtime:eb(r,"runtime"),created:!0===r.created,booted:!0===r.booted,iosSimulatorDeviceSet:e_(r,"ios_simulator_device_set"),identifiers:{deviceId:n,deviceName:i,udid:n}}}},apps:{install:async e=>eR(await o("install",[e.app,e.appPath],e),l(e)),reinstall:async e=>eR(await o("reinstall",[e.app,e.appPath],e),l(e)),installFromSource:async e=>(function(e,t){let a=eA(e,"bundleId"),r=eA(e,"packageName"),n=a??r??eA(e,"appId"),o=eA(e,"launchTarget")??r??a??n;if(!o)throw new u("COMMAND_FAILED",'Daemon response is missing "launchTarget".',{response:e});return{appName:eA(e,"appName"),appId:n,bundleId:a,packageName:r,launchTarget:o,installablePath:eA(e,"installablePath"),archivePath:eA(e,"archivePath"),materializationId:eA(e,"materializationId"),materializationExpiresAt:eA(e,"materializationExpiresAt"),identifiers:ev({session:t,bundleId:a,packageName:r,appId:n})}})(await o("install_source",[],{...e,installSource:e.source,retainMaterializedPaths:e.retainPaths,materializedPathRetentionMs:e.retentionMs}),l(e)),list:async(e={})=>{let t=await o("apps",[],e);return Array.isArray(t.apps)?t.apps.filter(e=>"string"==typeof e):[]},open:async e=>{let t=l(e),a=e.app?e.url?[e.app,e.url]:[e.app]:[],r=await o("open",a,e),n=function(e){let t=e.platform,a=eA(e,"id"),r=eA(e,"device");if("ios"!==t&&"macos"!==t&&"android"!==t||!a||!r)return;let n=eM(e,"target"),o=eg(t,a,r);return{platform:t,target:n,id:a,name:r,identifiers:o,ios:"ios"===t?{udid:eA(e,"device_udid")??a,simulatorSetPath:e_(e,"ios_simulator_device_set")}:void 0,android:"android"===t?{serial:eA(e,"serial")??a}:void 0}}(r),i=eA(r,"appBundleId");return{session:t,appName:eA(r,"appName"),appBundleId:i,appId:i,startup:function(e){if(ex(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:eA(e,"appTarget"),appBundleId:eA(e,"appBundleId")}}(r.startup),runtime:function(e){if(!ex(e))return;let t=e.platform,a=eA(e,"metroHost"),r="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:a,metroPort:r,bundleUrl:eA(e,"bundleUrl"),launchUrl:eA(e,"launchUrl")}}(r.runtime),device:n,identifiers:{session:t,deviceId:n?.id,deviceName:n?.name,udid:n?.ios?.udid,serial:n?.android?.serial,appId:i,appBundleId:i}}},close:async(e={})=>{let t=l(e),a=(await o("close",e.app?[e.app]:[],e)).shutdown;return{session:t,closedApp:e.app,shutdown:"object"==typeof a&&null!==a?a:void 0,identifiers:{session:t}}},push:async e=>{var t;return await s("push",[e.app,"string"==typeof(t=e.payload)?t:JSON.stringify(t)],e)},triggerEvent:async e=>{var t;return await s("trigger-app-event",[(t=e).event,...t.payload?[JSON.stringify(t.payload)]:[]],e)}},materializations:{release:async e=>{var t;return{released:!0===(t=await o("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:eb(t,"materializationId"),identifiers:{}}}},leases:{allocate:async e=>eq(await o("lease_allocate",[],{...e,leaseId:void 0,leaseTtlMs:e.ttlMs})),heartbeat:async e=>eq(await o("lease_heartbeat",[],{...e,leaseTtlMs:e.ttlMs})),release:async e=>({released:!0===(await o("lease_release",[],e)).released})},metro:{prepare:async t=>await y({projectRoot:t.projectRoot??e.cwd,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.bearerToken,bridgeScope:t.bridgeScope,launchUrl:t.launchUrl,companionProfileKey:t.companionProfileKey,companionConsumerKey:t.companionConsumerKey,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(e={})=>{var t;let a=l(e),r=await o("snapshot",[],e),n=eA(r,"appBundleId"),i="object"==typeof r.visibility&&null!==r.visibility?r.visibility:void 0;return{nodes:Array.isArray(t=r.nodes)?t:[],truncated:!0===r.truncated,appName:eA(r,"appName"),appBundleId:n,...i?{visibility:i}:{},warnings:Array.isArray(r.warnings)?r.warnings.filter(e=>"string"==typeof e):void 0,identifiers:{session:a,appId:n,appBundleId:n}}},screenshot:async(e={})=>{let t=l(e),a=await o("screenshot",e.path?[e.path]:[],{...e,screenshotFullscreen:e.fullscreen});return{path:eb(a,"path"),overlayRefs:function(e){let t=e.overlayRefs;if(!Array.isArray(t))return;let a=[];for(let e of t){if(!ex(e))continue;let t=eA(e,"ref"),r=eP(e,"rect"),n=eP(e,"overlayRect"),o=function(e,t){let a=e[t];if(!ex(a))return;let r="number"==typeof a.x?a.x:void 0,n="number"==typeof a.y?a.y:void 0;if(void 0!==r&&void 0!==n)return{x:r,y:n}}(e,"center");t&&r&&n&&o&&a.push({ref:t,label:eA(e,"label"),rect:r,overlayRect:n,center:o})}return a}(a),identifiers:{session:t}}},diff:async e=>await s("diff",[e.kind],{...e,interactiveOnly:e.interactiveOnly,compact:e.compact,depth:e.depth,scope:e.scope,raw:e.raw})},interactions:{click:async e=>await s("click",ej(e),{...e,clickButton:e.button}),press:async e=>await s("press",ej(e),e),longPress:async e=>await s("longpress",[String(e.x),String(e.y),...eB(e.durationMs)],e),swipe:async e=>await s("swipe",[String(e.from.x),String(e.from.y),String(e.to.x),String(e.to.y),...eB(e.durationMs)],e),focus:async e=>await s("focus",[String(e.x),String(e.y)],e),type:async e=>await s("type",[e.text],e),fill:async e=>await s("fill",[...ej(e),e.text],e),scroll:async e=>await s("scroll",[e.direction,...eB(e.amount)],e),pinch:async e=>await s("pinch",[String(e.scale),...eB(e.x),...eB(e.y)],e),get:async e=>{var t;return await s("get",[e.format,...void 0!==(t=e).ref?[t.ref,...e$(t.label)]:[t.selector]],e)},is:async e=>await s("is",[e.predicate,e.selector,..."text"===e.predicate?[e.value]:[]],e),find:async e=>await s("find",function(e){let t=e.locator&&"any"!==e.locator?[e.locator,e.query]:[e.query];switch(e.action){case void 0:case"click":case"focus":case"exists":return e.action?[...t,e.action]:t;case"getText":return[...t,"get","text"];case"getAttrs":return[...t,"get","attrs"];case"wait":return[...t,"wait",...eB(e.timeoutMs)];case"fill":case"type":return[...t,e.action,e.value]}}(e),{...e,findFirst:e.first,findLast:e.last})},replay:{run:async e=>await s("replay",[e.path],{...e,replayUpdate:e.update}),test:async e=>await s("test",e.paths,{...e,replayUpdate:e.update})},batch:{run:async e=>await s("batch",[],{...e,batchSteps:e.steps,batchOnError:e.onError,batchMaxSteps:e.maxSteps})},observability:{perf:async(e={})=>await s("perf",[],e),logs:async(e={})=>{var t;return await s("logs",[(t=e).action??"path",...e$(t.message)],e)},network:async(e={})=>{var t;return await s("network",[...(t=e).action?[t.action]:[],...eB(t.limit)],{...e,networkInclude:e.include})}},recording:{record:async e=>await s("record",[e.action,...e$(e.path)],e),trace:async e=>await s("trace",[e.action,...e$(e.path)],e)},settings:{update:async e=>await s("settings",[e.setting,e.state,..."permission"in e?[e.permission]:[],..."mode"in e&&e.mode?[e.mode]:[]],e)}}}function ej(e){return void 0!==e.ref?[e.ref,...e$(e.label)]:void 0!==e.selector?[e.selector]:[String(e.x),String(e.y)]}function e$(e){return void 0===e?[]:[e]}function eB(e){return void 0===e?[]:[String(e)]}function eq(e){let t=e.lease;if(!t||"object"!=typeof t||Array.isArray(t))throw Error("Invalid lease response from daemon");return{leaseId:eb(t,"leaseId"),tenantId:eb(t,"tenantId"),runId:eb(t,"runId"),backend:eb(t,"backend"),createdAt:"number"==typeof t.createdAt?t.createdAt:void 0,heartbeatAt:"number"==typeof t.heartbeatAt?t.heartbeatAt:void 0,expiresAt:"number"==typeof t.expiresAt?t.expiresAt:void 0}}export{AppError}from"./152.js";export{centerOfRect}from"./57.js";export{eF as createAgentDeviceClient};
@@ -1 +1 @@
1
- import e from"node:fs";import{setTimeout as r}from"node:timers/promises";import{normalizeBaseUrl as t,METRO_COMPANION_RUN_ARG as a,ENV_LAUNCH_URL as s,ENV_BEARER_TOKEN as n,ENV_SERVER_BASE_URL as o,ENV_LOCAL_BASE_URL as i,ENV_STATE_PATH as c}from"./320.js";async function f(e){var r,a;let s=await fetch(`${t(e.serverBaseUrl)}/api/metro/companion/register`,{method:"POST",headers:(r=e.serverBaseUrl,a=e.bearerToken,{authorization:`Bearer ${a}`,"content-type":"application/json",...r.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({local_base_url:t(e.localBaseUrl),...e.launchUrl?{launch_url:e.launchUrl}:{}})}),n=await s.json();if(!s.ok||!0!==n.ok||"string"!=typeof n.data?.ws_url)throw Error(`Failed to register Metro companion: ${JSON.stringify(n)}`);return{wsUrl:n.data.ws_url}}async function d(e){return"string"==typeof e?Buffer.from(e,"utf8"):e instanceof ArrayBuffer?Buffer.from(e):ArrayBuffer.isView(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):"u">typeof Blob&&e instanceof Blob?Buffer.from(await e.arrayBuffer()):Buffer.from(String(e),"utf8")}async function l(e){return JSON.parse((await d(e.data)).toString("utf8"))}function u(e,r){1===e.readyState&&e.send(JSON.stringify(r))}async function m(e,r){1!==e.readyState&&await new Promise((t,a)=>{let s=()=>{i(),t()},n=()=>{i(),a(Error(`${r} WebSocket failed before opening.`))},o=()=>{i(),a(Error(`${r} WebSocket closed before opening.`))},i=()=>{e.removeEventListener("open",s),e.removeEventListener("error",n),e.removeEventListener("close",o)};e.addEventListener("open",s,{once:!0}),e.addEventListener("error",n,{once:!0}),e.addEventListener("close",o,{once:!0})})}async function p(e){e.readyState>=WebSocket.CLOSING||await new Promise(r=>{let t=()=>{a(),r()},a=()=>{e.removeEventListener("close",t),e.removeEventListener("error",t)};e.addEventListener("close",t,{once:!0}),e.addEventListener("error",t,{once:!0}),e.readyState>=WebSocket.CLOSING&&t()})}function y(e,r,t){try{e.close(1e3===r||r>=3e3&&r<=4999?r:3001,t)}catch{}}function g(r){return!r.statePath||e.existsSync(r.statePath)}async function w(e,r,a,s){var n,o;switch(r.type){case"ping":return void u(e,{type:"pong",timestamp:r.timestamp});case"http-request":try{let s=await fetch(new URL(r.path,`${t(a.localBaseUrl)}/`),{method:r.method,headers:r.headers,...r.bodyBase64?{body:Buffer.from(r.bodyBase64,"base64")}:{}}),n=Buffer.from(await s.arrayBuffer());u(e,{type:"http-response",requestId:r.requestId,status:s.status,headers:Object.fromEntries(s.headers.entries()),...n.length>0?{bodyBase64:n.toString("base64")}:{}})}catch(t){u(e,{type:"http-error",requestId:r.requestId,message:t instanceof Error?t.message:String(t)})}return;case"ws-open":{let o,i=new WebSocket((n=a.localBaseUrl,(o=new URL(r.path,`${t(n)}/`)).protocol="https:"===o.protocol?"wss:":"ws:",o.toString()));i.binaryType="arraybuffer";let c=!1;i.addEventListener("message",t=>{(async()=>{if(!c)return;let a=await d(t.data);u(e,{type:"ws-frame",streamId:r.streamId,dataBase64:a.toString("base64"),binary:"string"!=typeof t.data})})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),i.addEventListener("close",t=>{s.delete(r.streamId),c&&u(e,{type:"ws-close",streamId:r.streamId,code:t.code,reason:t.reason})}),i.addEventListener("error",()=>{c&&u(e,{type:"ws-close",streamId:r.streamId,code:1011,reason:"Upstream WebSocket error."})}),s.set(r.streamId,i);try{await m(i,"Upstream"),c=!0,u(e,{type:"ws-open-result",streamId:r.streamId,success:!0,headers:{}})}catch(t){s.delete(r.streamId),y(i,1011,"open failed"),u(e,{type:"ws-open-result",streamId:r.streamId,success:!1,error:t instanceof Error?t.message:String(t)})}return}case"ws-frame":{let e=s.get(r.streamId);if(!e||1!==e.readyState)return;let t=Buffer.from(r.dataBase64,"base64");e.send(r.binary?t:t.toString("utf8"));return}case"ws-close":{let e=s.get(r.streamId);if(!e)return;s.delete(r.streamId),y(e,"number"==typeof(o=r.code)&&Number.isInteger(o)&&(1e3===o||o>=3e3&&o<=4999||o>=1001&&o<=1015&&1004!==o&&1005!==o&&1006!==o)?o:1011,r.reason??"bridge requested close");return}}}async function b(e){let t=new Map,a=setInterval(()=>{g(e)||process.exit(0)},250);for(a.unref();g(e);){try{let r=await f(e),a=new WebSocket(r.wsUrl);a.binaryType="arraybuffer",await m(a,"Bridge"),a.addEventListener("message",r=>{(async()=>{let s=await l(r);await w(a,s,e,t)})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),await p(a),t.forEach(e=>y(e,1012,"bridge disconnected")),t.clear()}catch(r){if(!g(e))break;console.error(r instanceof Error?r.message:String(r))}if(!g(e))break;await r(1e3)}clearInterval(a)}(async function(e,r){let t=function(e,r){if(e[0]!==a)return null;let t=r[o]?.trim(),f=r[n]?.trim(),d=r[i]?.trim();if(!t||!f||!d)throw Error("Metro companion worker is missing required environment configuration.");return{serverBaseUrl:t,bearerToken:f,localBaseUrl:d,launchUrl:r[s]?.trim()||void 0,statePath:r[c]?.trim()||void 0}}(e,r);return!!t&&(await b(t),!0)})(process.argv.slice(2),process.env).catch(e=>{if(e instanceof Error&&e.message.includes("missing required environment")){console.error(e.message),process.exitCode=1;return}console.error(e instanceof Error?e.stack??e.message:String(e)),process.exitCode=1});
1
+ import e from"node:fs";import{setTimeout as r}from"node:timers/promises";import{normalizeBaseUrl as t,METRO_COMPANION_RUN_ARG as a,ENV_SERVER_BASE_URL as s,ENV_LOCAL_BASE_URL as o,ENV_STATE_PATH as n,ENV_LAUNCH_URL as i,ENV_SCOPE_TENANT_ID as c,ENV_SCOPE_RUN_ID as f,ENV_BEARER_TOKEN as d,ENV_SCOPE_LEASE_ID as m}from"./320.js";async function l(e){var r,a;let s=await fetch(`${t(e.serverBaseUrl)}/api/metro/companion/register`,{method:"POST",headers:(r=e.serverBaseUrl,a=e.bearerToken,{authorization:`Bearer ${a}`,"content-type":"application/json",...r.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({...e.bridgeScope,local_base_url:t(e.localBaseUrl),...e.launchUrl?{launch_url:e.launchUrl}:{}})}),o=await s.json();if(!s.ok||!0!==o.ok||"string"!=typeof o.data?.ws_url)throw Error(`Failed to register Metro companion: ${JSON.stringify(o)}`);return{wsUrl:o.data.ws_url}}async function u(e){return"string"==typeof e?Buffer.from(e,"utf8"):e instanceof ArrayBuffer?Buffer.from(e):ArrayBuffer.isView(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):"u">typeof Blob&&e instanceof Blob?Buffer.from(await e.arrayBuffer()):Buffer.from(String(e),"utf8")}async function p(e){return JSON.parse((await u(e.data)).toString("utf8"))}function y(e,r){1===e.readyState&&e.send(JSON.stringify(r))}async function g(e,r){1!==e.readyState&&await new Promise((t,a)=>{let s=()=>{i(),t()},o=()=>{i(),a(Error(`${r} WebSocket failed before opening.`))},n=()=>{i(),a(Error(`${r} WebSocket closed before opening.`))},i=()=>{e.removeEventListener("open",s),e.removeEventListener("error",o),e.removeEventListener("close",n)};e.addEventListener("open",s,{once:!0}),e.addEventListener("error",o,{once:!0}),e.addEventListener("close",n,{once:!0})})}async function w(e){e.readyState>=WebSocket.CLOSING||await new Promise(r=>{let t=()=>{a(),r()},a=()=>{e.removeEventListener("close",t),e.removeEventListener("error",t)};e.addEventListener("close",t,{once:!0}),e.addEventListener("error",t,{once:!0}),e.readyState>=WebSocket.CLOSING&&t()})}function b(e,r,t){try{e.close(1e3===r||r>=3e3&&r<=4999?r:3001,t)}catch{}}function h(r){return!r.statePath||e.existsSync(r.statePath)}async function S(e,r,a,s){var o,n;switch(r.type){case"ping":return void y(e,{type:"pong",timestamp:r.timestamp});case"http-request":try{let s=await fetch(new URL(r.path,`${t(a.localBaseUrl)}/`),{method:r.method,headers:r.headers,...r.bodyBase64?{body:Buffer.from(r.bodyBase64,"base64")}:{}}),o=Buffer.from(await s.arrayBuffer());y(e,{type:"http-response",requestId:r.requestId,status:s.status,headers:Object.fromEntries(s.headers.entries()),...o.length>0?{bodyBase64:o.toString("base64")}:{}})}catch(t){y(e,{type:"http-error",requestId:r.requestId,message:t instanceof Error?t.message:String(t)})}return;case"ws-open":{let n,i=new WebSocket((o=a.localBaseUrl,(n=new URL(r.path,`${t(o)}/`)).protocol="https:"===n.protocol?"wss:":"ws:",n.toString()));i.binaryType="arraybuffer";let c=!1;i.addEventListener("message",t=>{(async()=>{if(!c)return;let a=await u(t.data);y(e,{type:"ws-frame",streamId:r.streamId,dataBase64:a.toString("base64"),binary:"string"!=typeof t.data})})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),i.addEventListener("close",t=>{s.delete(r.streamId),c&&y(e,{type:"ws-close",streamId:r.streamId,code:t.code,reason:t.reason})}),i.addEventListener("error",()=>{c&&y(e,{type:"ws-close",streamId:r.streamId,code:1011,reason:"Upstream WebSocket error."})}),s.set(r.streamId,i);try{await g(i,"Upstream"),c=!0,y(e,{type:"ws-open-result",streamId:r.streamId,success:!0,headers:{}})}catch(t){s.delete(r.streamId),b(i,1011,"open failed"),y(e,{type:"ws-open-result",streamId:r.streamId,success:!1,error:t instanceof Error?t.message:String(t)})}return}case"ws-frame":{let e=s.get(r.streamId);if(!e||1!==e.readyState)return;let t=Buffer.from(r.dataBase64,"base64");e.send(r.binary?t:t.toString("utf8"));return}case"ws-close":{let e=s.get(r.streamId);if(!e)return;s.delete(r.streamId),b(e,"number"==typeof(n=r.code)&&Number.isInteger(n)&&(1e3===n||n>=3e3&&n<=4999||n>=1001&&n<=1015&&1004!==n&&1005!==n&&1006!==n)?n:1011,r.reason??"bridge requested close");return}}}async function v(e){let t=new Map,a=setInterval(()=>{h(e)||process.exit(0)},250);for(a.unref();h(e);){try{let r=await l(e),a=new WebSocket(r.wsUrl);a.binaryType="arraybuffer",await g(a,"Bridge"),a.addEventListener("message",r=>{(async()=>{let s=await p(r);await S(a,s,e,t)})().catch(e=>{console.error(e instanceof Error?e.message:String(e))})}),await w(a),t.forEach(e=>b(e,1012,"bridge disconnected")),t.clear()}catch(r){if(!h(e))break;console.error(r instanceof Error?r.message:String(r))}if(!h(e))break;await r(1e3)}clearInterval(a)}(async function(e,r){let t=function(e,r){if(e[0]!==a)return null;let t=r[s]?.trim(),l=r[d]?.trim(),u=r[o]?.trim();if(!t||!l||!u)throw Error("Metro companion worker is missing required environment configuration.");let p=r[c]?.trim(),y=r[f]?.trim(),g=r[m]?.trim();if(!p||!y||!g)throw Error("Metro companion worker is missing required bridge scope configuration.");return{serverBaseUrl:t,bearerToken:l,localBaseUrl:u,bridgeScope:{tenantId:p,runId:y,leaseId:g},launchUrl:r[i]?.trim()||void 0,statePath:r[n]?.trim()||void 0}}(e,r);return!!t&&(await v(t),!0)})(process.argv.slice(2),process.env).catch(e=>{if(e instanceof Error&&e.message.includes("missing required environment")){console.error(e.message),process.exitCode=1;return}console.error(e instanceof Error?e.stack??e.message:String(e)),process.exitCode=1});
@@ -11,6 +11,11 @@ export declare type EnsureMetroTunnelOptions = {
11
11
  serverBaseUrl: string;
12
12
  bearerToken: string;
13
13
  localBaseUrl: string;
14
+ bridgeScope: {
15
+ tenantId: string;
16
+ runId: string;
17
+ leaseId: string;
18
+ };
14
19
  launchUrl?: string;
15
20
  profileKey?: string;
16
21
  consumerKey?: string;
@@ -155,6 +160,11 @@ export declare type PrepareRemoteMetroOptions = {
155
160
  publicBaseUrl: string;
156
161
  proxyBaseUrl?: string;
157
162
  proxyBearerToken?: string;
163
+ bridgeScope?: {
164
+ tenantId: string;
165
+ runId: string;
166
+ leaseId: string;
167
+ };
158
168
  launchUrl?: string;
159
169
  profileKey?: string;
160
170
  consumerKey?: string;
package/dist/src/metro.js CHANGED
@@ -1 +1 @@
1
- import{URL as t}from"node:url";import"./818.js";import{AppError as e}from"./152.js";import"./164.js";import{buildMetroRuntimeHints as r,ensureMetroCompanion as o,stopMetroCompanion as n,prepareMetroRuntime as i}from"./974.js";function s(t){let e=t?.trim();return e&&e.length>0?e:void 0}function u(t){if(Number.isInteger(t)&&!(t<=0)&&!(t>65535))return t}function l(r){return function(r){if(!r)return;let o=s(r.metroHost),n=u(r.metroPort),i="http",l=s(r.bundleUrl);if(l){var a;let r;try{r=new t(l)}catch(t){throw new e("INVALID_ARGS",`Invalid runtime bundle URL: ${l}`,{},t)}("http:"===r.protocol||"https:"===r.protocol)&&(o??=s(r.hostname),n??=u(r.port.length>0?Number(r.port):"https:"===(a=r.protocol)?443:"http:"===a?80:void 0),i="https:"===r.protocol?"https":"http")}if(o&&n)return{host:o,port:n,scheme:i}}(r)}async function a(t){let e=await i({projectRoot:t.projectRoot,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.proxyBearerToken,launchUrl:t.launchUrl,companionProfileKey:t.profileKey,companionConsumerKey:t.consumerKey,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,env:t.env});return{iosRuntime:e.iosRuntime,androidRuntime:e.androidRuntime,bridge:e.bridge,started:e.started,reused:e.reused,logPath:e.logPath}}async function p(t){let e=await o(t);return{pid:e.pid,started:e.spawned,logPath:e.logPath}}async function d(t){await n(t)}function m(t){return r(t,"ios")}function c(t){return r(t,"android")}export{buildBundleUrl,normalizeBaseUrl}from"./974.js";export{c as buildAndroidRuntimeHints,m as buildIosRuntimeHints,p as ensureMetroTunnel,a as prepareRemoteMetro,l as resolveRuntimeTransport,d as stopMetroTunnel};
1
+ import{URL as t}from"node:url";import"./818.js";import{AppError as e}from"./152.js";import"./164.js";import{buildMetroRuntimeHints as r,ensureMetroCompanion as o,stopMetroCompanion as n,prepareMetroRuntime as i}from"./974.js";function s(t){let e=t?.trim();return e&&e.length>0?e:void 0}function u(t){if(Number.isInteger(t)&&!(t<=0)&&!(t>65535))return t}function l(r){return function(r){if(!r)return;let o=s(r.metroHost),n=u(r.metroPort),i="http",l=s(r.bundleUrl);if(l){var p;let r;try{r=new t(l)}catch(t){throw new e("INVALID_ARGS",`Invalid runtime bundle URL: ${l}`,{},t)}("http:"===r.protocol||"https:"===r.protocol)&&(o??=s(r.hostname),n??=u(r.port.length>0?Number(r.port):"https:"===(p=r.protocol)?443:"http:"===p?80:void 0),i="https:"===r.protocol?"https":"http")}if(o&&n)return{host:o,port:n,scheme:i}}(r)}async function p(t){let e=await i({projectRoot:t.projectRoot,kind:t.kind,publicBaseUrl:t.publicBaseUrl,proxyBaseUrl:t.proxyBaseUrl,proxyBearerToken:t.proxyBearerToken,bridgeScope:t.bridgeScope,launchUrl:t.launchUrl,companionProfileKey:t.profileKey,companionConsumerKey:t.consumerKey,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,env:t.env});return{iosRuntime:e.iosRuntime,androidRuntime:e.androidRuntime,bridge:e.bridge,started:e.started,reused:e.reused,logPath:e.logPath}}async function a(t){let e=await o(t);return{pid:e.pid,started:e.spawned,logPath:e.logPath}}async function d(t){await n(t)}function m(t){return r(t,"ios")}function c(t){return r(t,"android")}export{buildBundleUrl,normalizeBaseUrl}from"./974.js";export{c as buildAndroidRuntimeHints,m as buildIosRuntimeHints,a as ensureMetroTunnel,p as prepareRemoteMetro,l as resolveRuntimeTransport,d as stopMetroTunnel};
@@ -8,6 +8,7 @@ export declare type RemoteConfigProfile = {
8
8
  sessionIsolation?: 'none' | 'tenant';
9
9
  runId?: string;
10
10
  leaseId?: string;
11
+ leaseBackend?: 'ios-simulator' | 'ios-instance' | 'android-instance';
11
12
  platform?: 'ios' | 'macos' | 'android' | 'linux' | 'apple';
12
13
  target?: 'mobile' | 'tv' | 'desktop';
13
14
  device?: string;
@@ -1 +1 @@
1
- export{resolveRemoteConfigPath,resolveRemoteConfigProfile}from"./924.js";
1
+ import e from"node:fs";import t from"node:path";import{AppError as n}from"./152.js";import{resolveUserPath as r}from"./267.js";let o=new Set(["1","true","yes","on"]),i=new Set(["0","false","no","off"]);function a(e,t,r,o){if(e.multiple)return(Array.isArray(t)?t:[t]).map(t=>a({...e,multiple:!1},t,r,o));if("boolean"===e.type){var i=t,l=r,u=o;if("boolean"==typeof i)return i;if("string"==typeof i){let e=s(i);if(void 0!==e)return e}throw new n("INVALID_ARGS",`Invalid value for "${u}" in ${l}. Expected boolean.`)}if("booleanOrString"===e.type){if("boolean"==typeof t)return t;if("string"==typeof t&&void 0!==s(t))return s(t);if("string"==typeof t&&t.trim().length>0)return t;throw new n("INVALID_ARGS",`Invalid value for "${o}" in ${r}. Expected boolean or non-empty string.`)}if("string"===e.type){if("string"==typeof t&&t.trim().length>0)return t;throw new n("INVALID_ARGS",`Invalid value for "${o}" in ${r}. Expected non-empty string.`)}if("enum"===e.type){if(void 0!==e.setValue){var y=e,f=t,m=r,p=o;let i=y.setValue;if(f===i)return i;if("string"==typeof f){let e=f.trim();if(""===e||"true"===e||"1"===e)return i;if("false"===e||"0"===e)return}if(!0===f)return i;if(!1!==f)throw new n("INVALID_ARGS",`Invalid value for "${p}" in ${m}. Expected boolean-like value for enum flag.`);return}if("string"!=typeof t||!e.enumValues?.includes(t))throw new n("INVALID_ARGS",`Invalid value for "${o}" in ${r}. Expected one of: ${e.enumValues?.join(", ")}.`);return t}let g="number"==typeof t?t:"string"==typeof t?Number(t):NaN;if(!Number.isFinite(g)||!Number.isInteger(g))throw new n("INVALID_ARGS",`Invalid value for "${o}" in ${r}. Expected integer.`);if("number"==typeof e.min&&g<e.min)throw new n("INVALID_ARGS",`Invalid value for "${o}" in ${r}. Must be >= ${e.min}.`);if("number"==typeof e.max&&g>e.max)throw new n("INVALID_ARGS",`Invalid value for "${o}" in ${r}. Must be <= ${e.max}.`);return g}function s(e){let t=e.trim().toLowerCase();return!!o.has(t)||!i.has(t)&&void 0}let l=[{key:"stateDir",type:"string",path:!0},{key:"daemonBaseUrl",type:"string"},{key:"daemonAuthToken",type:"string"},{key:"daemonTransport",type:"enum",enumValues:["auto","socket","http"]},{key:"daemonServerMode",type:"enum",enumValues:["socket","http","dual"]},{key:"tenant",type:"string"},{key:"sessionIsolation",type:"enum",enumValues:["none","tenant"]},{key:"runId",type:"string"},{key:"leaseId",type:"string"},{key:"leaseBackend",type:"enum",enumValues:["ios-simulator","ios-instance","android-instance"]},{key:"platform",type:"enum",enumValues:["ios","macos","android","linux","apple"]},{key:"target",type:"enum",enumValues:["mobile","tv","desktop"]},{key:"device",type:"string"},{key:"udid",type:"string"},{key:"serial",type:"string"},{key:"iosSimulatorDeviceSet",type:"string",path:!0,legacyEnvNames:["IOS_SIMULATOR_DEVICE_SET"]},{key:"androidDeviceAllowlist",type:"string",legacyEnvNames:["ANDROID_DEVICE_ALLOWLIST"]},{key:"session",type:"string"},{key:"metroProjectRoot",type:"string",path:!0},{key:"metroKind",type:"enum",enumValues:["auto","react-native","expo"]},{key:"metroPublicBaseUrl",type:"string"},{key:"metroProxyBaseUrl",type:"string"},{key:"metroBearerToken",type:"string",legacyEnvNames:["AGENT_DEVICE_PROXY_TOKEN"]},{key:"metroPreparePort",type:"int",min:1,max:65535},{key:"metroListenHost",type:"string"},{key:"metroStatusHost",type:"string"},{key:"metroStartupTimeoutMs",type:"int",min:1},{key:"metroProbeTimeoutMs",type:"int",min:1},{key:"metroRuntimeFile",type:"string",path:!0},{key:"metroNoReuseExisting",type:"boolean"},{key:"metroNoInstallDeps",type:"boolean"}],u=new Map(l.map(e=>[e.key,e]));function y(e){let t=e.env??process.env;return r(e.configPath,{cwd:e.cwd,env:t})}function f(o){let i=function(o){let i,s,l=o.env??process.env,f=y(o);if(!e.existsSync(f))throw new n("INVALID_ARGS",`Remote config file not found: ${f}`);try{i=e.readFileSync(f,"utf8")}catch(e){throw new n("INVALID_ARGS",`Failed to read remote config file: ${f}`,{cause:e instanceof Error?e.message:String(e)})}try{s=JSON.parse(i)}catch(e){throw new n("INVALID_ARGS",`Invalid JSON in remote config file: ${f}`,{cause:e instanceof Error?e.message:String(e)})}if(!s||"object"!=typeof s||Array.isArray(s))throw new n("INVALID_ARGS",`Remote config file must contain a JSON object: ${f}`);let m={},p=s,g=t.dirname(f);for(let[e,t]of Object.entries(p)){let o=u.get(e);if(!o)throw new n("INVALID_ARGS",`Unsupported remote config key "${e}" in remote config file ${f}.`);let i=a(o,t,`remote config file ${f}`,e);m[o.key]="string"==typeof i&&"path"in o&&o.path?r(i,{cwd:g,env:l}):i}return{resolvedPath:f,profile:m}}(o);return{resolvedPath:i.resolvedPath,profile:function(...e){let t={};for(let n of e)if(n)for(let e of l){let r=n[e.key];void 0!==r&&(t[e.key]=r)}return t}(function(e=process.env){let t={};for(let n of l){let r=(function(e){let t=u.get(e);return[`AGENT_DEVICE_${e.replace(/([A-Z])/g,"_$1").replace(/[^A-Za-z0-9_]/g,"_").toUpperCase()}`,...t?.legacyEnvNames??[]]})(n.key).map(t=>({name:t,value:e[t]})).find(e=>"string"==typeof e.value&&e.value.trim().length>0);r&&(t[n.key]=a(n,r.value,`environment variable ${r.name}`,r.name))}return t}(o.env),i.profile)}}export{y as resolveRemoteConfigPath,f as resolveRemoteConfigProfile};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -28,6 +28,10 @@
28
28
  "import": "./dist/src/install-source.js",
29
29
  "types": "./dist/src/install-source.d.ts"
30
30
  },
31
+ "./android-apps": {
32
+ "import": "./dist/src/android-apps.js",
33
+ "types": "./dist/src/android-apps.d.ts"
34
+ },
31
35
  "./contracts": {
32
36
  "import": "./dist/src/contracts.js",
33
37
  "types": "./dist/src/contracts.d.ts"
@@ -69,5 +69,5 @@ Use this skill as a router with mandatory defaults. Read this file first. For no
69
69
  - Need logs, network, alerts, permissions, or failure triage: [references/debugging.md](references/debugging.md)
70
70
  - Need screenshots, diff, recording, replay maintenance, or perf data: [references/verification.md](references/verification.md)
71
71
  - Need desktop surfaces, menu bar behavior, or macOS-specific interaction rules: [references/macos-desktop.md](references/macos-desktop.md)
72
- - Need remote HTTP transport, `--remote-config` launches, or tenant leases on a remote macOS host: [references/remote-tenancy.md](references/remote-tenancy.md)
72
+ - Need remote HTTP transport, `connect --remote-config`, or tenant leases on a remote macOS host: [references/remote-tenancy.md](references/remote-tenancy.md)
73
73
  This includes remote React Native runs where `agent-device` now prepares Metro locally and manages the local Metro companion tunnel automatically.
@@ -2,95 +2,97 @@
2
2
 
3
3
  ## When to open this file
4
4
 
5
- Open this file for remote daemon HTTP flows, including `--remote-config` launches, that let an agent running in a Linux sandbox talk to another `agent-device` instance on a remote macOS host in order to control devices that are not available locally. This file covers daemon URL setup, authentication, lease allocation, and tenant-scoped command admission.
5
+ Open this file for remote daemon HTTP flows that let an agent running in a Linux sandbox talk to another `agent-device` instance on a remote macOS host in order to control devices that are not available locally. This file covers daemon URL setup, authentication, `connect`, tenant lease scope, and remote Metro companion lifecycle.
6
6
 
7
7
  ## Main commands to reach for first
8
8
 
9
- - `agent-device open <app> --remote-config <path> --relaunch`
10
- - `AGENT_DEVICE_DAEMON_BASE_URL=...`
9
+ - `agent-device connect --remote-config <path>`
10
+ - `agent-device connection status`
11
+ - `agent-device disconnect`
11
12
  - `AGENT_DEVICE_DAEMON_AUTH_TOKEN=...`
12
- - `agent-device --tenant ... --session-isolation tenant --run-id ... --lease-id ...`
13
13
 
14
14
  ## Most common mistake to avoid
15
15
 
16
- Do not run a tenant-isolated command without matching `tenant`, `run`, and `lease` scope. Admission checks require all three to line up.
16
+ Do not run remote tenant work by repeating `--remote-config` on every command. `--remote-config` is a `connect` input. After connecting, use normal `agent-device` commands; the active connection supplies daemon URL, tenant, run, lease, and prepared Metro runtime context.
17
17
 
18
- ## Preferred remote launch path
18
+ ## Preferred remote flow
19
19
 
20
20
  Use this when the agent needs the simplest remote control flow: a Linux sandbox agent talks over HTTP to `agent-device` on a remote macOS host and launches the target app through a checked-in `--remote-config` profile.
21
21
 
22
22
  ```bash
23
- agent-device open com.example.myapp --remote-config ./agent-device.remote.json --relaunch
23
+ export AGENT_DEVICE_DAEMON_AUTH_TOKEN="YOUR_TOKEN"
24
+ export AGENT_DEVICE_PROXY_TOKEN="$AGENT_DEVICE_DAEMON_AUTH_TOKEN"
25
+
26
+ agent-device connect \
27
+ --remote-config ./remote-config.json
28
+
29
+ agent-device install com.example.app ./app.apk
30
+ agent-device open com.example.app --relaunch
31
+ agent-device snapshot -i
32
+ agent-device fill @e3 "test@example.com"
33
+ agent-device disconnect
24
34
  ```
25
35
 
26
- - This is the preferred remote launch path for sandbox or cloud agents.
27
- - `agent-device` prepares local Metro and auto-starts the local Metro companion tunnel when the remote bridge needs a path back to the developer machine.
28
- - `close --remote-config ...` cleans up the managed companion process for that project/profile, but leaves the developer’s Metro server running.
29
- - For Android React Native relaunch flows, install or reinstall the APK first, then relaunch by installed package name.
30
- - Do not use `open <apk|aab> --relaunch`; remote runtime hints are applied through the installed app sandbox.
36
+ `connect` resolves the remote profile, verifies daemon reachability through the normal client path, allocates or refreshes the tenant lease, prepares local Metro when the profile has Metro fields, starts the local Metro companion when the bridge needs it, and writes local non-secret connection state for later commands. `disconnect` closes the session when possible, stops the Metro companion owned by that connection, releases the lease, and removes local connection state.
31
37
 
32
- ## Lease flow example
38
+ Use `agent-device connection status --session adc-android` to inspect the active connection without reading JSON state manually. Status output must not include auth tokens.
33
39
 
34
- ```bash
35
- export AGENT_DEVICE_DAEMON_BASE_URL=<trusted-daemon-base-url>
36
- export AGENT_DEVICE_DAEMON_AUTH_TOKEN=<token>
37
-
38
- agent-device \
39
- --tenant acme \
40
- --session-isolation tenant \
41
- --run-id run-123 \
42
- --lease-id <lease-id> \
43
- session list --json
44
- ```
40
+ ## Remote config shape
45
41
 
46
- Low-level lease operations exist for host-side automation, but do not point them at arbitrary hosts. The remote daemon executes device-control commands, so only use a trusted daemon base URL and an auth token managed by the same operator boundary.
42
+ Example `remote-config.json` shape:
47
43
 
48
- Lease lifecycle methods exposed by the daemon:
44
+ ```json
45
+ {
46
+ "daemonBaseUrl": "https://bridge.example.com/agent-device",
47
+ "daemonTransport": "http",
48
+ "tenant": "acme",
49
+ "runId": "run-123",
50
+ "sessionIsolation": "tenant",
51
+ "session": "adc-android",
52
+ "platform": "android",
53
+ "leaseBackend": "android-instance",
54
+ "metroProjectRoot": ".",
55
+ "metroPublicBaseUrl": "http://127.0.0.1:8081",
56
+ "metroProxyBaseUrl": "https://bridge.example.com/metro/acme/run-123"
57
+ }
58
+ ```
49
59
 
50
- - `agent_device.lease.allocate`
51
- - `agent_device.lease.heartbeat`
52
- - `agent_device.lease.release`
53
- - `agent_device.command`
60
+ - Keep secrets in env/config managed by the operator boundary. Do not persist auth tokens in connection state.
61
+ - Omit Metro fields for non-React Native flows.
62
+ - Put `tenant`, `runId`, `session`, `sessionIsolation`, `platform`, and `leaseBackend` in the remote profile when possible so agents can run `agent-device connect --remote-config ./remote-config.json` without extra scope flags.
63
+ - Explicit command-line flags override connected defaults. Use them intentionally when switching session, platform, target, tenant, run, or lease scope.
64
+ - For React Native Metro runs with `metroProxyBaseUrl`, `agent-device >= 0.11.12` can manage the local companion tunnel, but Metro itself still needs to be running locally.
65
+ - Use a lease backend that matches the bridge target platform, for example `android-instance`, `ios-instance`, or an explicit `--lease-backend` override.
54
66
 
55
67
  ## Transport prerequisites
56
68
 
57
- - Start the daemon in HTTP mode with `AGENT_DEVICE_DAEMON_SERVER_MODE=http|dual`.
58
- - Point the client at the remote host with `AGENT_DEVICE_DAEMON_BASE_URL=http(s)://host:port[/base-path]`.
69
+ - Start the daemon in HTTP mode with `AGENT_DEVICE_DAEMON_SERVER_MODE=http|dual` on the host.
70
+ - Point the profile or env at the remote host with `daemonBaseUrl` or `AGENT_DEVICE_DAEMON_BASE_URL=http(s)://host:port[/base-path]`.
59
71
  - For non-loopback remote hosts, set `AGENT_DEVICE_DAEMON_AUTH_TOKEN` or `--daemon-auth-token`. The client rejects non-loopback remote daemon URLs without auth.
60
72
  - Direct JSON-RPC callers can authenticate with request params, `Authorization: Bearer <token>`, or `x-agent-device-token`.
61
73
  - Prefer an auth hook such as `AGENT_DEVICE_HTTP_AUTH_HOOK` when the host needs caller validation or tenant injection.
62
74
 
63
- ## Lease lifecycle
64
-
65
- Use JSON-RPC methods on `POST /rpc`:
66
-
67
- - `agent_device.lease.allocate`
68
- - `agent_device.lease.heartbeat`
69
- - `agent_device.lease.release`
70
-
71
- Keep the lease alive for the duration of the run and release it when the tenant-scoped work is complete.
72
-
73
- Host-level lease knobs:
74
-
75
- - `AGENT_DEVICE_MAX_SIMULATOR_LEASES`
76
- - `AGENT_DEVICE_LEASE_TTL_MS`
77
- - `AGENT_DEVICE_LEASE_MIN_TTL_MS`
78
- - `AGENT_DEVICE_LEASE_MAX_TTL_MS`
79
-
80
- ## Command admission contract
75
+ ## Manual lease debug fallback
81
76
 
82
- For tenant-isolated command execution, pass all four CLI flags together:
77
+ The main agent flow should use `connect`. Use manual JSON-RPC only for host-side automation or daemon-side auth/scope debugging, and only against trusted daemon hosts.
83
78
 
84
79
  ```bash
85
- agent-device \
86
- --tenant acme \
87
- --session-isolation tenant \
88
- --run-id run-123 \
89
- --lease-id <lease-id> \
90
- session list --json
80
+ curl -fsS "$AGENT_DEVICE_DAEMON_BASE_URL/rpc" \
81
+ -H "Authorization: Bearer $AGENT_DEVICE_DAEMON_AUTH_TOKEN" \
82
+ -H "Content-Type: application/json" \
83
+ -d '{
84
+ "jsonrpc": "2.0",
85
+ "id": "lease-1",
86
+ "method": "agent_device.lease.allocate",
87
+ "params": {
88
+ "tenantId": "acme",
89
+ "runId": "run-123",
90
+ "backend": "android-instance"
91
+ }
92
+ }'
91
93
  ```
92
94
 
93
- The CLI sends `AGENT_DEVICE_DAEMON_AUTH_TOKEN` in both the JSON-RPC request token field and HTTP auth headers so existing daemon auth paths continue to work.
95
+ Related daemon methods are `agent_device.lease.allocate`, `agent_device.lease.heartbeat`, `agent_device.lease.release`, and `agent_device.command`.
94
96
 
95
97
  ## Failure semantics and trust notes
96
98
 
@@ -54,14 +54,16 @@ Use `diff screenshot` when comparing the current rendered screen against a saved
54
54
 
55
55
  ```bash
56
56
  agent-device diff screenshot --baseline ./baseline.png --out /tmp/diff.png
57
+ agent-device diff screenshot --baseline ./baseline.png ./current.png --out /tmp/diff.png
57
58
  agent-device diff screenshot --baseline ./baseline.png --out /tmp/diff.png --overlay-refs
58
59
  ```
59
60
 
60
61
  - Text output includes ranked changed regions with screen-space rectangles, shape, size, density, average color, and luminance. JSON also includes normalized bounds.
61
62
  - The diff PNG uses a light grayscale current-screen context with changed pixels tinted red and changed regions outlined.
63
+ - When a current image path is provided, `diff screenshot` compares the two saved files instead of capturing from the live device or requiring an active session.
62
64
  - Install `tesseract` when you want `diff screenshot` to add best-effort OCR text deltas, movement clusters, and bbox size-change hints. OCR improves the text/JSON descriptions only; it does not change the pixel comparison or the diff PNG.
63
65
  - When OCR is available, `diff screenshot` also reports best-effort non-text visual deltas by masking OCR text boxes out of the pixel diff and clustering the remaining residuals. Treat these as hints for icons, controls, and separators, not semantic icon recognition.
64
- - Add `--overlay-refs` to `diff screenshot` when you also want a separate current-screen overlay guide. The raw screenshot is still used for pixel comparison; the overlay guide is only context for non-text controls, icons, and tappable regions. When overlay refs intersect changed regions, the output lists the best current-screen ref matches under the affected region.
66
+ - Add `--overlay-refs` to `diff screenshot` when you also want a separate current-screen overlay guide for a live capture. The raw screenshot is still used for pixel comparison; the overlay guide is only context for non-text controls, icons, and tappable regions. When overlay refs intersect changed regions, the output lists the best current-screen ref matches under the affected region. Saved-image comparisons do not have live accessibility refs, so omit `--overlay-refs` when passing a current image path.
65
67
 
66
68
  ## Session recording
67
69
 
package/dist/src/924.js DELETED
@@ -1 +0,0 @@
1
- import e from"node:fs";import t from"node:path";import{AppError as r}from"./152.js";import{resolveUserPath as n}from"./267.js";let o=new Set(["1","true","yes","on"]),i=new Set(["0","false","no","off"]);function a(e,t,n,o){if(e.multiple)return(Array.isArray(t)?t:[t]).map(t=>a({...e,multiple:!1},t,n,o));if("boolean"===e.type){var i=t,l=n,m=o;if("boolean"==typeof i)return i;if("string"==typeof i){let e=s(i);if(void 0!==e)return e}throw new r("INVALID_ARGS",`Invalid value for "${m}" in ${l}. Expected boolean.`)}if("booleanOrString"===e.type){if("boolean"==typeof t)return t;if("string"==typeof t&&void 0!==s(t))return s(t);if("string"==typeof t&&t.trim().length>0)return t;throw new r("INVALID_ARGS",`Invalid value for "${o}" in ${n}. Expected boolean or non-empty string.`)}if("string"===e.type){if("string"==typeof t&&t.trim().length>0)return t;throw new r("INVALID_ARGS",`Invalid value for "${o}" in ${n}. Expected non-empty string.`)}if("enum"===e.type){if(void 0!==e.setValue){var u=e,f=t,p=n,y=o;let i=u.setValue;if(f===i)return i;if("string"==typeof f){let e=f.trim();if(""===e||"true"===e||"1"===e)return i;if("false"===e||"0"===e)return}if(!0===f)return i;if(!1!==f)throw new r("INVALID_ARGS",`Invalid value for "${y}" in ${p}. Expected boolean-like value for enum flag.`);return}if("string"!=typeof t||!e.enumValues?.includes(t))throw new r("INVALID_ARGS",`Invalid value for "${o}" in ${n}. Expected one of: ${e.enumValues?.join(", ")}.`);return t}let g="number"==typeof t?t:"string"==typeof t?Number(t):NaN;if(!Number.isFinite(g)||!Number.isInteger(g))throw new r("INVALID_ARGS",`Invalid value for "${o}" in ${n}. Expected integer.`);if("number"==typeof e.min&&g<e.min)throw new r("INVALID_ARGS",`Invalid value for "${o}" in ${n}. Must be >= ${e.min}.`);if("number"==typeof e.max&&g>e.max)throw new r("INVALID_ARGS",`Invalid value for "${o}" in ${n}. Must be <= ${e.max}.`);return g}function s(e){let t=e.trim().toLowerCase();return!!o.has(t)||!i.has(t)&&void 0}let l=[{key:"stateDir",type:"string",path:!0},{key:"daemonBaseUrl",type:"string"},{key:"daemonAuthToken",type:"string"},{key:"daemonTransport",type:"enum",enumValues:["auto","socket","http"]},{key:"daemonServerMode",type:"enum",enumValues:["socket","http","dual"]},{key:"tenant",type:"string"},{key:"sessionIsolation",type:"enum",enumValues:["none","tenant"]},{key:"runId",type:"string"},{key:"leaseId",type:"string"},{key:"platform",type:"enum",enumValues:["ios","macos","android","linux","apple"]},{key:"target",type:"enum",enumValues:["mobile","tv","desktop"]},{key:"device",type:"string"},{key:"udid",type:"string"},{key:"serial",type:"string"},{key:"iosSimulatorDeviceSet",type:"string",path:!0,legacyEnvNames:["IOS_SIMULATOR_DEVICE_SET"]},{key:"androidDeviceAllowlist",type:"string",legacyEnvNames:["ANDROID_DEVICE_ALLOWLIST"]},{key:"session",type:"string"},{key:"metroProjectRoot",type:"string",path:!0},{key:"metroKind",type:"enum",enumValues:["auto","react-native","expo"]},{key:"metroPublicBaseUrl",type:"string"},{key:"metroProxyBaseUrl",type:"string"},{key:"metroBearerToken",type:"string",legacyEnvNames:["AGENT_DEVICE_PROXY_TOKEN"]},{key:"metroPreparePort",type:"int",min:1,max:65535},{key:"metroListenHost",type:"string"},{key:"metroStatusHost",type:"string"},{key:"metroStartupTimeoutMs",type:"int",min:1},{key:"metroProbeTimeoutMs",type:"int",min:1},{key:"metroRuntimeFile",type:"string",path:!0},{key:"metroNoReuseExisting",type:"boolean"},{key:"metroNoInstallDeps",type:"boolean"}],m=["session","platform","daemonBaseUrl","daemonAuthToken","daemonTransport","metroProjectRoot","metroKind","metroPublicBaseUrl","metroProxyBaseUrl","metroBearerToken","metroPreparePort","metroListenHost","metroStatusHost","metroStartupTimeoutMs","metroProbeTimeoutMs","metroRuntimeFile","metroNoReuseExisting","metroNoInstallDeps"],u=new Map(l.map(e=>[e.key,e]));function f(e){let t=e.env??process.env;return n(e.configPath,{cwd:e.cwd,env:t})}function p(o){let i=function(o){let i,s,l=o.env??process.env,m=f(o);if(!e.existsSync(m))throw new r("INVALID_ARGS",`Remote config file not found: ${m}`);try{i=e.readFileSync(m,"utf8")}catch(e){throw new r("INVALID_ARGS",`Failed to read remote config file: ${m}`,{cause:e instanceof Error?e.message:String(e)})}try{s=JSON.parse(i)}catch(e){throw new r("INVALID_ARGS",`Invalid JSON in remote config file: ${m}`,{cause:e instanceof Error?e.message:String(e)})}if(!s||"object"!=typeof s||Array.isArray(s))throw new r("INVALID_ARGS",`Remote config file must contain a JSON object: ${m}`);let p={},y=s,g=t.dirname(m);for(let[e,t]of Object.entries(y)){let o=u.get(e);if(!o)throw new r("INVALID_ARGS",`Unsupported remote config key "${e}" in remote config file ${m}.`);let i=a(o,t,`remote config file ${m}`,e);p[o.key]="string"==typeof i&&"path"in o&&o.path?n(i,{cwd:g,env:l}):i}return{resolvedPath:m,profile:p}}(o);return{resolvedPath:i.resolvedPath,profile:function(...e){let t={};for(let r of e)if(r)for(let e of l){let n=r[e.key];void 0!==n&&(t[e.key]=n)}return t}(function(e=process.env){let t={};for(let r of l){let n=(function(e){let t=u.get(e);return[`AGENT_DEVICE_${e.replace(/([A-Z])/g,"_$1").replace(/[^A-Za-z0-9_]/g,"_").toUpperCase()}`,...t?.legacyEnvNames??[]]})(r.key).map(t=>({name:t,value:e[t]})).find(e=>"string"==typeof e.value&&e.value.trim().length>0);n&&(t[r.key]=a(r,n.value,`environment variable ${n.name}`,n.name))}return t}(o.env),i.profile)}}export{l as REMOTE_CONFIG_FIELD_SPECS,m as REMOTE_OPEN_PROFILE_KEYS,f as resolveRemoteConfigPath,p as resolveRemoteConfigProfile};