agent-device
Advanced tools
| import type { DaemonInstallSource, DaemonRequest } from './types.ts'; | ||
| export declare function resolveInstallSource(req: DaemonRequest): { | ||
| source: DaemonInstallSource; | ||
| cleanup: () => void; | ||
| }; |
+2
-2
@@ -1,2 +0,2 @@ | ||
| import{createRequestId as e,node_path as t,isAgentDeviceDaemonProcess as r,runCmdDetached as a,readVersion as n,findProjectRoot as o,node_https as i,runCmdSync as s,withDiagnosticTimer as d,resolveDaemonTransportPreference as l,emitDiagnostic as c,spawn as u,AppError as m,node_fs as p,resolveDaemonPaths as h,node_net as f,resolveDaemonServerMode as I,stopProcessForTakeover as w,node_http as A}from"./331.js";async function g(e){let{localPath:r,baseUrl:a,token:n}=e,o=p.statSync(r).isDirectory(),s=t.basename(r),d=new URL("upload",a.endsWith("/")?a:`${a}/`),l="https:"===d.protocol?i:A,c={"x-artifact-type":o?"app-bundle":"file","x-artifact-filename":s,"transfer-encoding":"chunked"};return n&&(c.authorization=`Bearer ${n}`,c["x-agent-device-token"]=n),new Promise((e,a)=>{let n=l.request({protocol:d.protocol,host:d.hostname,port:d.port,method:"POST",path:d.pathname+d.search,headers:c},t=>{let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{clearTimeout(i);try{let t=JSON.parse(r);if(!t.ok||!t.uploadId)return void a(new m("COMMAND_FAILED",`Upload failed: ${r}`));e(t.uploadId)}catch{a(new m("COMMAND_FAILED",`Invalid upload response: ${r}`))}})}),i=setTimeout(()=>{n.destroy(),a(new m("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(n.on("error",e=>{clearTimeout(i),a(new m("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),o){let e=u("tar",["cf","-","-C",t.dirname(r),t.basename(r)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(n.destroy(),a(new m("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=p.createReadStream(r);e.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}let D=function(e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if(!e)return 9e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):9e4}(),v=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}(),y=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}(),E=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"];async function P(t){let r=t.meta?.requestId??e(),a=!!(t.meta?.debug||t.flags?.verbose),n=function(e){let t=e.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,r=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new m("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new m("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(e.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),a=e.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN,n=e.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,o=l(n);if(r&&"socket"===o)throw new m("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:r});let i=I(e.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===n?"dual":void 0));return{paths:h(t),transportPreference:o,serverMode:i,remoteBaseUrl:r,remoteAuthToken:a}}(t),o=await d("daemon_startup",async()=>await T(n),{requestId:r,session:t.session}),i=await _(t,o),s={...t,positionals:i.positionals,flags:i.flags,token:o.token,meta:{requestId:r,debug:a,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,...i.uploadedArtifactId?{uploadedArtifactId:i.uploadedArtifactId}:{},...i.clientArtifactPaths?{clientArtifactPaths:i.clientArtifactPaths}:{}}};return c({level:"info",phase:"daemon_request_prepare",data:{requestId:r,command:t.command,session:t.session}}),await d("daemon_request",async()=>await B(o,s,n.transportPreference),{requestId:r,command:t.command})}async function _(e,r){let a=[...e.positionals??[]],n=e.flags?{...e.flags}:void 0,o={};if(K(r)){let r=function(e,r){if("screenshot"===e.command){let t=M(e,"path",".png");return r[0]?{field:"path",localPath:t,positionalIndex:0,positionalPath:b("screenshot",".png")}:{field:"path",localPath:t,positionalIndex:0,flagPath:b("screenshot",".png")}}if("record"===e.command&&"start"===(r[0]??"").toLowerCase()){let r=M(e,"outPath",".mp4",1);return{field:"outPath",localPath:r,positionalIndex:1,positionalPath:b("recording",t.extname(r)||".mp4")}}return null}(e,a);r&&(void 0!==r.positionalPath&&(a[r.positionalIndex]=r.positionalPath),void 0!==r.flagPath&&((n??={}).out=r.flagPath),o[r.field]=r.localPath)}if(!K(r)||"install"!==e.command&&"reinstall"!==e.command||a.length<2)return{positionals:a,flags:n,...Object.keys(o).length>0?{clientArtifactPaths:o}:{}};let i=a[1];if(i.startsWith("remote:"))return a[1]=i.slice(7),{positionals:a,flags:n,...Object.keys(o).length>0?{clientArtifactPaths:o}:{}};let s=t.isAbsolute(i)?i:t.resolve(e.meta?.cwd??process.cwd(),i);return p.existsSync(s)?{positionals:a,flags:n,uploadedArtifactId:await g({localPath:s,baseUrl:r.baseUrl,token:r.token}),...Object.keys(o).length>0?{clientArtifactPaths:o}:{}}:{positionals:a,flags:n,...Object.keys(o).length>0?{clientArtifactPaths:o}:{}}}function M(e,r,a,n=0){let o=e.positionals?.[n]??e.flags?.out,i=`${"path"===r?"screenshot":"recording"}-${Date.now()}${a}`,s=o&&o.trim().length>0?o:i;return t.isAbsolute(s)?s:t.resolve(e.meta?.cwd??process.cwd(),s)}function b(e,r){let a=r.startsWith(".")?r:`.${r}`;return t.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${a}`)}async function T(e){let a;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await F(t,"http"))return t;throw new m("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 i=R(e.paths.infoPath),s=n(),d=function(e,r=o()){try{let a=p.statSync(e),n=t.relative(r,e)||e;return`${n}:${a.size}:${Math.trunc(a.mtimeMs)}`}catch{return"unknown"}}((a=$()).useSrc?a.srcPath:a.distPath,a.root),l=!!i&&await F(i,e.transportPreference);if(i&&i.version===s&&i.codeSignature===d&&l)return i;i&&(i.version!==s||i.codeSignature!==d||!l)&&(await O(i),q(e.paths.infoPath)),function(e){let t=U(e);if(!t.hasLock||t.hasInfo)return;let a=C(e.lockPath);if(!a)return q(e.lockPath);r(a.pid,a.processStartTime)||q(e.lockPath)}(e.paths);let c=0;for(let t=1;t<=y;t+=1){await x(e);let r=await N(v,e);if(r)return r;if(await S(e.paths)){c+=1;continue}let a=U(e.paths);if(!(t<y))break;if(!a.hasInfo&&!a.hasLock){await k(150);continue}}let u=U(e.paths);throw new m("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:v,startupAttempts:y,lockRecoveryCount:c,metadataState:u,hint:function(e,t=h(process.env.AGENT_DEVICE_STATE_DIR)){return e.hasLock&&!e.hasInfo?`Detected ${t.lockPath} without ${t.infoPath}. If no agent-device daemon process is running, delete ${t.lockPath} and retry.`:e.hasLock&&e.hasInfo?`Daemon metadata may be stale. If no agent-device daemon process is running, delete ${t.infoPath} and ${t.lockPath}, then retry.`:`Daemon metadata is missing or stale. Delete ${t.infoPath} if present and retry.`}(u,e.paths)})}async function N(e,t){let r=Date.now();for(;Date.now()-r<e;){let e=R(t.paths.infoPath);if(e&&await F(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function k(e){await new Promise(t=>setTimeout(t,e))}async function S(e){let t=U(e);if(!t.hasLock||t.hasInfo)return!1;let a=C(e.lockPath);return a&&r(a.pid,a.processStartTime)&&await w(a.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:a.processStartTime}),q(e.lockPath),!0}async function O(e){await w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function R(e){let t=L(e);if(!t||"string"!=typeof t.token||0===t.token.length)return null;let r=Number.isInteger(t.port)&&Number(t.port)>0,a=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;return r||a?{...t,port:r?Number(t.port):void 0,httpPort:a?Number(t.httpPort):void 0,pid:Number.isInteger(t.pid)&&t.pid>0?t.pid:0}:null}function C(e){let t=L(e);return t&&Number.isInteger(t.pid)&&!(t.pid<=0)?t:null}function U(e){return{hasInfo:p.existsSync(e.infoPath),hasLock:p.existsSync(e.lockPath)}}function L(e){if(!p.existsSync(e))return null;try{return JSON.parse(p.readFileSync(e,"utf8"))}catch{return null}}function q(e){try{p.existsSync(e)&&p.unlinkSync(e)}catch{}}async function F(e,t){var r;return"http"===z(e,t)?await function(e){let t=e.baseUrl?J(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!t)return Promise.resolve(!1);let r=new URL(t),a="https:"===r.protocol?i:A,n=e.baseUrl?3e3:500;return new Promise(e=>{let t=a.request({protocol:r.protocol,host:r.hostname,port:r.port,path:r.pathname+r.search,method:"GET",timeout:n},t=>{t.resume(),e((t.statusCode??500)<500)});t.on("timeout",()=>{t.destroy(),e(!1)}),t.on("error",()=>{e(!1)}),t.end()})}(e):await ((r=e.port)?new Promise(e=>{let t=f.createConnection({host:"127.0.0.1",port:r},()=>{t.destroy(),e(!0)});t.on("error",()=>{e(!1)})}):Promise.resolve(!1))}async function x(e){let t=$(),r=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],n={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};a(process.execPath,r,{env:n})}function $(){let e=o(),r=t.join(e,"dist","src","daemon.js"),a=t.join(e,"src","daemon.ts"),n=p.existsSync(r),i=p.existsSync(a);if(!n&&!i)throw new m("COMMAND_FAILED","Daemon entry not found",{distPath:r,srcPath:a});return{root:e,distPath:r,srcPath:a,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!n&&i}}async function B(e,t,r){return"http"===z(e,r)?await V(e,t):await G(e,t)}function z(e,t){if(e.baseUrl){if("socket"===t)throw new m("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t){if(!e.httpPort)throw new m("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");return"http"}if("socket"===t){if(!e.port)throw new m("COMMAND_FAILED","Daemon socket endpoint is unavailable");return"socket"}let r=e.transport;if("http"===r&&e.httpPort)return"http";if(("socket"===r||"dual"===r)&&e.port)return"socket";if(e.httpPort)return"http";if(e.port)return"socket";throw new m("COMMAND_FAILED","Daemon metadata has no reachable transport")}async function G(e,t){let r=e.port;if(!r)throw new m("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((a,n)=>{let o=f.createConnection({host:"127.0.0.1",port:r},()=>{o.write(`${JSON.stringify(t)} | ||
| `)}),i=setTimeout(()=>{o.destroy();let r=j(),a=H(e,h(t.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR));c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:D,requestId:t.meta?.requestId,command:t.command,timedOutRunnerPidsTerminated:r.terminated,timedOutRunnerCleanupError:r.error,daemonPidReset:e.pid,daemonPidForceKilled:a.forcedKill}}),n(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:D,requestId:t.meta?.requestId,hint:"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},D),s="";o.setEncoding("utf8"),o.on("data",e=>{let r=(s+=e).indexOf("\n");if(-1===r)return;let d=s.slice(0,r).trim();if(d)try{let e=JSON.parse(d);o.end(),clearTimeout(i),a(e)}catch(e){clearTimeout(i),n(new m("COMMAND_FAILED","Invalid daemon response",{requestId:t.meta?.requestId,line:d},e instanceof Error?e:void 0))}}),o.on("error",e=>{clearTimeout(i),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:t.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),n(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:t.meta?.requestId,hint:"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e))})})}async function V(t,r){let a=t.baseUrl?new URL(J(t.baseUrl,"rpc")):t.httpPort?new URL(`http://127.0.0.1:${t.httpPort}/rpc`):null;if(!a)throw new m("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let n=JSON.stringify({jsonrpc:"2.0",id:r.meta?.requestId??e(),method:"agent_device.command",params:r}),o={"content-type":"application/json","content-length":Buffer.byteLength(n)};return t.baseUrl&&t.token&&(o.authorization=`Bearer ${t.token}`,o["x-agent-device-token"]=t.token),await new Promise((e,s)=>{let d=h(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),l=("https:"===a.protocol?i:A).request({protocol:a.protocol,host:a.hostname,port:a.port,method:"POST",path:a.pathname+a.search,headers:o},a=>{let n="";a.setEncoding("utf8"),a.on("data",e=>{n+=e}),a.on("end",()=>{clearTimeout(u);try{let a=JSON.parse(n);if(a.error){let e=a.error.data??{};s(new m(String(e.code??"COMMAND_FAILED"),String(e.message??a.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(!a.result||"object"!=typeof a.result)return void s(new m("COMMAND_FAILED","Invalid daemon RPC response",{requestId:r.meta?.requestId}));if(t.baseUrl&&a.result.ok)return void W(t,r,a.result).then(e).catch(s);e(a.result)}catch(e){clearTimeout(u),s(new m("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:n},e instanceof Error?e:void 0))}})}),u=setTimeout(()=>{l.destroy();let e=K(t)?{terminated:0}:j(),a=K(t)?{forcedKill:!1}:H(t,d);c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:D,requestId:r.meta?.requestId,command:r.command,timedOutRunnerPidsTerminated:e.terminated,timedOutRunnerCleanupError:e.error,daemonPidReset:K(t)?void 0:t.pid,daemonPidForceKilled:K(t)?void 0:a.forcedKill,daemonBaseUrl:t.baseUrl}}),s(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:D,requestId:r.meta?.requestId,hint:K(t)?"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."}))},D);l.on("error",e=>{clearTimeout(u),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:r.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),s(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:r.meta?.requestId,hint:K(t)?"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))}),l.write(n),l.end()})}function j(){let e=0;try{for(let t of E){let r=s("pkill",["-f",t],{allowFailure:!0});0===r.exitCode&&(e+=1)}return{terminated:e}}catch(t){return{terminated:e,error:t instanceof Error?t.message:String(t)}}}function H(e,t){let a=!1;try{r(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),a=!0)}catch{w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{q(t.infoPath),q(t.lockPath)}return{forcedKill:a}}function K(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function J(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function W(e,r,a){let n=Array.isArray(a.data?.artifacts)?a.data.artifacts:[];if(0===n.length||!e.baseUrl)return a;let o=a.data?{...a.data}:{},i=[];for(let a of n){if(!a||"object"!=typeof a||"string"!=typeof a.artifactId){i.push(a);continue}let n=function(e,r){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let a=e.fileName?.trim()||`${e.field}-${Date.now()}`;return t.resolve(r.meta?.cwd??process.cwd(),a)}(a,r);await Q({baseUrl:e.baseUrl,token:e.token,artifactId:a.artifactId,destinationPath:n,requestId:r.meta?.requestId}),o[a.field]=n,i.push({...a,localPath:n})}return o.artifacts=i,{ok:!0,data:o}}async function Q(e){var r,a;let n,o=new URL((r=e.baseUrl,a=e.artifactId,n=r.endsWith("/")?r:`${r}/`,new URL(`upload/${encodeURIComponent(a)}`,n).toString())),s="https:"===o.protocol?i:A;await p.promises.mkdir(t.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,r)=>{let a=!1,n=e.timeoutMs??D,i=n=>{if(!a){if(a=!0,clearTimeout(l),n)return void p.promises.rm(e.destinationPath,{force:!0}).finally(()=>r(n));t()}},d=s.request({protocol:o.protocol,host:o.hostname,port:o.port,method:"GET",path:o.pathname+o.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:r}))});return}let r=p.createWriteStream(e.destinationPath);r.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("aborted",()=>{i(new m("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),r.on("finish",()=>{r.close(()=>i())}),t.pipe(r)}),l=setTimeout(()=>{d.destroy(new m("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n}))},n);d.on("error",t=>{t instanceof m?i(t):i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n},t instanceof Error?t:void 0))}),d.end()})}function X(e,t){let r=es(e,"bundleId"),a=es(e,"package"),n=r??a;return{app:ei(e,"app"),appPath:ei(e,"appPath"),platform:el(e,"platform"),appId:n,bundleId:r,package:a,identifiers:{session:t,appId:n,appBundleId:r,package:a}}}function Y(e,t){return{session:t,configured:!0===e.configured,cleared:!0===e.cleared||void 0,runtime:et(e.runtime),identifiers:{session:t}}}function Z(e){let t=en(e),r=el(t,"platform"),a=ei(t,"id");return{platform:r,target:ec(t,"target"),kind:function(e,t){let r=e[t];if("simulator"===r||"emulator"===r||"device"===r)return r;throw new m("COMMAND_FAILED",`Daemon response has invalid "${t}".`,{response:e})}(t,"kind"),id:a,name:ei(t,"name"),booted:"boolean"==typeof t.booted?t.booted:void 0,identifiers:{deviceId:a,deviceName:ei(t,"name"),..."ios"===r?{udid:a}:{serial:a}},ios:"ios"===r?{udid:a}:void 0,android:"android"===r?{serial:a}:void 0}}function ee(e){let t=en(e),r=el(t,"platform"),a=ei(t,"id"),n=ei(t,"name"),o=ec(t,"target"),i=ei(t,"device"),s={session:n,deviceId:a,deviceName:i,..."ios"===r?{udid:a}:{serial:a}};return{name:n,createdAt:function(e,t){let r=e[t];if("number"!=typeof r||!Number.isFinite(r))throw new m("COMMAND_FAILED",`Daemon response is missing numeric "${t}".`,{response:e});return r}(t,"createdAt"),device:{platform:r,target:o,id:a,name:i,identifiers:s,ios:"ios"===r?{udid:a,simulatorSetPath:ed(t,"ios_simulator_device_set")}:void 0,android:"android"===r?{serial:a}:void 0},identifiers:s}}function et(e){if(!eo(e))return;let t=e.platform,r=es(e,"metroHost"),a="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:r,metroPort:a,bundleUrl:es(e,"bundleUrl"),launchUrl:es(e,"launchUrl")}}function er(e,t){return t??e??"default"}function ea(e){let t={};for(let[r,a]of Object.entries(e))void 0!==a&&(t[r]=a);return t}function en(e){if(!eo(e))throw new m("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function eo(e){return"object"==typeof e&&null!==e}function ei(e,t){let r=e[t];if("string"!=typeof r||0===r.length)throw new m("COMMAND_FAILED",`Daemon response is missing "${t}".`,{response:e});return r}function es(e,t){let r=e[t];return"string"==typeof r&&r.length>0?r:void 0}function ed(e,t){let r=e[t];return null===r?null:"string"==typeof r&&r.length>0?r:void 0}function el(e,t){let r=e[t];if("ios"===r||"android"===r)return r;throw new m("COMMAND_FAILED",`Daemon response has invalid "${t}".`,{response:e})}function ec(e,t){return"tv"===e[t]?"tv":"mobile"}function eu(e={},t={}){let r=t.transport??P,a=async(t,a=[],n={})=>{let o={...e,...n},i=await r({session:er(e.session,n.session),command:t,positionals:a,flags:ea({stateDir:o.stateDir,daemonBaseUrl:o.daemonBaseUrl,daemonAuthToken:o.daemonAuthToken,daemonTransport:o.daemonTransport,daemonServerMode:o.daemonServerMode,tenant:o.tenant,sessionIsolation:o.sessionIsolation,runId:o.runId,leaseId:o.leaseId,platform:o.platform,target:o.target,device:o.device,udid:o.udid,serial:o.serial,iosSimulatorDeviceSet:o.iosSimulatorDeviceSet,androidDeviceAllowlist:o.androidDeviceAllowlist,runtime:o.simulatorRuntimeId,boot:o.boot,reuseExisting:o.reuseExisting,activity:o.activity,relaunch:o.relaunch,shutdown:o.shutdown,saveScript:o.saveScript,noRecord:o.noRecord,metroHost:o.metroHost,metroPort:o.metroPort,bundleUrl:o.bundleUrl,launchUrl:o.launchUrl,snapshotInteractiveOnly:o.interactiveOnly,snapshotCompact:o.compact,snapshotDepth:o.depth,snapshotScope:o.scope,snapshotRaw:o.raw,verbose:o.debug}),runtime:o.runtime,meta:ea({requestId:o.requestId,cwd:o.cwd,debug:o.debug,lockPolicy:o.lockPolicy,lockPlatform:o.lockPlatform,tenantId:o.tenant,runId:o.runId,leaseId:o.leaseId,sessionIsolation:o.sessionIsolation,installSource:o.installSource,retainMaterializedPaths:o.retainMaterializedPaths,materializedPathRetentionMs:o.materializedPathRetentionMs,materializationId:o.materializationId})});if(!i.ok)throw new m(i.error.code,i.error.message,{...i.error.details??{},hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath});return i.data??{}},n=async(e={})=>{let t=await a("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(ee)};return{devices:{list:async(e={})=>{let t=await a("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(Z)}},sessions:{list:async(e={})=>await n(e),close:async(t={})=>{let r=er(e.session,t.session),n=(await a("close",[],t)).shutdown;return{session:r,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},simulators:{ensure:async e=>{let{runtime:t,...r}=e,n=await a("ensure-simulator",[],{...r,simulatorRuntimeId:t}),o=ei(n,"udid"),i=ei(n,"device");return{udid:o,device:i,runtime:ei(n,"runtime"),created:!0===n.created,booted:!0===n.booted,iosSimulatorDeviceSet:ed(n,"ios_simulator_device_set"),identifiers:{deviceId:o,deviceName:i,udid:o}}}},apps:{install:async t=>X(await a("install",[t.app,t.appPath],t),er(e.session,t.session)),reinstall:async t=>X(await a("reinstall",[t.app,t.appPath],t),er(e.session,t.session)),installFromSource:async t=>{var r,n;let o,i,s;return r=await a("install_source",[],{...t,installSource:t.source,retainMaterializedPaths:t.retainPaths,materializedPathRetentionMs:t.retentionMs}),n=er(e.session,t.session),o=es(r,"bundleId"),i=es(r,"packageName"),s=o??i,{appName:es(r,"appName"),appId:s,bundleId:o,packageName:i,launchTarget:ei(r,"launchTarget"),installablePath:es(r,"installablePath"),archivePath:es(r,"archivePath"),materializationId:es(r,"materializationId"),materializationExpiresAt:es(r,"materializationExpiresAt"),identifiers:{session:n,appId:s,appBundleId:o,package:i}}},open:async t=>{let r=er(e.session,t.session),n=t.url?[t.app,t.url]:[t.app],o=await a("open",n,t),i=function(e){let t=e.platform,r=es(e,"id"),a=es(e,"device");if("ios"!==t&&"android"!==t||!r||!a)return;let n=ec(e,"target"),o={deviceId:r,deviceName:a,..."ios"===t?{udid:r}:{serial:r}};return{platform:t,target:n,id:r,name:a,identifiers:o,ios:"ios"===t?{udid:es(e,"device_udid")??r,simulatorSetPath:ed(e,"ios_simulator_device_set")}:void 0,android:"android"===t?{serial:es(e,"serial")??r}:void 0}}(o),s=es(o,"appBundleId");return{session:r,appName:es(o,"appName"),appBundleId:s,appId:s,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:es(e,"appTarget"),appBundleId:es(e,"appBundleId")}}(o.startup),runtime:et(o.runtime),device:i,identifiers:{session:r,deviceId:i?.id,deviceName:i?.name,udid:i?.ios?.udid,serial:i?.android?.serial,appId:s,appBundleId:s}}},close:async(t={})=>{let r=er(e.session,t.session),n=(await a("close",t.app?[t.app]:[],t)).shutdown;return{session:r,closedApp:t.app,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},materializations:{release:async e=>{var t;return{released:!0===(t=await a("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:ei(t,"materializationId"),identifiers:{}}}},runtime:{set:async t=>Y(await a("runtime",["set"],t),er(e.session,t.session)),show:async(t={})=>Y(await a("runtime",["show"],t),er(e.session,t.session))},capture:{snapshot:async(t={})=>{var r;let n=er(e.session,t.session),o=await a("snapshot",[],t),i=es(o,"appBundleId");return{nodes:Array.isArray(r=o.nodes)?r:[],truncated:!0===o.truncated,appName:es(o,"appName"),appBundleId:i,identifiers:{session:n,appId:i,appBundleId:i}}},screenshot:async(t={})=>{let r=er(e.session,t.session);return{path:ei(await a("screenshot",t.path?[t.path]:[],t),"path"),identifiers:{session:r}}}}}}export{eu as createAgentDeviceClient,P as sendToDaemon}; | ||
| import{createRequestId as e,node_path as t,isAgentDeviceDaemonProcess as r,runCmdDetached as a,readVersion as n,findProjectRoot as o,node_https as i,runCmdSync as s,withDiagnosticTimer as d,resolveDaemonTransportPreference as l,emitDiagnostic as c,spawn as u,AppError as m,node_fs as p,resolveDaemonPaths as h,node_net as f,resolveDaemonServerMode as I,stopProcessForTakeover as w,node_http as A}from"./331.js";async function g(e){let{localPath:r,baseUrl:a,token:n}=e,o=p.statSync(r).isDirectory(),s=t.basename(r),d=new URL("upload",a.endsWith("/")?a:`${a}/`),l="https:"===d.protocol?i:A,c={"x-artifact-type":o?"app-bundle":"file","x-artifact-filename":s,"transfer-encoding":"chunked"};return n&&(c.authorization=`Bearer ${n}`,c["x-agent-device-token"]=n),new Promise((e,a)=>{let n=l.request({protocol:d.protocol,host:d.hostname,port:d.port,method:"POST",path:d.pathname+d.search,headers:c},t=>{let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{clearTimeout(i);try{let t=JSON.parse(r);if(!t.ok||!t.uploadId)return void a(new m("COMMAND_FAILED",`Upload failed: ${r}`));e(t.uploadId)}catch{a(new m("COMMAND_FAILED",`Invalid upload response: ${r}`))}})}),i=setTimeout(()=>{n.destroy(),a(new m("COMMAND_FAILED","Artifact upload timed out",{timeoutMs:3e5,hint:"The upload to the remote daemon exceeded the 5-minute timeout."}))},3e5);if(n.on("error",e=>{clearTimeout(i),a(new m("COMMAND_FAILED","Failed to upload artifact to remote daemon",{hint:"Verify the remote daemon is reachable and supports artifact uploads."},e))}),o){let e=u("tar",["cf","-","-C",t.dirname(r),t.basename(r)],{stdio:["ignore","pipe","pipe"]});e.stdout.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to create tar archive for app bundle",{},e))}),e.on("close",e=>{0!==e&&(n.destroy(),a(new m("COMMAND_FAILED",`tar failed with exit code ${e}`)))})}else{let e=p.createReadStream(r);e.pipe(n),e.on("error",e=>{n.destroy(),a(new m("COMMAND_FAILED","Failed to read local artifact",{},e))})}})}let v=function(e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS){if(!e)return 9e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):9e4}(),D=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}(),y=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}(),E=["xcodebuild .*AgentDeviceRunnerUITests/RunnerTests/testCommand","xcodebuild .*AgentDeviceRunner\\.env\\.session-","xcodebuild build-for-testing .*ios-runner/AgentDeviceRunner/AgentDeviceRunner\\.xcodeproj"];async function P(t){let r=t.meta?.requestId??e(),a=!!(t.meta?.debug||t.flags?.verbose),n=function(e){let t=e.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR,r=function(e){let t;if(e){try{t=new URL(e)}catch(t){throw new m("INVALID_ARGS","Invalid daemon base URL",{daemonBaseUrl:e},t instanceof Error?t:void 0)}if("http:"!==t.protocol&&"https:"!==t.protocol)throw new m("INVALID_ARGS","Daemon base URL must use http or https",{daemonBaseUrl:e});return t.toString().replace(/\/+$/,"")}}(e.flags?.daemonBaseUrl??process.env.AGENT_DEVICE_DAEMON_BASE_URL),a=e.flags?.daemonAuthToken??process.env.AGENT_DEVICE_DAEMON_AUTH_TOKEN,n=e.flags?.daemonTransport??process.env.AGENT_DEVICE_DAEMON_TRANSPORT,o=l(n);if(r&&"socket"===o)throw new m("INVALID_ARGS","Remote daemon base URL only supports HTTP transport. Remove --daemon-transport socket.",{daemonBaseUrl:r});let i=I(e.flags?.daemonServerMode??process.env.AGENT_DEVICE_DAEMON_SERVER_MODE??("dual"===n?"dual":void 0));return{paths:h(t),transportPreference:o,serverMode:i,remoteBaseUrl:r,remoteAuthToken:a}}(t),o=await d("daemon_startup",async()=>await S(n),{requestId:r,session:t.session}),i=await _(t,o),s={...t,positionals:i.positionals,flags:i.flags,token:o.token,meta:{...t.meta??{},requestId:r,debug:a,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,...i.uploadedArtifactId?{uploadedArtifactId:i.uploadedArtifactId}:{},...i.clientArtifactPaths?{clientArtifactPaths:i.clientArtifactPaths}:{},...i.installSource?{installSource:i.installSource}:{}}};return c({level:"info",phase:"daemon_request_prepare",data:{requestId:r,command:t.command,session:t.session}}),await d("daemon_request",async()=>await z(o,s,n.transportPreference),{requestId:r,command:t.command})}async function _(e,r){let a,n=[...e.positionals??[]],o=e.flags?{...e.flags}:void 0,i=e.meta?.installSource,s={};if(W(r)){let d=function(e,r){if("screenshot"===e.command){let t=b(e,"path",".png");return r[0]?{field:"path",localPath:t,positionalIndex:0,positionalPath:T("screenshot",".png")}:{field:"path",localPath:t,positionalIndex:0,flagPath:T("screenshot",".png")}}if("record"===e.command&&"start"===(r[0]??"").toLowerCase()){let r=b(e,"outPath",".mp4",1);return{field:"outPath",localPath:r,positionalIndex:1,positionalPath:T("recording",t.extname(r)||".mp4")}}return null}(e,n);d&&(void 0!==d.positionalPath&&(n[d.positionalIndex]=d.positionalPath),void 0!==d.flagPath&&((o??={}).out=d.flagPath),s[d.field]=d.localPath);let l=await M(e,r);l&&(i=l.installSource,a=l.uploadedArtifactId??a)}if(!W(r)||"install"!==e.command&&"reinstall"!==e.command||n.length<2)return{positionals:n,flags:o,installSource:i,uploadedArtifactId:a,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}};let d=n[1];if(d.startsWith("remote:"))return n[1]=d.slice(7),{positionals:n,flags:o,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}};let l=t.isAbsolute(d)?d:t.resolve(e.meta?.cwd??process.cwd(),d);return p.existsSync(l)?{positionals:n,flags:o,installSource:i,uploadedArtifactId:a=await g({localPath:l,baseUrl:r.baseUrl,token:r.token}),...Object.keys(s).length>0?{clientArtifactPaths:s}:{}}:{positionals:n,flags:o,...Object.keys(s).length>0?{clientArtifactPaths:s}:{}}}async function M(e,r){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 o=t.isAbsolute(n)?n:t.resolve(e.meta?.cwd??process.cwd(),n);if(!p.existsSync(o))return{installSource:{...a,path:o}};let i=await g({localPath:o,baseUrl:r.baseUrl,token:r.token});return{installSource:{...a,path:o},uploadedArtifactId:i}}function b(e,r,a,n=0){let o=e.positionals?.[n]??e.flags?.out,i=`${"path"===r?"screenshot":"recording"}-${Date.now()}${a}`,s=o&&o.trim().length>0?o:i;return t.isAbsolute(s)?s:t.resolve(e.meta?.cwd??process.cwd(),s)}function T(e,r){let a=r.startsWith(".")?r:`.${r}`;return t.posix.join("/tmp",`agent-device-${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}${a}`)}async function S(e){let a;if(e.remoteBaseUrl){let t={transport:"http",token:e.remoteAuthToken??"",pid:0,baseUrl:e.remoteBaseUrl};if(await x(t,"http"))return t;throw new m("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 i=C(e.paths.infoPath),s=n(),d=function(e,r=o()){try{let a=p.statSync(e),n=t.relative(r,e)||e;return`${n}:${a.size}:${Math.trunc(a.mtimeMs)}`}catch{return"unknown"}}((a=B()).useSrc?a.srcPath:a.distPath,a.root),l=!!i&&await x(i,e.transportPreference);if(i&&i.version===s&&i.codeSignature===d&&l)return i;i&&(i.version!==s||i.codeSignature!==d||!l)&&(await R(i),F(e.paths.infoPath)),function(e){let t=L(e);if(!t.hasLock||t.hasInfo)return;let a=U(e.lockPath);if(!a)return F(e.lockPath);r(a.pid,a.processStartTime)||F(e.lockPath)}(e.paths);let c=0;for(let t=1;t<=y;t+=1){await $(e);let r=await N(D,e);if(r)return r;if(await O(e.paths)){c+=1;continue}let a=L(e.paths);if(!(t<y))break;if(!a.hasInfo&&!a.hasLock){await k(150);continue}}let u=L(e.paths);throw new m("COMMAND_FAILED","Failed to start daemon",{kind:"daemon_startup_failed",infoPath:e.paths.infoPath,lockPath:e.paths.lockPath,startupTimeoutMs:D,startupAttempts:y,lockRecoveryCount:c,metadataState:u,hint:function(e,t=h(process.env.AGENT_DEVICE_STATE_DIR)){return e.hasLock&&!e.hasInfo?`Detected ${t.lockPath} without ${t.infoPath}. If no agent-device daemon process is running, delete ${t.lockPath} and retry.`:e.hasLock&&e.hasInfo?`Daemon metadata may be stale. If no agent-device daemon process is running, delete ${t.infoPath} and ${t.lockPath}, then retry.`:`Daemon metadata is missing or stale. Delete ${t.infoPath} if present and retry.`}(u,e.paths)})}async function N(e,t){let r=Date.now();for(;Date.now()-r<e;){let e=C(t.paths.infoPath);if(e&&await x(e,t.transportPreference))return e;await new Promise(e=>setTimeout(e,100))}return null}async function k(e){await new Promise(t=>setTimeout(t,e))}async function O(e){let t=L(e);if(!t.hasLock||t.hasInfo)return!1;let a=U(e.lockPath);return a&&r(a.pid,a.processStartTime)&&await w(a.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:a.processStartTime}),F(e.lockPath),!0}async function R(e){await w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}function C(e){let t=q(e);if(!t||"string"!=typeof t.token||0===t.token.length)return null;let r=Number.isInteger(t.port)&&Number(t.port)>0,a=Number.isInteger(t.httpPort)&&Number(t.httpPort)>0;return r||a?{...t,port:r?Number(t.port):void 0,httpPort:a?Number(t.httpPort):void 0,pid:Number.isInteger(t.pid)&&t.pid>0?t.pid:0}:null}function U(e){let t=q(e);return t&&Number.isInteger(t.pid)&&!(t.pid<=0)?t:null}function L(e){return{hasInfo:p.existsSync(e.infoPath),hasLock:p.existsSync(e.lockPath)}}function q(e){if(!p.existsSync(e))return null;try{return JSON.parse(p.readFileSync(e,"utf8"))}catch{return null}}function F(e){try{p.existsSync(e)&&p.unlinkSync(e)}catch{}}async function x(e,t){var r;return"http"===G(e,t)?await function(e){let t=e.baseUrl?J(e.baseUrl,"health"):e.httpPort?`http://127.0.0.1:${e.httpPort}/health`:null;if(!t)return Promise.resolve(!1);let r=new URL(t),a="https:"===r.protocol?i:A,n=e.baseUrl?3e3:500;return new Promise(e=>{let t=a.request({protocol:r.protocol,host:r.hostname,port:r.port,path:r.pathname+r.search,method:"GET",timeout:n},t=>{t.resume(),e((t.statusCode??500)<500)});t.on("timeout",()=>{t.destroy(),e(!1)}),t.on("error",()=>{e(!1)}),t.end()})}(e):await ((r=e.port)?new Promise(e=>{let t=f.createConnection({host:"127.0.0.1",port:r},()=>{t.destroy(),e(!0)});t.on("error",()=>{e(!1)})}):Promise.resolve(!1))}async function $(e){let t=B(),r=t.useSrc?["--experimental-strip-types",t.srcPath]:[t.distPath],n={...process.env,AGENT_DEVICE_STATE_DIR:e.paths.baseDir,AGENT_DEVICE_DAEMON_SERVER_MODE:e.serverMode};a(process.execPath,r,{env:n})}function B(){let e=o(),r=t.join(e,"dist","src","daemon.js"),a=t.join(e,"src","daemon.ts"),n=p.existsSync(r),i=p.existsSync(a);if(!n&&!i)throw new m("COMMAND_FAILED","Daemon entry not found",{distPath:r,srcPath:a});return{root:e,distPath:r,srcPath:a,useSrc:process.execArgv.includes("--experimental-strip-types")?i:!n&&i}}async function z(e,t,r){return"http"===G(e,r)?await j(e,t):await V(e,t)}function G(e,t){if(e.baseUrl){if("socket"===t)throw new m("COMMAND_FAILED","Remote daemon endpoint only supports HTTP transport",{daemonBaseUrl:e.baseUrl});return"http"}if("http"===t){if(!e.httpPort)throw new m("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");return"http"}if("socket"===t){if(!e.port)throw new m("COMMAND_FAILED","Daemon socket endpoint is unavailable");return"socket"}let r=e.transport;if("http"===r&&e.httpPort)return"http";if(("socket"===r||"dual"===r)&&e.port)return"socket";if(e.httpPort)return"http";if(e.port)return"socket";throw new m("COMMAND_FAILED","Daemon metadata has no reachable transport")}async function V(e,t){let r=e.port;if(!r)throw new m("COMMAND_FAILED","Daemon socket endpoint is unavailable");return new Promise((a,n)=>{let o=f.createConnection({host:"127.0.0.1",port:r},()=>{o.write(`${JSON.stringify(t)} | ||
| `)}),i=setTimeout(()=>{o.destroy();let r=H(),a=K(e,h(t.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR));c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:v,requestId:t.meta?.requestId,command:t.command,timedOutRunnerPidsTerminated:r.terminated,timedOutRunnerCleanupError:r.error,daemonPidReset:e.pid,daemonPidForceKilled:a.forcedKill}}),n(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:v,requestId:t.meta?.requestId,hint:"Retry with --debug and check daemon diagnostics logs. Timed-out iOS runner xcodebuild processes were terminated when detected."}))},v),s="";o.setEncoding("utf8"),o.on("data",e=>{let r=(s+=e).indexOf("\n");if(-1===r)return;let d=s.slice(0,r).trim();if(d)try{let e=JSON.parse(d);o.end(),clearTimeout(i),a(e)}catch(e){clearTimeout(i),n(new m("COMMAND_FAILED","Invalid daemon response",{requestId:t.meta?.requestId,line:d},e instanceof Error?e:void 0))}}),o.on("error",e=>{clearTimeout(i),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:t.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),n(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:t.meta?.requestId,hint:"Retry command. If this persists, clean stale daemon metadata and start a fresh session."},e))})})}async function j(t,r){let a=t.baseUrl?new URL(J(t.baseUrl,"rpc")):t.httpPort?new URL(`http://127.0.0.1:${t.httpPort}/rpc`):null;if(!a)throw new m("COMMAND_FAILED","Daemon HTTP endpoint is unavailable");let n=JSON.stringify({jsonrpc:"2.0",id:r.meta?.requestId??e(),method:"agent_device.command",params:r}),o={"content-type":"application/json","content-length":Buffer.byteLength(n)};return t.baseUrl&&t.token&&(o.authorization=`Bearer ${t.token}`,o["x-agent-device-token"]=t.token),await new Promise((e,s)=>{let d=h(r.flags?.stateDir??process.env.AGENT_DEVICE_STATE_DIR),l=("https:"===a.protocol?i:A).request({protocol:a.protocol,host:a.hostname,port:a.port,method:"POST",path:a.pathname+a.search,headers:o},a=>{let n="";a.setEncoding("utf8"),a.on("data",e=>{n+=e}),a.on("end",()=>{clearTimeout(u);try{let a=JSON.parse(n);if(a.error){let e=a.error.data??{};s(new m(String(e.code??"COMMAND_FAILED"),String(e.message??a.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(!a.result||"object"!=typeof a.result)return void s(new m("COMMAND_FAILED","Invalid daemon RPC response",{requestId:r.meta?.requestId}));if(t.baseUrl&&a.result.ok)return void Q(t,r,a.result).then(e).catch(s);e(a.result)}catch(e){clearTimeout(u),s(new m("COMMAND_FAILED","Invalid daemon response",{requestId:r.meta?.requestId,line:n},e instanceof Error?e:void 0))}})}),u=setTimeout(()=>{l.destroy();let e=W(t)?{terminated:0}:H(),a=W(t)?{forcedKill:!1}:K(t,d);c({level:"error",phase:"daemon_request_timeout",data:{timeoutMs:v,requestId:r.meta?.requestId,command:r.command,timedOutRunnerPidsTerminated:e.terminated,timedOutRunnerCleanupError:e.error,daemonPidReset:W(t)?void 0:t.pid,daemonPidForceKilled:W(t)?void 0:a.forcedKill,daemonBaseUrl:t.baseUrl}}),s(new m("COMMAND_FAILED","Daemon request timed out",{timeoutMs:v,requestId:r.meta?.requestId,hint:W(t)?"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."}))},v);l.on("error",e=>{clearTimeout(u),c({level:"error",phase:"daemon_request_socket_error",data:{requestId:r.meta?.requestId,message:e instanceof Error?e.message:String(e)}}),s(new m("COMMAND_FAILED","Failed to communicate with daemon",{requestId:r.meta?.requestId,hint:W(t)?"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))}),l.write(n),l.end()})}function H(){let e=0;try{for(let t of E){let r=s("pkill",["-f",t],{allowFailure:!0});0===r.exitCode&&(e+=1)}return{terminated:e}}catch(t){return{terminated:e,error:t instanceof Error?t.message:String(t)}}}function K(e,t){let a=!1;try{r(e.pid,e.processStartTime)&&(process.kill(e.pid,"SIGKILL"),a=!0)}catch{w(e.pid,{termTimeoutMs:3e3,killTimeoutMs:1e3,expectedStartTime:e.processStartTime})}finally{F(t.infoPath),F(t.lockPath)}return{forcedKill:a}}function W(e){return"string"==typeof e.baseUrl&&e.baseUrl.length>0}function J(e,t){return new URL(t,e.endsWith("/")?e:`${e}/`).toString()}async function Q(e,r,a){let n=Array.isArray(a.data?.artifacts)?a.data.artifacts:[];if(0===n.length||!e.baseUrl)return a;let o=a.data?{...a.data}:{},i=[];for(let a of n){if(!a||"object"!=typeof a||"string"!=typeof a.artifactId){i.push(a);continue}let n=function(e,r){if(e.localPath&&e.localPath.trim().length>0)return e.localPath;let a=e.fileName?.trim()||`${e.field}-${Date.now()}`;return t.resolve(r.meta?.cwd??process.cwd(),a)}(a,r);await X({baseUrl:e.baseUrl,token:e.token,artifactId:a.artifactId,destinationPath:n,requestId:r.meta?.requestId}),o[a.field]=n,i.push({...a,localPath:n})}return o.artifacts=i,{ok:!0,data:o}}async function X(e){var r,a;let n,o=new URL((r=e.baseUrl,a=e.artifactId,n=r.endsWith("/")?r:`${r}/`,new URL(`upload/${encodeURIComponent(a)}`,n).toString())),s="https:"===o.protocol?i:A;await p.promises.mkdir(t.dirname(e.destinationPath),{recursive:!0}),await new Promise((t,r)=>{let a=!1,n=e.timeoutMs??v,i=n=>{if(!a){if(a=!0,clearTimeout(l),n)return void p.promises.rm(e.destinationPath,{force:!0}).finally(()=>r(n));t()}},d=s.request({protocol:o.protocol,host:o.hostname,port:o.port,method:"GET",path:o.pathname+o.search,headers:e.token?{authorization:`Bearer ${e.token}`,"x-agent-device-token":e.token}:void 0},t=>{if((t.statusCode??500)>=400){let r="";t.setEncoding("utf8"),t.on("data",e=>{r+=e}),t.on("end",()=>{i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,statusCode:t.statusCode,requestId:e.requestId,body:r}))});return}let r=p.createWriteStream(e.destinationPath);r.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("error",e=>{i(e instanceof Error?e:Error(String(e)))}),t.on("aborted",()=>{i(new m("COMMAND_FAILED","Remote artifact download was interrupted",{artifactId:e.artifactId,requestId:e.requestId}))}),r.on("finish",()=>{r.close(()=>i())}),t.pipe(r)}),l=setTimeout(()=>{d.destroy(new m("COMMAND_FAILED","Remote artifact download timed out",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n}))},n);d.on("error",t=>{t instanceof m?i(t):i(new m("COMMAND_FAILED","Failed to download remote artifact",{artifactId:e.artifactId,requestId:e.requestId,timeoutMs:n},t instanceof Error?t:void 0))}),d.end()})}function Y(e,t){let r=ed(e,"bundleId"),a=ed(e,"package"),n=r??a;return{app:es(e,"app"),appPath:es(e,"appPath"),platform:ec(e,"platform"),appId:n,bundleId:r,package:a,identifiers:{session:t,appId:n,appBundleId:r,package:a}}}function Z(e,t){return{session:t,configured:!0===e.configured,cleared:!0===e.cleared||void 0,runtime:er(e.runtime),identifiers:{session:t}}}function ee(e){let t=eo(e),r=ec(t,"platform"),a=es(t,"id");return{platform:r,target:eu(t,"target"),kind:function(e,t){let r=e[t];if("simulator"===r||"emulator"===r||"device"===r)return r;throw new m("COMMAND_FAILED",`Daemon response has invalid "${t}".`,{response:e})}(t,"kind"),id:a,name:es(t,"name"),booted:"boolean"==typeof t.booted?t.booted:void 0,identifiers:{deviceId:a,deviceName:es(t,"name"),..."ios"===r?{udid:a}:{serial:a}},ios:"ios"===r?{udid:a}:void 0,android:"android"===r?{serial:a}:void 0}}function et(e){let t=eo(e),r=ec(t,"platform"),a=es(t,"id"),n=es(t,"name"),o=eu(t,"target"),i=es(t,"device"),s={session:n,deviceId:a,deviceName:i,..."ios"===r?{udid:a}:{serial:a}};return{name:n,createdAt:function(e,t){let r=e[t];if("number"!=typeof r||!Number.isFinite(r))throw new m("COMMAND_FAILED",`Daemon response is missing numeric "${t}".`,{response:e});return r}(t,"createdAt"),device:{platform:r,target:o,id:a,name:i,identifiers:s,ios:"ios"===r?{udid:a,simulatorSetPath:el(t,"ios_simulator_device_set")}:void 0,android:"android"===r?{serial:a}:void 0},identifiers:s}}function er(e){if(!ei(e))return;let t=e.platform,r=ed(e,"metroHost"),a="number"==typeof e.metroPort?e.metroPort:void 0;return{platform:"ios"===t||"android"===t?t:void 0,metroHost:r,metroPort:a,bundleUrl:ed(e,"bundleUrl"),launchUrl:ed(e,"launchUrl")}}function ea(e,t){return t??e??"default"}function en(e){let t={};for(let[r,a]of Object.entries(e))void 0!==a&&(t[r]=a);return t}function eo(e){if(!ei(e))throw new m("COMMAND_FAILED","Daemon returned an unexpected response shape.",{value:e});return e}function ei(e){return"object"==typeof e&&null!==e}function es(e,t){let r=e[t];if("string"!=typeof r||0===r.length)throw new m("COMMAND_FAILED",`Daemon response is missing "${t}".`,{response:e});return r}function ed(e,t){let r=e[t];return"string"==typeof r&&r.length>0?r:void 0}function el(e,t){let r=e[t];return null===r?null:"string"==typeof r&&r.length>0?r:void 0}function ec(e,t){let r=e[t];if("ios"===r||"android"===r)return r;throw new m("COMMAND_FAILED",`Daemon response has invalid "${t}".`,{response:e})}function eu(e,t){return"tv"===e[t]?"tv":"mobile"}function em(e={},t={}){let r=t.transport??P,a=async(t,a=[],n={})=>{let o={...e,...n},i=await r({session:ea(e.session,n.session),command:t,positionals:a,flags:en({stateDir:o.stateDir,daemonBaseUrl:o.daemonBaseUrl,daemonAuthToken:o.daemonAuthToken,daemonTransport:o.daemonTransport,daemonServerMode:o.daemonServerMode,tenant:o.tenant,sessionIsolation:o.sessionIsolation,runId:o.runId,leaseId:o.leaseId,platform:o.platform,target:o.target,device:o.device,udid:o.udid,serial:o.serial,iosSimulatorDeviceSet:o.iosSimulatorDeviceSet,androidDeviceAllowlist:o.androidDeviceAllowlist,runtime:o.simulatorRuntimeId,boot:o.boot,reuseExisting:o.reuseExisting,activity:o.activity,relaunch:o.relaunch,shutdown:o.shutdown,saveScript:o.saveScript,noRecord:o.noRecord,metroHost:o.metroHost,metroPort:o.metroPort,bundleUrl:o.bundleUrl,launchUrl:o.launchUrl,snapshotInteractiveOnly:o.interactiveOnly,snapshotCompact:o.compact,snapshotDepth:o.depth,snapshotScope:o.scope,snapshotRaw:o.raw,verbose:o.debug}),runtime:o.runtime,meta:en({requestId:o.requestId,cwd:o.cwd,debug:o.debug,lockPolicy:o.lockPolicy,lockPlatform:o.lockPlatform,tenantId:o.tenant,runId:o.runId,leaseId:o.leaseId,sessionIsolation:o.sessionIsolation,installSource:o.installSource,retainMaterializedPaths:o.retainMaterializedPaths,materializedPathRetentionMs:o.materializedPathRetentionMs,materializationId:o.materializationId})});if(!i.ok)throw new m(i.error.code,i.error.message,{...i.error.details??{},hint:i.error.hint,diagnosticId:i.error.diagnosticId,logPath:i.error.logPath});return i.data??{}},n=async(e={})=>{let t=await a("session_list",[],e);return(Array.isArray(t.sessions)?t.sessions:[]).map(et)};return{devices:{list:async(e={})=>{let t=await a("devices",[],e);return(Array.isArray(t.devices)?t.devices:[]).map(ee)}},sessions:{list:async(e={})=>await n(e),close:async(t={})=>{let r=ea(e.session,t.session),n=(await a("close",[],t)).shutdown;return{session:r,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},simulators:{ensure:async e=>{let{runtime:t,...r}=e,n=await a("ensure-simulator",[],{...r,simulatorRuntimeId:t}),o=es(n,"udid"),i=es(n,"device");return{udid:o,device:i,runtime:es(n,"runtime"),created:!0===n.created,booted:!0===n.booted,iosSimulatorDeviceSet:el(n,"ios_simulator_device_set"),identifiers:{deviceId:o,deviceName:i,udid:o}}}},apps:{install:async t=>Y(await a("install",[t.app,t.appPath],t),ea(e.session,t.session)),reinstall:async t=>Y(await a("reinstall",[t.app,t.appPath],t),ea(e.session,t.session)),installFromSource:async t=>{var r,n;let o,i,s;return r=await a("install_source",[],{...t,installSource:t.source,retainMaterializedPaths:t.retainPaths,materializedPathRetentionMs:t.retentionMs}),n=ea(e.session,t.session),o=ed(r,"bundleId"),i=ed(r,"packageName"),s=o??i,{appName:ed(r,"appName"),appId:s,bundleId:o,packageName:i,launchTarget:es(r,"launchTarget"),installablePath:ed(r,"installablePath"),archivePath:ed(r,"archivePath"),materializationId:ed(r,"materializationId"),materializationExpiresAt:ed(r,"materializationExpiresAt"),identifiers:{session:n,appId:s,appBundleId:o,package:i}}},open:async t=>{let r=ea(e.session,t.session),n=t.url?[t.app,t.url]:[t.app],o=await a("open",n,t),i=function(e){let t=e.platform,r=ed(e,"id"),a=ed(e,"device");if("ios"!==t&&"android"!==t||!r||!a)return;let n=eu(e,"target"),o={deviceId:r,deviceName:a,..."ios"===t?{udid:r}:{serial:r}};return{platform:t,target:n,id:r,name:a,identifiers:o,ios:"ios"===t?{udid:ed(e,"device_udid")??r,simulatorSetPath:el(e,"ios_simulator_device_set")}:void 0,android:"android"===t?{serial:ed(e,"serial")??r}:void 0}}(o),s=ed(o,"appBundleId");return{session:r,appName:ed(o,"appName"),appBundleId:s,appId:s,startup:function(e){if(ei(e)&&"number"==typeof e.durationMs&&"string"==typeof e.measuredAt&&"string"==typeof e.method)return{durationMs:e.durationMs,measuredAt:e.measuredAt,method:e.method,appTarget:ed(e,"appTarget"),appBundleId:ed(e,"appBundleId")}}(o.startup),runtime:er(o.runtime),device:i,identifiers:{session:r,deviceId:i?.id,deviceName:i?.name,udid:i?.ios?.udid,serial:i?.android?.serial,appId:s,appBundleId:s}}},close:async(t={})=>{let r=ea(e.session,t.session),n=(await a("close",t.app?[t.app]:[],t)).shutdown;return{session:r,closedApp:t.app,shutdown:"object"==typeof n&&null!==n?n:void 0,identifiers:{session:r}}}},materializations:{release:async e=>{var t;return{released:!0===(t=await a("release_materialized_paths",[],{...e,materializationId:e.materializationId})).released,materializationId:es(t,"materializationId"),identifiers:{}}}},runtime:{set:async t=>Z(await a("runtime",["set"],t),ea(e.session,t.session)),show:async(t={})=>Z(await a("runtime",["show"],t),ea(e.session,t.session))},capture:{snapshot:async(t={})=>{var r;let n=ea(e.session,t.session),o=await a("snapshot",[],t),i=ed(o,"appBundleId");return{nodes:Array.isArray(r=o.nodes)?r:[],truncated:!0===o.truncated,appName:ed(o,"appName"),appBundleId:i,identifiers:{session:n,appId:i,appBundleId:i}}},screenshot:async(t={})=>{let r=ea(e.session,t.session);return{path:es(await a("screenshot",t.path?[t.path]:[],t),"path"),identifiers:{session:r}}}}}}export{em as createAgentDeviceClient,P as sendToDaemon}; |
@@ -16,2 +16,3 @@ export type MaterializeInstallSource = { | ||
| installableLabel: string; | ||
| allowArchiveExtraction?: boolean; | ||
| signal?: AbortSignal; | ||
@@ -27,2 +28,4 @@ downloadTimeoutMs?: number; | ||
| export declare function expandSourcePath(inputPath: string): string; | ||
| export declare function validateDownloadSourceUrl(parsedUrl: URL): Promise<void>; | ||
| export declare function isTrustedInstallSourceUrl(sourceUrl: string | URL): boolean; | ||
| export {}; |
+1
-1
| { | ||
| "name": "agent-device", | ||
| "version": "0.8.0", | ||
| "version": "0.8.1", | ||
| "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
+7
-0
@@ -84,2 +84,7 @@ <a href="https://www.callstack.com/open-source?utm_campaign=generic&utm_source=github&utm_medium=referral&utm_content=agent-device" align="center"> | ||
| `installFromSource` URL sources are intentionally limited: | ||
| - Private and loopback hosts are blocked by default. | ||
| - Archive-backed URL installs are only supported for trusted artifact services, currently GitHub Actions and EAS. | ||
| - For other hosts, prefer `source: { kind: 'path', path: ... }` so the client downloads/uploads the artifact explicitly. | ||
| The skill is also accessible on [ClawHub](https://clawhub.ai/okwasniewski/agent-device). | ||
@@ -617,2 +622,4 @@ For structured exploratory QA workflows, use the dogfood skill at [skills/dogfood/SKILL.md](skills/dogfood/SKILL.md). | ||
| - `AGENT_DEVICE_HTTP_AUTH_EXPORT=<export-name>` optional export name from auth hook module (default: `default`). | ||
| - `AGENT_DEVICE_SOURCE_DOWNLOAD_TIMEOUT_MS=<ms>` timeout for `installFromSource` URL downloads (default: `120000`). | ||
| - `AGENT_DEVICE_ALLOW_PRIVATE_SOURCE_URLS=1` opt out of the default SSRF guard that blocks loopback/private-network artifact URLs for `installFromSource`. | ||
| - `AGENT_DEVICE_MAX_SIMULATOR_LEASES=<n>` optional max concurrent simulator leases for HTTP lease allocation (default: unlimited). | ||
@@ -619,0 +626,0 @@ - `AGENT_DEVICE_LEASE_TTL_MS=<ms>` default lease TTL used by `agent_device.lease.allocate` and `agent_device.lease.heartbeat` (default: `60000`). |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 52 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 51 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
863875
0.51%174
0.58%3791
0.48%644
1.1%92
1.1%