agent-device 0.7.16 → 0.7.17

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.
@@ -8,7 +8,7 @@ ${e}`.toLowerCase()).includes("failed registering bundle identifier")||r.include
8
8
  `)}function tI(e){if(e&&$.existsSync(e))try{$.unlinkSync(e)}catch{}}async function tv(e,t=2e3){await Promise.race([e.then(()=>void 0).catch(()=>void 0),new Promise(e=>setTimeout(e,t))])}async function tA(e){await new Promise(t=>setTimeout(t,e))}function ty(e,t){let r=t.includeTokens?.filter(e=>e.length>0)??[],i="",n=i=>{(!(r.length>0)||r.some(e=>i.includes(e)))&&e.write(function(e,t){if(0===t.length)return e;let r=e;for(let e of t)r=r.replace(e,"[REDACTED]");return r}(i,t.redactionPatterns))};return{onChunk:e=>{let t=`${i}${e}`.split("\n");for(let e of(i=t.pop()??"",t))n(`${e}
9
9
  `)},flush:()=>{i&&(n(i),i="")}}}function tb(e,t,r){let i=e.stdout,n=e.stderr;return i&&n?(i.setEncoding("utf8"),n.setEncoding("utf8"),i.on("data",r.writer.onChunk),n.on("data",r.writer.onChunk),t.on("error",()=>{e.killed||e.kill("SIGKILL")}),e.on("error",()=>t.destroy()),new Promise(i=>{e.on("close",e=>{r.writer.flush(),r.endStreamOnClose&&t.end(),i({stdout:"",stderr:"",exitCode:e??1})})})):Promise.resolve({stdout:"",stderr:"missing stdio pipes",exitCode:1})}async function tN(e,t){let r=(await p("adb",["-s",e,"shell","pidof",t],{allowFailure:!0})).stdout.trim().split(/\s+/)[0];return r&&/^\d+$/.test(r)?r:null}async function tS(e,t,r,i,n){let a,o,s="active",l=!1,d=(async()=>{try{for(;!l;){let d=await tN(e,t);if(!d){await tA(1e3);continue}let u=h("adb",["-s",e,"logcat","-v","time","--pid",d],{stdio:["ignore","pipe","pipe"]});a=u;let c=ty(r,{redactionPatterns:i});o=tb(u,r,{endStreamOnClose:!1,writer:c}),"number"==typeof u.pid&&tg(n,u.pid);let p=await o;if(tI(n),a=void 0,o=void 0,l)break;0!==p.exitCode&&(s="failed"),await tA(500)}return{stdout:"",stderr:"",exitCode:0}}finally{r.end(),tI(n)}})();return{backend:"android",getState:()=>s,startedAt:Date.now(),wait:d,stop:async()=>{l=!0,a&&!a.killed&&a.kill("SIGINT"),o&&await tv(o),a&&!a.killed&&a.kill("SIGKILL"),await tv(d),tI(n)}}}async function t_(e,t,r,i){let n="active",a=h("log",["stream","--style","compact","--predicate",`subsystem == "${e}" OR processImagePath ENDSWITH[c] "/${e}" OR senderImagePath ENDSWITH[c] "/${e}" OR eventMessage CONTAINS[c] "${e}"`],{stdio:["ignore","pipe","pipe"]}),o=ty(t,{redactionPatterns:r});"number"==typeof a.pid&&tg(i,a.pid);let s=tb(a,t,{endStreamOnClose:!0,writer:o}).then(e=>(0!==e.exitCode&&(n="failed"),tI(i),e));return{backend:"ios-simulator",getState:()=>n,startedAt:Date.now(),wait:s,stop:async()=>{a.killed||a.kill("SIGINT"),await tv(s),a.killed||a.kill("SIGKILL"),await tv(s),tI(i)}}}async function tD(e,t,r,i){let n="active",a=h("xcrun",["devicectl","device","log","stream","--device",e],{stdio:["ignore","pipe","pipe"]}),o=ty(t,{redactionPatterns:r});"number"==typeof a.pid&&tg(i,a.pid);let s=tb(a,t,{endStreamOnClose:!0,writer:o}).then(e=>(0!==e.exitCode&&(n="failed"),tI(i),e));return{backend:"ios-device",getState:()=>n,startedAt:Date.now(),wait:s,stop:async()=>{a.killed||a.kill("SIGINT"),await tv(s),a.killed||a.kill("SIGKILL"),await tv(s),tI(i)}}}function tE(e,t){let r=process.env[e];if(!r)return t;let i=Number.parseInt(r,10);return Number.isInteger(i)&&i>0?i:t}function tk(e){let t=n.dirname(e);$.existsSync(t)||$.mkdirSync(t,{recursive:!0}),function(e,t){if($.existsSync(e)&&!($.statSync(e).size<t.maxBytes))for(let r=t.maxRotatedFiles;r>=1;r-=1){let t=1===r?e:`${e}.${r-1}`,i=`${e}.${r}`;$.existsSync(t)&&($.existsSync(i)&&$.unlinkSync(i),$.renameSync(t,i))}}(e,{maxBytes:tE("AGENT_DEVICE_APP_LOG_MAX_BYTES",5242880),maxRotatedFiles:tE("AGENT_DEVICE_APP_LOG_MAX_FILES",1)})}async function tO(e,t,r,i){tk(r);let n=$.createWriteStream(r,{flags:"a"}),a=function(){let e=process.env.AGENT_DEVICE_APP_LOG_REDACT_PATTERNS;if(!e)return[];let t=e.split(",").map(e=>e.trim()).filter(e=>e.length>0),r=[];for(let e of t)try{r.push(RegExp(e,"gi"))}catch{}return r}();if("ios"===e.platform)return"device"===e.kind?await tD(e.id,n,a,i):await t_(t,n,a,i);if("android"===e.platform){if(!/^[a-zA-Z0-9._:-]+$/.test(t))throw new I("INVALID_ARGS",`Invalid Android package name for logs: ${t}`);return await tS(e.id,t,n,a,i)}throw n.end(),new I("UNSUPPORTED_PLATFORM",`unsupported platform: ${e.platform}`)}async function tL(e){await e.stop(),await tv(e.wait)}async function tM(e,t){let r={},i=[];if(t||i.push("No app bundle is tracked in this session. Run open <app> first for app-scoped logs."),"android"===e.platform){try{let e=await p("adb",["version"],{allowFailure:!0});r.adbAvailable=0===e.exitCode}catch{r.adbAvailable=!1}if(t)try{r.androidPidVisible=(await p("adb",["-s",e.id,"shell","pidof",t],{allowFailure:!0})).stdout.trim().length>0}catch{r.androidPidVisible=!1}}if("ios"===e.platform&&"simulator"===e.kind)try{let e=await p("xcrun",["simctl","help"],{allowFailure:!0});r.simctlAvailable=0===e.exitCode}catch{r.simctlAvailable=!1}if("ios"===e.platform&&"device"===e.kind)try{let e=await p("xcrun",["devicectl","--version"],{allowFailure:!0});r.devicectlAvailable=0===e.exitCode}catch{r.devicectlAvailable=!1}return{checks:r,notes:i}}function tx(e){let t=n.dirname(e),r=n.basename(e);$.existsSync(t)||$.mkdirSync(t,{recursive:!0}),$.existsSync(e)?$.truncateSync(e,0):$.writeFileSync(e,"","utf8");let i=0;for(let e of $.readdirSync(t)){if(!e.startsWith(`${r}.`))continue;let a=e.slice(r.length+1);if(/^\d+$/.test(a))try{$.unlinkSync(n.join(t,e)),i+=1}catch{}}return{path:e,cleared:!0,removedRotatedFiles:i}}let tC=new Map;function tR(e){let t=tC.get(e);if(t&&(clearTimeout(t.timer),tC.delete(e),t.deleteAfterDownload))try{$.rmSync(t.artifactPath,{force:!0})}catch{}}let tT=new Map;function tP(e){let t=tT.get(e);t&&(clearTimeout(t.timer),tT.delete(e),$.rmSync(t.tempDir,{recursive:!0,force:!0}))}async function t$(e){let t=e.headers["x-artifact-type"],r=e.headers["x-artifact-filename"];if(!t||!r)throw new I("INVALID_ARGS","Missing required headers: x-artifact-type and x-artifact-filename");if("file"!==t&&"app-bundle"!==t)throw new I("INVALID_ARGS",`Invalid x-artifact-type: ${t}. Must be "file" or "app-bundle".`);!function(e){let t=e.headers["content-length"];if("string"!=typeof t)return;let r=Number(t);if(Number.isFinite(r)&&r>0x80000000)throw new I("INVALID_ARGS","Upload exceeds maximum size of 2147483648 bytes")}(e);let i=function(e){let t=n.basename(e);if(!t||"."===t||".."===t)throw new I("INVALID_ARGS",`Invalid artifact filename: ${e}`);return t}(r),a=$.mkdtempSync(n.join(V.tmpdir(),"agent-device-upload-"));try{if("file"===t){let t=n.join(a,i);return await tF(e,t),{artifactPath:t,tempDir:a}}let r=n.join(a,"artifact.tar");await tF(e,r),await tV(r,i),await p("tar",["xf",r,"-C",a]),$.rmSync(r,{force:!0});let o=n.join(a,i);if(!$.existsSync(o))throw new I("INVALID_ARGS",`Expected extracted bundle "${i}" not found in archive`);return{artifactPath:o,tempDir:a}}catch(e){throw $.rmSync(a,{recursive:!0,force:!0}),e}}function tF(e,t){return new Promise((r,i)=>{let n=$.createWriteStream(t),a=!1,o=0,s=e=>{a||(a=!0,i(e))};e.on("data",t=>{if((o+=t.length)>0x80000000){let t=new I("INVALID_ARGS","Upload exceeds maximum size of 2147483648 bytes");e.destroy(t),n.destroy(t)}}),e.pipe(n),n.on("finish",()=>{a||(a=!0,r())}),n.on("error",s),e.on("error",s)})}async function tV(e,t){let r=(await p("tar",["-tf",e])).stdout.split(/\r?\n/).map(e=>e.trim()).filter(Boolean);if(0===r.length)throw new I("INVALID_ARGS","Uploaded app bundle archive is empty");if(!r.some(e=>e===t||e.startsWith(`${t}/`)))throw new I("INVALID_ARGS",`Uploaded archive must contain a top-level "${t}" bundle`);for(let e of r)!function(e,t){if(e.includes("\0"))throw new I("INVALID_ARGS",`Invalid archive entry: ${e}`);if(n.posix.isAbsolute(e))throw new I("INVALID_ARGS",`Archive entry must be relative: ${e}`);let r=n.posix.normalize(e).replace(/^\.\/+/,"");if(!r||"."===r||r.startsWith("../"))throw new I("INVALID_ARGS",`Archive entry escapes bundle root: ${e}`);if(r!==t&&!r.startsWith(`${t}/`))throw new I("INVALID_ARGS",`Archive entry must stay inside top-level "${t}" bundle: ${e}`)}(e,t);for(let t of(await p("tar",["-tvf",e])).stdout.split(/\r?\n/).filter(Boolean)){let e=t[0];if("l"===e||"h"===e)throw new I("INVALID_ARGS","Uploaded app bundle archive cannot contain symlinks or hard links")}}let tU=new Set(["agent_device.command","agent-device.command"]),tG={"agent_device.lease.allocate":"lease_allocate","agent-device.lease.allocate":"lease_allocate","agent_device.lease.heartbeat":"lease_heartbeat","agent-device.lease.heartbeat":"lease_heartbeat","agent_device.lease.release":"lease_release","agent-device.lease.release":"lease_release"},tB=new Set([...tU,...Object.keys(tG)]);function tj(e,t,r,i){return{jsonrpc:"2.0",id:e,error:{code:t,message:r,data:i}}}function tq(e,t,r=200){e.statusCode=r,e.setHeader("content-type","application/json"),e.end(JSON.stringify(t))}function tH(e){switch(e){case"INVALID_ARGS":return 400;case"UNAUTHORIZED":return 401;case"SESSION_NOT_FOUND":return 404;default:return 500}}function tW(e,t){let r="string"==typeof t.authorization?t.authorization:"",i=r.toLowerCase().startsWith("bearer ")?r.slice(7):void 0,n="string"==typeof t["x-agent-device-token"]?t["x-agent-device-token"]:void 0;return("string"==typeof e.token?e.token:void 0)??n??i??""}function tJ(e,t){let r=e[t];return"string"==typeof r?r:void 0}async function tz(e,t){if(!e)return{ok:!0};let r=await e(t);if(void 0===r||!0===r)return{ok:!0};if(!1===r){let e=_(new I("UNAUTHORIZED","Request rejected by auth hook"));return{ok:!1,statusCode:401,response:tj(t.rpcRequest.id??null,-32001,e.message,e)}}if(!1===r.ok){let e=_(new I(r.code??"UNAUTHORIZED",r.message??"Request rejected by auth hook",r.details));return{ok:!1,statusCode:401,response:tj(t.rpcRequest.id??null,-32001,e.message,e)}}if("string"==typeof r.tenantId&&r.tenantId.length>0){let e=o(r.tenantId);if(!e){let e=_(new I("INVALID_ARGS","Auth hook returned invalid tenantId"));return{ok:!1,statusCode:500,response:tj(t.rpcRequest.id??null,-32e3,e.message,e)}}return{ok:!0,tenantId:e}}return{ok:!0}}async function tK(){let e,t=process.env.AGENT_DEVICE_HTTP_AUTH_HOOK;if(!t)return null;let r=process.env.AGENT_DEVICE_HTTP_AUTH_EXPORT||"default",i=n.isAbsolute(t)?t:n.resolve(t);try{e=await import(g(i).href)}catch(e){throw new I("COMMAND_FAILED","Failed to load AGENT_DEVICE_HTTP_AUTH_HOOK module",{hookPath:i,error:e instanceof Error?e.message:String(e)})}let a=e[r];if("function"!=typeof a)throw new I("INVALID_ARGS",`Auth hook export ${r} is not a function`,{hookPath:i,exportName:r});return a}async function tX(e){let t=await tK(),{handleRequest:r,token:i}=e;return j.createServer((e,n)=>{if("GET"===e.method&&"/health"===e.url){n.statusCode=200,n.setHeader("content-type","application/json"),n.end(JSON.stringify({ok:!0}));return}if("POST"===e.method&&"/upload"===e.url)return void tY(e,n,t,i);if("GET"===e.method&&e.url?.startsWith("/upload/"))return void tZ(e,n,t,i);if("POST"!==e.method||"/rpc"!==e.url){n.statusCode=404,n.end("Not found");return}let a="";e.setEncoding("utf8"),e.on("data",t=>{(a+=t).length>1048576&&e.destroy(Error("request too large"))}),e.on("error",()=>{n.headersSent||tq(n,tj(null,-32700,"Parse error"),400)}),e.on("end",async()=>{let i,o;try{i=JSON.parse(a)}catch{tq(n,tj(null,-32700,"Parse error"),400);return}if("2.0"!==i.jsonrpc||"string"!=typeof i.method)return void tq(n,tj(i.id??null,-32600,"Invalid Request"),400);if(!tB.has(i.method))return void tq(n,tj(i.id??null,-32601,`Method not found: ${i.method}`),404);if(!i.params||"object"!=typeof i.params)return void tq(n,tj(i.id??null,-32602,"Invalid params"),400);try{var s;let a=i.params,l=function(e,t,r){if(tU.has(e))return{token:tW(t,r),session:t.session??"default",command:t.command??"",positionals:Array.isArray(t.positionals)?t.positionals:[],flags:t.flags,meta:t.meta};let i=tG[e];if(i){let e;return{token:tW(t,r),session:tJ(t,"session")??"default",command:i,positionals:[],meta:{tenantId:tJ(t,"tenantId")??tJ(t,"tenant"),runId:tJ(t,"runId"),leaseId:tJ(t,"leaseId"),leaseTtlMs:(e=t.ttlMs,Number.isInteger(e)?Number(e):void 0),leaseBackend:tJ(t,"backend")}}}throw new I("INVALID_ARGS",`Method not found: ${e}`)}(i.method,a,e.headers);if(s=i.method,tU.has(s)&&("string"!=typeof l.command||0===l.command.length))return void tq(n,tj(i.id??null,-32602,"Invalid params: command is required"),400);o=ei(l.meta?.requestId,i.id),l.meta={...l.meta,requestId:o},en(o);let d=()=>{n.writableFinished||ea(o)};e.on("aborted",d),n.on("close",d);let u=await tz(t,{headers:e.headers,rpcRequest:i,daemonRequest:l});if(!u.ok)return void tq(n,u.response,u.statusCode);u.tenantId&&(l.meta={...l.meta,tenantId:u.tenantId,sessionIsolation:l.meta?.sessionIsolation??l.flags?.sessionIsolation??"tenant"});let c=await r(l);if(c.ok)return void tq(n,{jsonrpc:"2.0",id:i.id??null,result:c});tq(n,tj(i.id??null,-32e3,c.error.message,c.error),tH(c.error.code))}catch(t){let e=_(t);tq(n,tj(i.id??null,-32e3,e.message,e),tH(e.code))}finally{eo(o)}})})}async function tY(e,t,r,i){try{var n;let a,o,s=tW({},e.headers),l=tQ(s,i);if(l){t.statusCode=tH(l.code),t.setHeader("content-type","application/json"),t.end(JSON.stringify({ok:!1,error:l.message,code:l.code}));return}let d=await tz(r,{headers:e.headers,rpcRequest:{jsonrpc:"2.0",id:null,method:"agent_device.command"},daemonRequest:{token:s,session:"default",command:"upload",positionals:[]}});if(!d.ok){t.statusCode=d.statusCode,t.setHeader("content-type","application/json"),t.end(JSON.stringify({ok:!1,error:d.response.error?.data?.message??d.response.error?.message??"Unauthorized"}));return}let c=await t$(e),p=(n={artifactPath:c.artifactPath,tempDir:c.tempDir,tenantId:d.tenantId},a=u.randomUUID(),o=setTimeout(()=>{tP(a)},3e5),tT.set(a,{artifactPath:n.artifactPath,tempDir:n.tempDir,tenantId:n.tenantId,timer:o}),a);t.statusCode=200,t.setHeader("content-type","application/json"),t.end(JSON.stringify({ok:!0,uploadId:p}))}catch(r){let e=_(r);t.statusCode=tH(e.code),t.setHeader("content-type","application/json"),t.end(JSON.stringify({ok:!1,error:e.message,code:e.code}))}}async function tZ(e,t,r,i){let n=e.url?.slice("/upload/".length)??"";if(!n){t.statusCode=400,t.end("Missing artifact id");return}try{let a=tW({},e.headers),o=tQ(a,i);if(o){t.statusCode=tH(o.code),t.setHeader("content-type","application/json"),t.end(JSON.stringify({ok:!1,error:o.message,code:o.code}));return}let s=await tz(r,{headers:e.headers,rpcRequest:{jsonrpc:"2.0",id:null,method:"agent_device.command"},daemonRequest:{token:a,session:"default",command:"download_artifact",positionals:[n]}});if(!s.ok){t.statusCode=s.statusCode,t.setHeader("content-type","application/json"),t.end(JSON.stringify({ok:!1,error:s.response.error?.data?.message??s.response.error?.message??"Unauthorized"}));return}let l=function(e,t){let r=tC.get(e);if(!r)throw new I("INVALID_ARGS",`Artifact not found: ${e}`);if(r.tenantId&&r.tenantId!==t)throw new I("UNAUTHORIZED","Artifact belongs to a different tenant");if(!$.existsSync(r.artifactPath))throw tR(e),new I("COMMAND_FAILED",`Artifact file is missing: ${r.artifactPath}`);return{artifactPath:r.artifactPath,fileName:r.fileName,deleteAfterDownload:r.deleteAfterDownload}}(n,s.tenantId),d=$.createReadStream(l.artifactPath);t.statusCode=200,t.setHeader("content-type","application/octet-stream"),l.fileName&&t.setHeader("content-disposition",`attachment; filename="${l.fileName.replace(/"/g,"")}"`),d.on("error",e=>{if(t.headersSent)t.destroy(e);else{let r=_(e);t.statusCode=tH(r.code),t.end(r.message)}}),t.on("close",()=>{t.writableFinished&&tR(n)}),d.pipe(t)}catch(r){let e=_(r);t.statusCode=tH(e.code),t.setHeader("content-type","application/json"),t.end(JSON.stringify({ok:!1,error:e.message,code:e.code}))}}function tQ(e,t){return t&&e!==t?_(new I("UNAUTHORIZED","Invalid token")):null}function t0(e){if(!e)return;let t=e.trim();if(t&&/^[a-zA-Z0-9._-]{1,128}$/.test(t))return t}function t1(e){if(!e)return;let t=e.trim();if(t&&/^[a-f0-9]{16,128}$/i.test(t))return t.toLowerCase()}function t2(e){let t=(e??"").trim().toLowerCase();if(!t||"ios-simulator"===t)return"ios-simulator";throw new I("INVALID_ARGS",`Unsupported lease backend: ${e??""}`)}class t3{leases=new Map;runBindings=new Map;maxActiveSimulatorLeases;defaultLeaseTtlMs;minLeaseTtlMs;maxLeaseTtlMs;now;constructor(e={}){this.maxActiveSimulatorLeases=Number.isInteger(e.maxActiveSimulatorLeases)?Math.max(0,Number(e.maxActiveSimulatorLeases)):0,this.defaultLeaseTtlMs=Number.isInteger(e.defaultLeaseTtlMs)?Math.max(1,Number(e.defaultLeaseTtlMs)):6e4,this.minLeaseTtlMs=Number.isInteger(e.minLeaseTtlMs)?Math.max(1,Number(e.minLeaseTtlMs)):5e3,this.maxLeaseTtlMs=Number.isInteger(e.maxLeaseTtlMs)?Math.max(this.minLeaseTtlMs,Number(e.maxLeaseTtlMs)):6e5,this.now=e.now??(()=>Date.now())}allocateLease(e){let t=t2(e.backend),r=o(e.tenantId);if(!r)throw new I("INVALID_ARGS","Invalid tenant id. Use 1-128 chars: letters, numbers, dot, underscore, hyphen.");let i=t0(e.runId);if(!i)throw new I("INVALID_ARGS","Invalid run id. Use 1-128 chars: letters, numbers, dot, underscore, hyphen.");this.cleanupExpiredLeases();let n=this.resolveLeaseTtlMs(e.ttlMs),a=this.bindingKey(r,i,t),s=this.runBindings.get(a);if(s){let e=this.leases.get(s);if(e)return this.refreshLease(e,n);this.runBindings.delete(a)}this.enforceCapacity(t);let l=this.now(),d={leaseId:u.randomBytes(16).toString("hex"),tenantId:r,runId:i,backend:t,createdAt:l,heartbeatAt:l,expiresAt:l+n};return this.leases.set(d.leaseId,d),this.runBindings.set(a,d.leaseId),{...d}}heartbeatLease(e){let t=t1(e.leaseId);if(!t)throw new I("INVALID_ARGS","Invalid lease id.");this.cleanupExpiredLeases();let r=this.leases.get(t);if(!r)throw new I("UNAUTHORIZED","Lease is not active",{reason:"LEASE_NOT_FOUND"});this.assertOptionalScopeMatch(r,e.tenantId,e.runId);let i=this.resolveLeaseTtlMs(e.ttlMs);return this.refreshLease(r,i)}releaseLease(e){let t=t1(e.leaseId);if(!t)throw new I("INVALID_ARGS","Invalid lease id.");this.cleanupExpiredLeases();let r=this.leases.get(t);return r?(this.assertOptionalScopeMatch(r,e.tenantId,e.runId),this.leases.delete(t),this.runBindings.delete(this.bindingKey(r.tenantId,r.runId,r.backend)),{released:!0}):{released:!1}}assertLeaseAdmission(e){let t=t2(e.backend),r=o(e.tenantId);if(!r)throw new I("INVALID_ARGS","tenant isolation requires tenant id.");let i=t0(e.runId);if(!i)throw new I("INVALID_ARGS","tenant isolation requires run id.");let n=t1(e.leaseId);if(!n)throw new I("INVALID_ARGS","tenant isolation requires lease id.");this.cleanupExpiredLeases();let a=this.leases.get(n);if(!a)throw new I("UNAUTHORIZED","Lease is not active",{reason:"LEASE_NOT_FOUND"});if(a.backend!==t||a.tenantId!==r||a.runId!==i)throw new I("UNAUTHORIZED","Lease does not match tenant/run scope",{reason:"LEASE_SCOPE_MISMATCH"})}listActiveLeases(){return this.cleanupExpiredLeases(),Array.from(this.leases.values()).map(e=>({...e}))}cleanupExpiredLeases(){let e=this.now();for(let t of this.leases.values())t.expiresAt>e||(this.leases.delete(t.leaseId),this.runBindings.delete(this.bindingKey(t.tenantId,t.runId,t.backend)))}enforceCapacity(e){if("ios-simulator"!==e||this.maxActiveSimulatorLeases<=0)return;let t=Array.from(this.leases.values()).filter(e=>"ios-simulator"===e.backend).length;if(!(t<this.maxActiveSimulatorLeases))throw new I("COMMAND_FAILED","No simulator lease capacity available",{reason:"LEASE_CAPACITY_EXCEEDED",activeLeases:t,maxActiveLeases:this.maxActiveSimulatorLeases,backend:e,hint:"Retry after releasing another simulator lease."})}resolveLeaseTtlMs(e){if(!Number.isInteger(e))return this.defaultLeaseTtlMs;let t=Number(e);if(t<this.minLeaseTtlMs||t>this.maxLeaseTtlMs)throw new I("INVALID_ARGS",`Lease ttlMs must be between ${this.minLeaseTtlMs} and ${this.maxLeaseTtlMs}.`);return t}refreshLease(e,t){let r=this.now(),i={...e,heartbeatAt:r,expiresAt:r+t};return this.leases.set(i.leaseId,i),this.runBindings.set(this.bindingKey(i.tenantId,i.runId,i.backend),i.leaseId),{...i}}bindingKey(e,t,r){return`${e}:${t}:${r}`}assertOptionalScopeMatch(e,t,r){let i=o(t),n=t0(r);if(t&&!i)throw new I("INVALID_ARGS","Invalid tenant id. Use 1-128 chars: letters, numbers, dot, underscore, hyphen.");if(r&&!n)throw new I("INVALID_ARGS","Invalid run id. Use 1-128 chars: letters, numbers, dot, underscore, hyphen.");if(i&&e.tenantId!==i||n&&e.runId!==n)throw new I("UNAUTHORIZED","Lease does not match tenant/run scope",{reason:"LEASE_SCOPE_MISMATCH"})}}function t4(e,t){return["-s",e.id,...t]}async function t8(){if(!await A("adb"))throw new I("TOOL_MISSING","adb not found in PATH")}function t5(e,t){let r=`${e}
10
10
  ${t}`.toLowerCase();return r.includes("no shell command implementation")||r.includes("unknown command")}async function t6(e){await new Promise(t=>setTimeout(t,e))}function t9(e){let t=e.trim();if(!t||/\s/.test(t))return!1;let r=/^([A-Za-z][A-Za-z0-9+.-]*):(.+)$/.exec(t);if(!r)return!1;let i=r[1]?.toLowerCase(),n=r[2]??"";return"http"!==i&&"https"!==i&&"ws"!==i&&"wss"!==i&&"ftp"!==i&&"ftps"!==i||n.startsWith("//")}function t7(e,t){let r,i=e?.trim();return i?i:"http"===(r=t.trim().split(":")[0]?.toLowerCase())||"https"===r?"com.apple.mobilesafari":void 0}let re=["android.software.leanback","android.software.leanback_only","android.hardware.type.television"];function rt(e){return`${e.stdout}
11
- ${e.stderr}`}function rr(e,t){return["-s",e,...t]}function ri(e){return e.startsWith("emulator-")}function rn(e){return e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim()}async function ra(e,t=z){return p("adb",rr(e,["shell","getprop","sys.boot_completed"]),{allowFailure:!0,timeoutMs:t})}async function ro(e,t){let r=t.replace(/_/g," ").trim();if(!ri(e))return r||e;let i=await rs(e);return i?i.replace(/_/g," "):r||e}async function rs(e){for(let t of["ro.boot.qemu.avd_name","persist.sys.avd_name"]){let r=await p("adb",rr(e,["shell","getprop",t]),{allowFailure:!0,timeoutMs:1500}),i=r.stdout.trim();if(0===r.exitCode&&i.length>0)return i}let t=await p("adb",rr(e,["emu","avd","name"]),{allowFailure:!0,timeoutMs:1500}),r=t.stdout.trim();if(0===t.exitCode&&r.length>0)return r}async function rl(e,t){let r=rt(await p("adb",rr(e,["shell","cmd","package","has-feature",t]),{allowFailure:!0,timeoutMs:z})).toLowerCase();return!!r.includes("true")||!r.includes("false")&&null}async function rd(e){return(await Promise.all(re.map(async t=>await rl(e,t)))).some(e=>!0===e)}async function ru(e){var t;let r;return"tv"===((r=rt(await p("adb",rr(e,["shell","getprop","ro.build.characteristics"]),{allowFailure:!0,timeoutMs:z})).toLowerCase()).includes("tv")||r.includes("leanback")?"tv":null)||await rd(e)?"tv":(t=rt(await p("adb",rr(e,["shell","pm","list","features"]),{allowFailure:!0,timeoutMs:z})),/feature:android\.(software\.leanback(_only)?|hardware\.type\.television)\b/i.test(t))?"tv":"mobile"}async function rc(e={}){if(!await A("adb"))throw new I("TOOL_MISSING","adb not found in PATH");let t=e.serialAllowlist??eN(void 0),r=(await rp()).filter(e=>!t||t.has(e.serial));return await Promise.all(r.map(async({serial:e,rawModel:t})=>{let[r,i,n]=await Promise.all([ro(e,t),rw(e),ru(e)]);return{platform:"android",id:e,name:r,kind:ri(e)?"emulator":"device",target:n,booted:i}}))}async function rp(){return(await p("adb",["devices","-l"],{timeoutMs:z})).stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("List of devices")).map(e=>e.split(/\s+/)).filter(e=>"device"===e[1]).map(e=>({serial:e[0],rawModel:(e.find(e=>e.startsWith("model:"))??"").replace("model:","")}))}async function rf(){let e=await p("emulator",["-list-avds"],{allowFailure:!0,timeoutMs:z});if(0!==e.exitCode)throw new I("COMMAND_FAILED","Failed to list Android emulator AVDs",{stdout:e.stdout,stderr:e.stderr,exitCode:e.exitCode,hint:"Verify Android emulator tooling is installed and available in PATH."});return e.stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0)}async function rm(e){let t=Date.now();for(;Date.now()-t<e.timeoutMs;){try{let t=await rh(e.avdName,e.serial);if(t)return{platform:"android",id:t,name:e.avdName,kind:"emulator",target:"mobile",booted:!1}}catch{}await new Promise(e=>setTimeout(e,1e3))}throw new I("COMMAND_FAILED","Android emulator did not appear in time",{avdName:e.avdName,serial:e.serial,timeoutMs:e.timeoutMs,hint:"Check emulator logs and verify the AVD can start from command line."})}async function rh(e,t){let r=rn(e);for(let e of(await rp()).filter(e=>(!t||e.serial===t)&&ri(e.serial)))if(rn(e.rawModel)===r||rn(await ro(e.serial,e.rawModel))===r)return e.serial}async function rw(e){try{let t=await ra(e);return"1"===t.stdout.trim()}catch{return!1}}async function rg(e){var t,r;let i,n=e.avdName.trim();if(!n)throw new I("INVALID_ARGS","Android emulator boot requires a non-empty AVD name.");let a=e.timeoutMs??12e4;if(!await A("adb"))throw new I("TOOL_MISSING","adb not found in PATH");if(!await A("emulator"))throw new I("TOOL_MISSING","emulator not found in PATH");let o=await rf(),s=function(e,t){let r=e.find(e=>e===t);if(r)return r;let i=rn(t);return e.find(e=>rn(e)===i)}(o,n);if(!s)throw new I("DEVICE_NOT_FOUND",`No Android emulator AVD named ${e.avdName}`,{requestedAvdName:n,availableAvds:o,hint:"Run `emulator -list-avds` and pass an existing AVD name to --device."});let d=Date.now(),u=(t=await rc(),r=e.serial,i=rn(s),t.find(e=>"android"===e.platform&&"emulator"===e.kind&&(!r||e.id===r)&&rn(e.name)===i));if(!u){let t=["-avd",s];e.headless&&t.push("-no-window","-no-audio"),l("emulator",t)}let c=u??await rm({avdName:s,serial:e.serial,timeoutMs:a}),p=Math.max(1e3,a-(Date.now()-d));await rI(c.id,p);let f=(await rc()).find(e=>e.id===c.id);return f?{...f,name:s,booted:!0}:{...c,name:s,booted:!0}}async function rI(e,t=6e4){let r,i=K.fromTimeoutMs(t),n=Math.max(1,Math.ceil(t/1e3)),a=!1;try{await X(async({deadline:n})=>{if(n?.isExpired())throw a=!0,new I("COMMAND_FAILED","Android boot deadline exceeded",{serial:e,timeoutMs:t,elapsedMs:i.elapsedMs(),message:"timeout"});let o=Math.max(1e3,n?.remainingMs()??t),s=await ra(e,Math.min(o,z));if(r=s,"1"!==s.stdout.trim())throw new I("COMMAND_FAILED","Android device is still booting",{serial:e,stdout:s.stdout,stderr:s.stderr,exitCode:s.exitCode})},{maxAttempts:n,baseDelayMs:1e3,maxDelayMs:1e3,jitter:0,shouldRetry:e=>{let t=eu({error:e,stdout:r?.stdout,stderr:r?.stderr,context:{platform:"android",phase:"boot"}});return"ADB_TRANSPORT_UNAVAILABLE"!==t&&"ANDROID_BOOT_TIMEOUT"!==t}},{deadline:i,phase:"boot",classifyReason:e=>eu({error:e,stdout:r?.stdout,stderr:r?.stderr,context:{platform:"android",phase:"boot"}})})}catch(c){let n=w(c),o=r?.stdout,s=r?.stderr,l=r?.exitCode,d=eu({error:c,stdout:o,stderr:s,context:{platform:"android",phase:"boot"}});"BOOT_COMMAND_FAILED"===d&&"Android device is still booting"===n.message&&(d="ANDROID_BOOT_TIMEOUT");let u={serial:e,timeoutMs:t,elapsedMs:i.elapsedMs(),reason:d,hint:ec(d),stdout:o,stderr:s,exitCode:l};if(a||"ANDROID_BOOT_TIMEOUT"===d)throw new I("COMMAND_FAILED","Android device did not finish booting in time",u);if("TOOL_MISSING"===n.code)throw new I("TOOL_MISSING",n.message,{...u,...n.details??{}});if("ADB_TRANSPORT_UNAVAILABLE"===d)throw new I("COMMAND_FAILED",n.message,{...u,...n.details??{}});throw new I(n.code,n.message,{...u,...n.details??{}},n.cause)}}let rv={settings:{type:"intent",value:"android.settings.SETTINGS"}},rA="android.intent.category.LAUNCHER",ry="android.intent.category.LEANBACK_LAUNCHER",rb="android.intent.category.DEFAULT";async function rN(e,t){let r=t.trim();if(r.includes(".")&&!r.includes("/"))return{type:"package",value:r};let i=rv[r.toLowerCase()];if(i)return i;let n=(await p("adb",t4(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(r.toLowerCase()));if(1===n.length)return{type:"package",value:n[0]};if(n.length>1)throw new I("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:n});throw new I("APP_NOT_INSTALLED",`No package found matching "${t}"`)}async function rS(e,t="all"){let r=await r_(e);return("user-installed"===t?(await rE(e)).filter(e=>r.has(e)):Array.from(r)).sort((e,t)=>e.localeCompare(t)).map(e=>({package:e,name:function(e){let t=new Set(["com","android","google","app","apps","service","services","mobile","client"]),r=e.split(".").flatMap(e=>e.split(/[_-]+/)).map(e=>e.trim().toLowerCase()).filter(e=>e.length>0),i=r[r.length-1]??e;for(let e=r.length-1;e>=0;e-=1){let n=r[e];if(!t.has(n)){i=n;break}}return i.split(/[^a-z0-9]+/i).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}(e)}))}async function r_(e){let t=new Set;for(let r of rD(e,{includeFallbackWhenUnknown:!0})){let i=await p("adb",t4(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c",r]),{allowFailure:!0});if(0===i.exitCode&&0!==i.stdout.trim().length)for(let e of i.stdout.split("\n")){let r=e.trim();if(!r)continue;let i=r.split(/\s+/)[0],n=i.includes("/")?i.split("/")[0]:i;n&&t.add(n)}}return t}function rD(e,t={}){return"tv"===e.target?[ry]:"mobile"===e.target?[rA]:t.includeFallbackWhenUnknown?[rA,ry]:[rA]}async function rE(e){return(await p("adb",t4(e,["shell","pm","list","packages","-3"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function rk(e){let t=await rO(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let r=await rO(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return r||{}}async function rO(e,t){for(let r of t){let t=function(e){for(let t of[/mCurrentFocus=Window\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mFocusedApp=AppWindowToken\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/,/ResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/]){let r=t.exec(e);if(r)return{package:r[1],activity:r[2]}}return null}((await p("adb",t4(e,r),{allowFailure:!0})).stdout??"");if(t)return t}return null}async function rL(e,t,r){var i,n;let a;e.booted||await rI(e.id);let o=t.trim();if(t9(o)){if(r)throw new I("INVALID_ARGS","Activity override is not supported when opening a deep link URL");await p("adb",t4(e,["shell","am","start","-W","-a","android.intent.action.VIEW","-d",o]));return}let s=await rN(e,t),l=rD(e)[0]??rA;if("intent"===s.type){if(r)throw new I("INVALID_ARGS","Activity override requires a package name, not an intent");await p("adb",t4(e,["shell","am","start","-W","-a",s.value]));return}if(r){let t=r.includes("/")?r:`${s.value}/${r.startsWith(".")?r:`.${r}`}`;await p("adb",t4(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",rb,"-c",l,"-n",t]));return}let d=await p("adb",t4(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",rb,"-c",l,"-p",s.value]),{allowFailure:!0});if(0===d.exitCode&&(i=d.stdout,n=d.stderr,a=`${i}
11
+ ${e.stderr}`}function rr(e,t){return["-s",e,...t]}function ri(e){return e.startsWith("emulator-")}function rn(e){return e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim()}async function ra(e,t=z){return p("adb",rr(e,["shell","getprop","sys.boot_completed"]),{allowFailure:!0,timeoutMs:t})}async function ro(e,t){let r=t.replace(/_/g," ").trim();if(!ri(e))return r||e;let i=await rs(e);return i?i.replace(/_/g," "):r||e}async function rs(e){for(let t of["ro.boot.qemu.avd_name","persist.sys.avd_name"]){let r=await p("adb",rr(e,["shell","getprop",t]),{allowFailure:!0,timeoutMs:1e4}),i=r.stdout.trim();if(0===r.exitCode&&i.length>0)return i}let t=await p("adb",rr(e,["emu","avd","name"]),{allowFailure:!0,timeoutMs:1e4}),r=t.stdout.trim();if(0===t.exitCode&&r.length>0)return r}async function rl(e,t){let r=rt(await p("adb",rr(e,["shell","cmd","package","has-feature",t]),{allowFailure:!0,timeoutMs:z})).toLowerCase();return!!r.includes("true")||!r.includes("false")&&null}async function rd(e){return(await Promise.all(re.map(async t=>await rl(e,t)))).some(e=>!0===e)}async function ru(e){var t;let r;return"tv"===((r=rt(await p("adb",rr(e,["shell","getprop","ro.build.characteristics"]),{allowFailure:!0,timeoutMs:z})).toLowerCase()).includes("tv")||r.includes("leanback")?"tv":null)||await rd(e)?"tv":(t=rt(await p("adb",rr(e,["shell","pm","list","features"]),{allowFailure:!0,timeoutMs:z})),/feature:android\.(software\.leanback(_only)?|hardware\.type\.television)\b/i.test(t))?"tv":"mobile"}async function rc(e={}){if(!await A("adb"))throw new I("TOOL_MISSING","adb not found in PATH");let t=e.serialAllowlist??eN(void 0),r=(await rp()).filter(e=>!t||t.has(e.serial));return await Promise.all(r.map(async({serial:e,rawModel:t})=>{let[r,i,n]=await Promise.all([ro(e,t),rw(e),ru(e)]);return{platform:"android",id:e,name:r,kind:ri(e)?"emulator":"device",target:n,booted:i}}))}async function rp(){return(await p("adb",["devices","-l"],{timeoutMs:z})).stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("List of devices")).map(e=>e.split(/\s+/)).filter(e=>"device"===e[1]).map(e=>({serial:e[0],rawModel:(e.find(e=>e.startsWith("model:"))??"").replace("model:","")}))}async function rf(){let e=await p("emulator",["-list-avds"],{allowFailure:!0,timeoutMs:z});if(0!==e.exitCode)throw new I("COMMAND_FAILED","Failed to list Android emulator AVDs",{stdout:e.stdout,stderr:e.stderr,exitCode:e.exitCode,hint:"Verify Android emulator tooling is installed and available in PATH."});return e.stdout.split("\n").map(e=>e.trim()).filter(e=>e.length>0)}async function rm(e){let t=Date.now();for(;Date.now()-t<e.timeoutMs;){try{let t=await rh(e.avdName,e.serial);if(t)return{platform:"android",id:t,name:e.avdName,kind:"emulator",target:"mobile",booted:!1}}catch{}await new Promise(e=>setTimeout(e,1e3))}throw new I("COMMAND_FAILED","Android emulator did not appear in time",{avdName:e.avdName,serial:e.serial,timeoutMs:e.timeoutMs,hint:"Check emulator logs and verify the AVD can start from command line."})}async function rh(e,t){let r=rn(e);for(let e of(await rp()).filter(e=>(!t||e.serial===t)&&ri(e.serial)))if(rn(e.rawModel)===r||rn(await ro(e.serial,e.rawModel))===r)return e.serial}async function rw(e){try{let t=await ra(e);return"1"===t.stdout.trim()}catch{return!1}}async function rg(e){var t,r;let i,n=e.avdName.trim();if(!n)throw new I("INVALID_ARGS","Android emulator boot requires a non-empty AVD name.");let a=e.timeoutMs??12e4;if(!await A("adb"))throw new I("TOOL_MISSING","adb not found in PATH");if(!await A("emulator"))throw new I("TOOL_MISSING","emulator not found in PATH");let o=await rf(),s=function(e,t){let r=e.find(e=>e===t);if(r)return r;let i=rn(t);return e.find(e=>rn(e)===i)}(o,n);if(!s)throw new I("DEVICE_NOT_FOUND",`No Android emulator AVD named ${e.avdName}`,{requestedAvdName:n,availableAvds:o,hint:"Run `emulator -list-avds` and pass an existing AVD name to --device."});let d=Date.now(),u=(t=await rc(),r=e.serial,i=rn(s),t.find(e=>"android"===e.platform&&"emulator"===e.kind&&(!r||e.id===r)&&rn(e.name)===i));if(!u){let t=["-avd",s];e.headless&&t.push("-no-window","-no-audio"),l("emulator",t)}let c=u??await rm({avdName:s,serial:e.serial,timeoutMs:a}),p=Math.max(1e3,a-(Date.now()-d));await rI(c.id,p);let f=(await rc()).find(e=>e.id===c.id);return f?{...f,name:s,booted:!0}:{...c,name:s,booted:!0}}async function rI(e,t=6e4){let r,i=K.fromTimeoutMs(t),n=Math.max(1,Math.ceil(t/1e3)),a=!1;try{await X(async({deadline:n})=>{if(n?.isExpired())throw a=!0,new I("COMMAND_FAILED","Android boot deadline exceeded",{serial:e,timeoutMs:t,elapsedMs:i.elapsedMs(),message:"timeout"});let o=Math.max(1e3,n?.remainingMs()??t),s=await ra(e,Math.min(o,z));if(r=s,"1"!==s.stdout.trim())throw new I("COMMAND_FAILED","Android device is still booting",{serial:e,stdout:s.stdout,stderr:s.stderr,exitCode:s.exitCode})},{maxAttempts:n,baseDelayMs:1e3,maxDelayMs:1e3,jitter:0,shouldRetry:e=>{let t=eu({error:e,stdout:r?.stdout,stderr:r?.stderr,context:{platform:"android",phase:"boot"}});return"ADB_TRANSPORT_UNAVAILABLE"!==t&&"ANDROID_BOOT_TIMEOUT"!==t}},{deadline:i,phase:"boot",classifyReason:e=>eu({error:e,stdout:r?.stdout,stderr:r?.stderr,context:{platform:"android",phase:"boot"}})})}catch(c){let n=w(c),o=r?.stdout,s=r?.stderr,l=r?.exitCode,d=eu({error:c,stdout:o,stderr:s,context:{platform:"android",phase:"boot"}});"BOOT_COMMAND_FAILED"===d&&"Android device is still booting"===n.message&&(d="ANDROID_BOOT_TIMEOUT");let u={serial:e,timeoutMs:t,elapsedMs:i.elapsedMs(),reason:d,hint:ec(d),stdout:o,stderr:s,exitCode:l};if(a||"ANDROID_BOOT_TIMEOUT"===d)throw new I("COMMAND_FAILED","Android device did not finish booting in time",u);if("TOOL_MISSING"===n.code)throw new I("TOOL_MISSING",n.message,{...u,...n.details??{}});if("ADB_TRANSPORT_UNAVAILABLE"===d)throw new I("COMMAND_FAILED",n.message,{...u,...n.details??{}});throw new I(n.code,n.message,{...u,...n.details??{}},n.cause)}}let rv={settings:{type:"intent",value:"android.settings.SETTINGS"}},rA="android.intent.category.LAUNCHER",ry="android.intent.category.LEANBACK_LAUNCHER",rb="android.intent.category.DEFAULT";async function rN(e,t){let r=t.trim();if(r.includes(".")&&!r.includes("/"))return{type:"package",value:r};let i=rv[r.toLowerCase()];if(i)return i;let n=(await p("adb",t4(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(r.toLowerCase()));if(1===n.length)return{type:"package",value:n[0]};if(n.length>1)throw new I("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:n});throw new I("APP_NOT_INSTALLED",`No package found matching "${t}"`)}async function rS(e,t="all"){let r=await r_(e);return("user-installed"===t?(await rE(e)).filter(e=>r.has(e)):Array.from(r)).sort((e,t)=>e.localeCompare(t)).map(e=>({package:e,name:function(e){let t=new Set(["com","android","google","app","apps","service","services","mobile","client"]),r=e.split(".").flatMap(e=>e.split(/[_-]+/)).map(e=>e.trim().toLowerCase()).filter(e=>e.length>0),i=r[r.length-1]??e;for(let e=r.length-1;e>=0;e-=1){let n=r[e];if(!t.has(n)){i=n;break}}return i.split(/[^a-z0-9]+/i).filter(Boolean).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}(e)}))}async function r_(e){let t=new Set;for(let r of rD(e,{includeFallbackWhenUnknown:!0})){let i=await p("adb",t4(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c",r]),{allowFailure:!0});if(0===i.exitCode&&0!==i.stdout.trim().length)for(let e of i.stdout.split("\n")){let r=e.trim();if(!r)continue;let i=r.split(/\s+/)[0],n=i.includes("/")?i.split("/")[0]:i;n&&t.add(n)}}return t}function rD(e,t={}){return"tv"===e.target?[ry]:"mobile"===e.target?[rA]:t.includeFallbackWhenUnknown?[rA,ry]:[rA]}async function rE(e){return(await p("adb",t4(e,["shell","pm","list","packages","-3"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function rk(e){let t=await rO(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let r=await rO(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return r||{}}async function rO(e,t){for(let r of t){let t=function(e){for(let t of[/mCurrentFocus=Window\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mFocusedApp=AppWindowToken\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/,/ResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/]){let r=t.exec(e);if(r)return{package:r[1],activity:r[2]}}return null}((await p("adb",t4(e,r),{allowFailure:!0})).stdout??"");if(t)return t}return null}async function rL(e,t,r){var i,n;let a;e.booted||await rI(e.id);let o=t.trim();if(t9(o)){if(r)throw new I("INVALID_ARGS","Activity override is not supported when opening a deep link URL");await p("adb",t4(e,["shell","am","start","-W","-a","android.intent.action.VIEW","-d",o]));return}let s=await rN(e,t),l=rD(e)[0]??rA;if("intent"===s.type){if(r)throw new I("INVALID_ARGS","Activity override requires a package name, not an intent");await p("adb",t4(e,["shell","am","start","-W","-a",s.value]));return}if(r){let t=r.includes("/")?r:`${s.value}/${r.startsWith(".")?r:`.${r}`}`;await p("adb",t4(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",rb,"-c",l,"-n",t]));return}let d=await p("adb",t4(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",rb,"-c",l,"-p",s.value]),{allowFailure:!0});if(0===d.exitCode&&(i=d.stdout,n=d.stderr,a=`${i}
12
12
  ${n}`,!/Error:.*(?:Activity not started|unable to resolve Intent)/i.test(a)))return;let u=await rM(e,s.value);if(!u)throw new I("COMMAND_FAILED",`Failed to launch ${s.value}`,{stdout:d.stdout,stderr:d.stderr});await p("adb",t4(e,["shell","am","start","-W","-a","android.intent.action.MAIN","-c",rb,"-c",l,"-n",u]))}async function rM(e,t){for(let r of Array.from(new Set(rD(e,{includeFallbackWhenUnknown:!0})))){let i=await p("adb",t4(e,["shell","cmd","package","resolve-activity","--brief","-a","android.intent.action.MAIN","-c",r,t]),{allowFailure:!0});if(0!==i.exitCode)continue;let n=function(e){let t=e.split("\n").map(e=>e.trim()).filter(Boolean);for(let e=t.length-1;e>=0;e-=1){let r=t[e];if(r.includes("/"))return r.split(/\s+/)[0]}return null}(i.stdout);if(n)return n}return null}async function rx(e){e.booted||await rI(e.id)}async function rC(e,t){if("settings"===t.trim().toLowerCase())return void await p("adb",t4(e,["shell","am","force-stop","com.android.settings"]));let r=await rN(e,t);if("intent"===r.type)throw new I("INVALID_ARGS","Close requires a package name, not an intent");await p("adb",t4(e,["shell","am","force-stop",r.value]))}async function rR(e,t){let r=await rN(e,t);if("intent"===r.type)throw new I("INVALID_ARGS","App uninstall requires a package name, not an intent");let i=await p("adb",t4(e,["uninstall",r.value]),{allowFailure:!0});if(0!==i.exitCode){let e=`${i.stdout}
13
13
  ${i.stderr}`.toLowerCase();if(!e.includes("unknown package")&&!e.includes("not installed"))throw new I("COMMAND_FAILED",`adb uninstall failed for ${r.value}`,{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode})}return{package:r.value}}let rT=null;async function rP(){let e=`${process.env.PATH??""}::${process.env.AGENT_DEVICE_BUNDLETOOL_JAR??""}`;if(rT?.key===e)return rT.invocation;if(await A("bundletool")){let t={cmd:"bundletool",prefixArgs:[]};return rT={key:e,invocation:t},t}let t=process.env.AGENT_DEVICE_BUNDLETOOL_JAR?.trim();if(!t)throw new I("TOOL_MISSING","bundletool not found in PATH. Install bundletool or set AGENT_DEVICE_BUNDLETOOL_JAR to a bundletool-all.jar path.");try{await x.access(t)}catch{throw new I("TOOL_MISSING",`AGENT_DEVICE_BUNDLETOOL_JAR points to a missing file: ${t}`)}let r={cmd:"java",prefixArgs:["-jar",t]};return rT={key:e,invocation:r},r}async function r$(e){let t=await rP();await p(t.cmd,[...t.prefixArgs,...e])}async function rF(e,t){let r,i=await x.mkdtemp(n.join(V.tmpdir(),"agent-device-aab-")),a=n.join(i,"bundle.apks"),o=(r=process.env.AGENT_DEVICE_ANDROID_BUNDLETOOL_MODE?.trim())&&r.length>0?r:"universal";try{await r$(["build-apks","--bundle",t,"--output",a,"--mode",o]),await r$(["install-apks","--apks",a,"--device-id",e.id])}finally{await x.rm(i,{recursive:!0,force:!0})}}async function rV(e,t){".aab"===n.extname(t).toLowerCase()?await rF(e,t):await p("adb",t4(e,["install","-r",t]))}async function rU(e,t){e.booted||await rI(e.id),await rV(e,t)}async function rG(e,t,r){e.booted||await rI(e.id);let{package:i}=await rR(e,t);return await rV(e,r),{package:i}}function rB(e){let t=rj(e),r=e=>{let r=rq(t,e);if(null!==r)return"true"===r};return{text:rq(t,"text"),desc:rq(t,"content-desc"),resourceId:rq(t,"resource-id"),className:rq(t,"class"),bounds:rq(t,"bounds"),clickable:r("clickable"),enabled:r("enabled"),focusable:r("focusable"),focused:r("focused")}}function rj(e){let t=new Map,r=e.indexOf(" "),i=e.lastIndexOf(">");if(r<0||i<=r)return t;let n=/([^\s=/>]+)\s*=\s*(["'])([\s\S]*?)\2/y,a=r;for(;a<i;){for(;a<i;){let t=e[a];if(" "!==t&&"\n"!==t&&"\r"!==t&&" "!==t)break;a+=1}if(a>=i)break;let r=e[a];if("/"===r||">"===r)break;n.lastIndex=a;let o=n.exec(e);if(!o)break;t.set(o[1],o[3]),a=n.lastIndex}return t}function rq(e,t){return e.get(t)??null}function rH(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let r=Number(t[1]),i=Number(t[2]);return{x:r,y:i,width:Math.max(0,Number(t[3])-r),height:Math.max(0,Number(t[4])-i)}}function rW(e){return e?e.toLowerCase():""}function rJ(e){let t=e.trim();return!!t&&/^[\w.]+:id\/[\w.-]+$/i.test(t)}async function rz(e,t={}){return function(e,t,r){let i=function(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},r=[t],i=/<node\b[^>]*>|<\/node>/g,n=i.exec(e);for(;n;){let t=n[0];if(t.startsWith("</node")){r.length>1&&r.pop(),n=i.exec(e);continue}let a=rB(t),o=rH(a.bounds),s=r[r.length-1],l={type:a.className,label:a.text||a.desc,value:a.text,identifier:a.resourceId,rect:o,enabled:a.enabled,hittable:a.clickable??a.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||r.push(l),n=i.exec(e)}return t}(e),n=[],a=!1,o=r.depth??1/0,s=r.scope?function(e,t){let r=t.toLowerCase(),i=[...e.children];for(;i.length>0;){let e=i.shift(),t=e.label?.toLowerCase()??"",n=e.value?.toLowerCase()??"",a=e.identifier?.toLowerCase()??"";if(t.includes(r)||n.includes(r)||a.includes(r))return e;i.push(...e.children)}return null}(i,r.scope):null,l=s?[s]:i.children,d=new Map,u=e=>{let t=d.get(e);if(void 0!==t)return t;for(let t of e.children)if(t.hittable||u(t))return d.set(e,!0),!0;return d.set(e,!1),!1},c=(e,t,i,s=!1,l=!1)=>{var d,p,f,m,h,w;let g,I,v,A,y,b,N,S;if(n.length>=800){a=!0;return}if(t>o)return;let _=!!r.raw||(d=e,p=r,f=s,m=u(e),h=l,I=rW(d.type),v=!!(d.label&&d.label.trim().length>0),A=!!(d.identifier&&d.identifier.trim().length>0),y=v&&!rJ(d.label??""),b=A&&!rJ(d.identifier??""),N=(g=(w=I).split(".").pop()??w).includes("layout")||"viewgroup"===g||"view"===g,S="imageview"===I||"imagebutton"===I,p.interactiveOnly?!!d.hittable||!!(y||b)&&!S&&(!N||!!h)&&(f||m||h):p.compact?y||b||!!d.hittable:!N&&!S||!!d.hittable||!!y||!!b&&!!m||m),D=i;_&&(D=n.length,n.push({index:D,type:e.type??void 0,label:e.label??void 0,value:e.value??void 0,identifier:e.identifier??void 0,rect:e.rect,enabled:e.enabled,hittable:e.hittable,depth:t,parentIndex:i}));let E=s||!!e.hittable,k=l||function(e){if(!e)return!1;let t=rW(e);return t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")}(e.type);for(let r of e.children)if(c(r,t+1,D,E,k),a)return};for(let e of l)if(c(e,0,void 0,!1,!1),a)break;return a?{nodes:n,truncated:a}:{nodes:n}}(await rY(e),0,t)}let rK=Buffer.from([137,80,78,71,13,10,26,10]);async function rX(e,t){let r=await p("adb",t4(e,["exec-out","screencap","-p"]),{binaryStdout:!0});if(!r.stdoutBuffer)throw new I("COMMAND_FAILED","Failed to capture screenshot");let i=r.stdoutBuffer.indexOf(rK);if(i<0)throw new I("COMMAND_FAILED","Screenshot data does not contain a valid PNG header");let n=function(e,t){let r=t+rK.length;for(;r+8<=e.length;){let t=e.readUInt32BE(r),i=r+4,n=e.toString("ascii",i,i+4),a=r+12+t;if(a>e.length)break;if("IEND"===n)return a;r=a}return null}(r.stdoutBuffer,i);if(!n)throw new I("COMMAND_FAILED","Screenshot data does not contain a complete PNG payload");await x.writeFile(t,r.stdoutBuffer.subarray(i,n))}async function rY(e){return Y(()=>rZ(e),{shouldRetry:r0})}async function rZ(e){var t,r,i;let n,a,o=await p("adb",t4(e,["exec-out","uiautomator","dump","/dev/tty"]),{allowFailure:!0});if(0===o.exitCode){let e=rQ(o.stdout,o.stderr);if(e)return e}let s="/sdcard/window_dump.xml",l=await p("adb",t4(e,["shell","uiautomator","dump",s])),d=(t=s,r=l.stdout,i=l.stderr,n=`${r}
14
14
  ${i}`,a=/dumped to:\s*(\S+)/i.exec(n),a?.[1]??t),u=await p("adb",t4(e,["shell","cat",d])),c=rQ(u.stdout,u.stderr);if(!c)throw new I("COMMAND_FAILED","uiautomator dump did not return XML",{stdout:u.stdout,stderr:u.stderr});return c}function rQ(e,t){let r=`${e}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.7.16",
3
+ "version": "0.7.17",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",