@buoy-gg/mcp 7.0.76 → 7.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/{camera-V7J5ZQUM.mjs → camera-6HTJDU4Y.mjs} +1 -1
- package/dist/{chunk-GDVMXPFL.mjs → chunk-2IWYZICF.mjs} +1 -1
- package/dist/{chunk-KJOH5NDI.mjs → chunk-3STZVJQG.mjs} +1 -1
- package/dist/{chunk-FRGTROJC.mjs → chunk-5NOZVPWB.mjs} +1 -1
- package/dist/{chunk-PAOWVPJM.mjs → chunk-D5K6WO6B.mjs} +1 -1
- package/dist/{chunk-WKQTAALH.mjs → chunk-HEPFVYZG.mjs} +75 -74
- package/dist/{chunk-ZRL5AY5M.mjs → chunk-LHGIYW7N.mjs} +3 -3
- package/dist/{chunk-TKNYLZJ4.mjs → chunk-ODGXMS2O.mjs} +19 -19
- package/dist/{chunk-RVUAVOJU.mjs → chunk-XA74T4JN.mjs} +2 -2
- package/dist/cli.js +11 -10
- package/dist/cli.mjs +7 -7
- package/dist/{flowRun-A5KBJUS5.mjs → flowRun-XEK65IL7.mjs} +1 -1
- package/dist/index.js +49 -48
- package/dist/index.mjs +1 -1
- package/dist/{init-KDX5VSCZ.mjs → init-VCQMVZXB.mjs} +1 -1
- package/dist/{main-B5VNTI32.mjs → main-6OX5577P.mjs} +2 -2
- package/dist/{policy-72DKIGU2.mjs → policy-UNODNYFR.mjs} +1 -1
- package/dist/{simFarm-4UNZBEMG.mjs → simFarm-R4P6QXRK.mjs} +1 -1
- package/dist/{src-IQBNAXE4.mjs → src-R7ICMP6Q.mjs} +1 -1
- package/package.json +4 -4
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{createRequire as So}from"module";const Yr=So(import.meta.url);var te=1;var et=1;var Et={parkAfterMinutes:10,maxConcurrentBoots:3,reserveRamGB:24,reserveDiskGB:40,slimSimGB:2,stockSimGB:6,cloneDiskGB:2,freezePilot:false,personLeaseHours:8,runCapMinutes:60,keepRunsHours:24,fairMinFreeShare:.25,fairMaxSwingShare:.12,fairMaxThermalLevel:0,fairMaxSwapGrowthMB:256,phoneProfile:"iphone-se-2nd-gen",phoneOnLimit:"warn",phoneCapMB:0,trimIdleHours:24,keepWarmPerTemplate:2};var p=class extends Error{constructor(e,t,s,i=false){super(t);this.code=e;this.next=s;this.retryable=i;this.name="FarmError"}toJSON(){return{code:this.code,message:this.message,next:this.next,retryable:this.retryable}}};import{createHash as bo}from"crypto";var ks=["com.apple.sharingd"];var Ce=[{"id":"widgets","name":"Widgets & Wallpaper","downside":"Home and Lock Screen widgets, wallpaper posters, and Live Activities stop updating.","approxMemoryMB":675,"labels":["com.apple.PosterBoard","com.apple.chronod","com.apple.liveactivitiesd"]},{"id":"siri","name":"Siri & Intelligence","downside":"Siri, speech features, and Apple Intelligence services are unavailable.","approxMemoryMB":265,"labels":["com.apple.assistantd","com.apple.assistant_cdmd","com.apple.assistant_service","com.apple.siriactionsd","com.apple.siriinferenced","com.apple.siriknowledged","com.apple.sirittsd","com.apple.siri.context.service","com.apple.siri.acousticsignature","com.apple.corespeechd","com.apple.voiced","com.apple.voicebankingd","com.apple.speechmodeltrainingd","com.apple.intelligenceplatformd","com.apple.intelligencecontextd","com.apple.intelligenceflowd","com.apple.intelligencetasksd","com.apple.generativeexperiencesd","com.apple.knowledgeconstructiond","com.apple.naturallanguaged","com.apple.textunderstandingd","com.apple.modelcatalogd","com.apple.modelmanagerd","com.apple.mlhostd","com.apple.mlruntimed","com.apple.suggestd","com.apple.parsecd","com.apple.parsec-fbf","com.apple.proactiveeventtrackerd"]},{"id":"search","name":"Spotlight & Search","downside":"Spotlight and Settings search return no results.","approxMemoryMB":50,"labels":["com.apple.searchd","com.apple.searchtoold","com.apple.spotlightknowledged","com.apple.spotlightknowledged.updater","com.apple.corespotlightservice"]},{"id":"icloud","name":"iCloud & Apple Account","downside":"iCloud sync, Apple Account, Keychain, and backup workflows will not work.","approxMemoryMB":100,"labels":["com.apple.appleaccountd","com.apple.appleaccounttransparencyd","com.apple.appleidsetupd","com.apple.akd","com.apple.amsaccountsd","com.apple.amsengagementd","com.apple.amsondevicestoraged","com.apple.cloudd","com.apple.cloudphotod","com.apple.ckdiscretionaryd","com.apple.cloudsettingssyncagent","com.apple.bird","com.apple.syncdefaultsd","com.apple.cdpd","com.apple.sosd","com.apple.SecureBackupDaemon","com.apple.TrustedPeersHelper","com.apple.protectedcloudstorage.protectedcloudkeysyncing","com.apple.icloudmailagent","com.apple.icloudsubscriptionoptimizerd","com.apple.communicationtrustd"]},{"id":"store","name":"App Store, Push & Media","downside":"Remote push notifications and StoreKit or App Store testing will not work.","approxMemoryMB":80,"labels":["com.apple.appstored","com.apple.appstorecomponentsd","com.apple.apsd","com.apple.itunescloudd","com.apple.itunesstored","com.apple.storekitd","com.apple.amsaccountsd","com.apple.amsengagementd","com.apple.amsondevicestoraged","com.apple.passd","com.apple.financed","com.apple.videosubscriptionsd","com.apple.assetsubscriptiond","com.apple.musicd"]},{"id":"pim","name":"Mail, Calendar & Contacts","downside":"Contacts, Calendar, Reminders, and Mail-backed pickers or sync may fail.","approxMemoryMB":80,"labels":["com.apple.email.maild","com.apple.exchangesyncd","com.apple.dataaccess.dataaccessd","com.apple.calaccessd","com.apple.remindd","com.apple.contactsd","com.apple.contacts.postersyncd","com.apple.peopled"]},{"id":"web","name":"Safari Sync & Web Services","downside":"Universal links and Safari sync or background web services will not work.","approxMemoryMB":50,"labels":["com.apple.SafariBookmarksSyncAgent","com.apple.Safari.History","com.apple.Safari.passwordbreachd","com.apple.Safari.SafeBrowsing.Service","com.apple.safarifetcherd","com.apple.WebBookmarks.webbookmarksd","com.apple.webkit.adattributiond","com.apple.webkit.webpushd","com.apple.webprivacyd","com.apple.swcd"]},{"id":"family","name":"Family & Screen Time","downside":"Family Sharing, Screen Time, and usage tracking stop working.","approxMemoryMB":65,"labels":["com.apple.familycircled","com.apple.FamilyControlsAgent","com.apple.familynotification","com.apple.askpermissiond","com.apple.asktod","com.apple.ScreenTimeAgent","com.apple.ScreenTimeSettingsAgent","com.apple.UsageTrackingAgent"]},{"id":"health","name":"Health, Home & Fitness","downside":"HealthKit, HomeKit, and Fitness integrations will not work.","approxMemoryMB":135,"labels":["com.apple.healthd","com.apple.healthappd","com.apple.healthcontentd","com.apple.healtheventsd","com.apple.healthrecordsd","com.apple.finhealthd","com.apple.homed","com.apple.homeeventsd","com.apple.fitcore","com.apple.fitcore.session","com.apple.fitnesscoachingd","com.apple.fitnessintelligenced","com.apple.activityawardsd","com.apple.activitysharingd"]},{"id":"photos","name":"Photos & Media Analysis","downside":"Photo picker, Photos-library workflows, and media analysis may fail.","approxMemoryMB":60,"labels":["com.apple.photoanalysisd","com.apple.photosface","com.apple.mediaanalysisd","com.apple.mediaanalysisd.service","com.apple.mediastream.mstreamd","com.apple.medialibraryd","com.apple.assetsd","com.apple.assetsd.nebulad"]},{"id":"apps","name":"News, Weather, Maps & Games","downside":"News, Weather, Maps background data, and game-controller services are unavailable.","approxMemoryMB":90,"labels":["com.apple.newsd","com.apple.weatherd","com.apple.Maps.mapssyncd","com.apple.Maps.mapspushd","com.apple.Maps.geocorrectiond","com.apple.maps.destinationd","com.apple.MapKit.SnapshotService","com.apple.jetpackassetd","com.apple.tipsd","com.apple.gamed","com.apple.gamesaved","com.apple.GameController.gamecontrollerd"]},{"id":"messaging","name":"Messaging & FaceTime","downside":"iMessage, FaceTime, and related identity services will not work.","approxMemoryMB":60,"labels":["com.apple.identityservicesd","com.apple.ids_simd","com.apple.imautomatichistorydeletionagent","com.apple.imcore.imtransferagent","com.apple.imdpersistence.IMDPersistenceAgent","com.apple.facetimemessagestored","com.apple.telephonyutilities.callservicesd"]},{"id":"connectivity","name":"Sharing & Device Connectivity","downside":"AirDrop, Continuity, CarPlay, Watch, and Find My connectivity will not work.","approxMemoryMB":65,"labels":["com.apple.rapportd","com.apple.companiond","com.apple.carkitd","com.apple.wcd","com.apple.tvremoted","com.apple.avatarsd","com.apple.stickersd","com.apple.sociallayerd","com.apple.announced","com.apple.navd","com.apple.findmy.findmylocated"]},{"id":"telemetry","name":"Ads, Diagnostics & Telemetry","downside":"DeviceCheck plus analytics, diagnostics, and feedback services are unavailable.","approxMemoryMB":105,"labels":["com.apple.ap.adprivacyd","com.apple.ap.promotedcontentd","com.apple.diagnosticextensionsd","com.apple.feedbackd","com.apple.rtcreportingd","com.apple.securityuploadd","com.apple.geoanalyticsd","com.apple.triald","com.apple.followupd","com.apple.purplebuddy.budd","com.apple.devicecheckd"]},{"id":"other","name":"Other Background Services","downside":"Wallet, merchant, business, asset, and miscellaneous background services are unavailable.","approxMemoryMB":195,"labels":["com.apple.financed","com.apple.passd","com.apple.merchantd","com.apple.coreidvd","com.apple.businessservicesd","com.apple.deviceaccessd","com.apple.replicatord","com.apple.linkd","com.apple.ind","com.apple.storagedatad","com.apple.StatusKitAgent","com.apple.countryd","com.apple.mobileassetd","com.apple.managedconfiguration.passcodenagd"]}];function Ms(){const e=new Set(ks);const t=new Set;for(const s of Ce)for(const i of s.labels)if(!e.has(i))t.add(i);return[...t].sort()}function Ee(e){return e==="slim"?Ms():[]}function _s(e){return bo("sha256").update(e+"\n"+Ee(e).join("\n")).digest("hex").slice(0,16)}import{promises as H,existsSync as $t,readFileSync as ko}from"fs";import C from"path";import{AsyncLocalStorage as Mo}from"async_hooks";import{createHash as _o,randomBytes as we}from"crypto";import{promises as $e}from"fs";import It from"path";import{randomBytes as Eo}from"crypto";import{spawn as $o}from"child_process";var xe=new Set;function Bt(){return xe.size}function Io(){for(const e of xe){try{e.kill("SIGKILL")}catch{}}}function _(e,t,s={}){const{timeoutMs:i=3e4,maxBytes:o=4*1024*1024}=s;return new Promise(n=>{const a=$o(e,t,{env:s.env??process.env,stdio:[s.input==null?"ignore":"pipe","pipe","pipe"]});xe.add(a);let r="";let c="";let l=false;a.stdout.on("data",h=>{if(r.length<o)r+=h.toString("utf8")});a.stderr.on("data",h=>{if(c.length<o)c+=h.toString("utf8")});if(s.input!=null)a.stdin.end(s.input);const d=setTimeout(()=>{l=true;a.kill("SIGTERM");setTimeout(()=>a.kill("SIGKILL"),2e3).unref?.()},i);a.on("error",h=>{xe.delete(a);clearTimeout(d);n({code:null,stdout:r,stderr:c+String(h),timedOut:l})});a.on("close",h=>{xe.delete(a);clearTimeout(d);n({code:h,stdout:r,stderr:c,timedOut:l})})})}async function U(e,t,s={}){const i=await _(e,t,s);if(i.timedOut)throw new Error(`${e} ${t.slice(0,3).join(" ")} timed out after ${s.timeoutMs??3e4} ms`);if(i.code!==0)throw new Error(`${e} ${t.slice(0,3).join(" ")} failed (${i.code}): ${(i.stderr||i.stdout).trim().slice(0,500)}`);return i.stdout}async function tt(){const e=await U("ps",["-axww","-o","pid=,ppid=,lstart=,stat=,comm="],{timeoutMs:15e3});const t=await U("ps",["-axww","-o","pid=,command="],{timeoutMs:15e3});const s=new Map;for(const o of t.split("\n")){const n=/^\s*(\d+)\s+(.*)$/.exec(o);if(n)s.set(Number(n[1]),n[2])}const i=[];for(const o of e.split("\n")){const n=/^\s*(\d+)\s+(\d+)\s+(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})\s+(\S+)\s+(.*)$/.exec(o);if(!n)continue;const a=Number(n[1]);i.push({pid:a,ppid:Number(n[2]),start:n[3].replace(/\s+/g," "),stat:n[4],comm:n[5].trim(),command:s.get(a)??n[5].trim()})}return i}function Ot(e,t){const s=new Map;const i=new Map;for(const c of e){i.set(c.pid,c);const l=s.get(c.ppid)??[];l.push(c);s.set(c.ppid,l)}const o=[];const n=i.get(t);if(!n)return o;const a=[n];const r=new Set;while(a.length){const c=a.pop();if(r.has(c.pid))continue;r.add(c.pid);o.push(c);for(const l of s.get(c.pid)??[])a.push(l)}return o}async function Ie(e){const t=await _("ps",["-o","lstart=","-p",String(e)],{timeoutMs:5e3});const s=t.stdout.trim().replace(/\s+/g," ");return t.code===0&&s?s:null}async function J(e,t,s=false){if(!Number.isInteger(e)||e<=0)return false;try{process.kill(e,0)}catch(o){if(o.code!=="EPERM")return false}if(!t)return true;const i=await Ie(e);if(i===null)return s;return i===t}function st(e=process.env){return Object.fromEntries(Object.entries(e).filter(([t])=>!t.startsWith("SIMCTL_CHILD_")))}var T=e=>new Promise(t=>setTimeout(t,e));var Be=process.env.BUOY_FARM_DISABLED_ROOT||"/private/var/tmp";function Es(e,t=Be){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return It.join(t,`com.apple.CoreSimulator.SimDevice.${e}`)}function $s(e,t=Be){return It.join(Es(e,t),"disabled.plist")}async function Is(e,t=Be){const s=$s(e,t);let i;try{i=await $e.lstat(s)}catch{return{}}if(!i.isFile())throw new Error(`refusing ${s}: not a regular file`);const o=await U("plutil",["-convert","json","-o","-",s],{timeoutMs:1e4});const n=JSON.parse(o);const a={};for(const[r,c]of Object.entries(n))if(typeof c==="boolean")a[r]=c;return a}function Bo(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function Oo(e){const t=Object.keys(e).sort();let s='<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n';for(const i of t)s+=` <key>${Bo(i)}</key>
|
|
1
|
+
import{createRequire as So}from"module";const zr=So(import.meta.url);var te=1;var et=1;var Et={parkAfterMinutes:10,maxConcurrentBoots:3,reserveRamGB:24,reserveDiskGB:40,slimSimGB:2,stockSimGB:6,cloneDiskGB:2,freezePilot:false,personLeaseHours:8,runCapMinutes:60,keepRunsHours:24,fairMinFreeShare:.25,fairMaxSwingShare:.12,fairMaxThermalLevel:0,fairMaxSwapGrowthMB:256,phoneProfile:"iphone-se-2nd-gen",phoneOnLimit:"warn",phoneCapMB:0,trimIdleHours:24,keepWarmPerTemplate:2};var p=class extends Error{constructor(e,t,s,i=false){super(t);this.code=e;this.next=s;this.retryable=i;this.name="FarmError"}toJSON(){return{code:this.code,message:this.message,next:this.next,retryable:this.retryable}}};import{createHash as bo}from"crypto";var ks=["com.apple.sharingd"];var Ce=[{"id":"widgets","name":"Widgets & Wallpaper","downside":"Home and Lock Screen widgets, wallpaper posters, and Live Activities stop updating.","approxMemoryMB":675,"labels":["com.apple.PosterBoard","com.apple.chronod","com.apple.liveactivitiesd"]},{"id":"siri","name":"Siri & Intelligence","downside":"Siri, speech features, and Apple Intelligence services are unavailable.","approxMemoryMB":265,"labels":["com.apple.assistantd","com.apple.assistant_cdmd","com.apple.assistant_service","com.apple.siriactionsd","com.apple.siriinferenced","com.apple.siriknowledged","com.apple.sirittsd","com.apple.siri.context.service","com.apple.siri.acousticsignature","com.apple.corespeechd","com.apple.voiced","com.apple.voicebankingd","com.apple.speechmodeltrainingd","com.apple.intelligenceplatformd","com.apple.intelligencecontextd","com.apple.intelligenceflowd","com.apple.intelligencetasksd","com.apple.generativeexperiencesd","com.apple.knowledgeconstructiond","com.apple.naturallanguaged","com.apple.textunderstandingd","com.apple.modelcatalogd","com.apple.modelmanagerd","com.apple.mlhostd","com.apple.mlruntimed","com.apple.suggestd","com.apple.parsecd","com.apple.parsec-fbf","com.apple.proactiveeventtrackerd"]},{"id":"search","name":"Spotlight & Search","downside":"Spotlight and Settings search return no results.","approxMemoryMB":50,"labels":["com.apple.searchd","com.apple.searchtoold","com.apple.spotlightknowledged","com.apple.spotlightknowledged.updater","com.apple.corespotlightservice"]},{"id":"icloud","name":"iCloud & Apple Account","downside":"iCloud sync, Apple Account, Keychain, and backup workflows will not work.","approxMemoryMB":100,"labels":["com.apple.appleaccountd","com.apple.appleaccounttransparencyd","com.apple.appleidsetupd","com.apple.akd","com.apple.amsaccountsd","com.apple.amsengagementd","com.apple.amsondevicestoraged","com.apple.cloudd","com.apple.cloudphotod","com.apple.ckdiscretionaryd","com.apple.cloudsettingssyncagent","com.apple.bird","com.apple.syncdefaultsd","com.apple.cdpd","com.apple.sosd","com.apple.SecureBackupDaemon","com.apple.TrustedPeersHelper","com.apple.protectedcloudstorage.protectedcloudkeysyncing","com.apple.icloudmailagent","com.apple.icloudsubscriptionoptimizerd","com.apple.communicationtrustd"]},{"id":"store","name":"App Store, Push & Media","downside":"Remote push notifications and StoreKit or App Store testing will not work.","approxMemoryMB":80,"labels":["com.apple.appstored","com.apple.appstorecomponentsd","com.apple.apsd","com.apple.itunescloudd","com.apple.itunesstored","com.apple.storekitd","com.apple.amsaccountsd","com.apple.amsengagementd","com.apple.amsondevicestoraged","com.apple.passd","com.apple.financed","com.apple.videosubscriptionsd","com.apple.assetsubscriptiond","com.apple.musicd"]},{"id":"pim","name":"Mail, Calendar & Contacts","downside":"Contacts, Calendar, Reminders, and Mail-backed pickers or sync may fail.","approxMemoryMB":80,"labels":["com.apple.email.maild","com.apple.exchangesyncd","com.apple.dataaccess.dataaccessd","com.apple.calaccessd","com.apple.remindd","com.apple.contactsd","com.apple.contacts.postersyncd","com.apple.peopled"]},{"id":"web","name":"Safari Sync & Web Services","downside":"Universal links and Safari sync or background web services will not work.","approxMemoryMB":50,"labels":["com.apple.SafariBookmarksSyncAgent","com.apple.Safari.History","com.apple.Safari.passwordbreachd","com.apple.Safari.SafeBrowsing.Service","com.apple.safarifetcherd","com.apple.WebBookmarks.webbookmarksd","com.apple.webkit.adattributiond","com.apple.webkit.webpushd","com.apple.webprivacyd","com.apple.swcd"]},{"id":"family","name":"Family & Screen Time","downside":"Family Sharing, Screen Time, and usage tracking stop working.","approxMemoryMB":65,"labels":["com.apple.familycircled","com.apple.FamilyControlsAgent","com.apple.familynotification","com.apple.askpermissiond","com.apple.asktod","com.apple.ScreenTimeAgent","com.apple.ScreenTimeSettingsAgent","com.apple.UsageTrackingAgent"]},{"id":"health","name":"Health, Home & Fitness","downside":"HealthKit, HomeKit, and Fitness integrations will not work.","approxMemoryMB":135,"labels":["com.apple.healthd","com.apple.healthappd","com.apple.healthcontentd","com.apple.healtheventsd","com.apple.healthrecordsd","com.apple.finhealthd","com.apple.homed","com.apple.homeeventsd","com.apple.fitcore","com.apple.fitcore.session","com.apple.fitnesscoachingd","com.apple.fitnessintelligenced","com.apple.activityawardsd","com.apple.activitysharingd"]},{"id":"photos","name":"Photos & Media Analysis","downside":"Photo picker, Photos-library workflows, and media analysis may fail.","approxMemoryMB":60,"labels":["com.apple.photoanalysisd","com.apple.photosface","com.apple.mediaanalysisd","com.apple.mediaanalysisd.service","com.apple.mediastream.mstreamd","com.apple.medialibraryd","com.apple.assetsd","com.apple.assetsd.nebulad"]},{"id":"apps","name":"News, Weather, Maps & Games","downside":"News, Weather, Maps background data, and game-controller services are unavailable.","approxMemoryMB":90,"labels":["com.apple.newsd","com.apple.weatherd","com.apple.Maps.mapssyncd","com.apple.Maps.mapspushd","com.apple.Maps.geocorrectiond","com.apple.maps.destinationd","com.apple.MapKit.SnapshotService","com.apple.jetpackassetd","com.apple.tipsd","com.apple.gamed","com.apple.gamesaved","com.apple.GameController.gamecontrollerd"]},{"id":"messaging","name":"Messaging & FaceTime","downside":"iMessage, FaceTime, and related identity services will not work.","approxMemoryMB":60,"labels":["com.apple.identityservicesd","com.apple.ids_simd","com.apple.imautomatichistorydeletionagent","com.apple.imcore.imtransferagent","com.apple.imdpersistence.IMDPersistenceAgent","com.apple.facetimemessagestored","com.apple.telephonyutilities.callservicesd"]},{"id":"connectivity","name":"Sharing & Device Connectivity","downside":"AirDrop, Continuity, CarPlay, Watch, and Find My connectivity will not work.","approxMemoryMB":65,"labels":["com.apple.rapportd","com.apple.companiond","com.apple.carkitd","com.apple.wcd","com.apple.tvremoted","com.apple.avatarsd","com.apple.stickersd","com.apple.sociallayerd","com.apple.announced","com.apple.navd","com.apple.findmy.findmylocated"]},{"id":"telemetry","name":"Ads, Diagnostics & Telemetry","downside":"DeviceCheck plus analytics, diagnostics, and feedback services are unavailable.","approxMemoryMB":105,"labels":["com.apple.ap.adprivacyd","com.apple.ap.promotedcontentd","com.apple.diagnosticextensionsd","com.apple.feedbackd","com.apple.rtcreportingd","com.apple.securityuploadd","com.apple.geoanalyticsd","com.apple.triald","com.apple.followupd","com.apple.purplebuddy.budd","com.apple.devicecheckd"]},{"id":"other","name":"Other Background Services","downside":"Wallet, merchant, business, asset, and miscellaneous background services are unavailable.","approxMemoryMB":195,"labels":["com.apple.financed","com.apple.passd","com.apple.merchantd","com.apple.coreidvd","com.apple.businessservicesd","com.apple.deviceaccessd","com.apple.replicatord","com.apple.linkd","com.apple.ind","com.apple.storagedatad","com.apple.StatusKitAgent","com.apple.countryd","com.apple.mobileassetd","com.apple.managedconfiguration.passcodenagd"]}];function Ms(){const e=new Set(ks);const t=new Set;for(const s of Ce)for(const i of s.labels)if(!e.has(i))t.add(i);return[...t].sort()}function Ee(e){return e==="slim"?Ms():[]}function _s(e){return bo("sha256").update(e+"\n"+Ee(e).join("\n")).digest("hex").slice(0,16)}import{promises as q,existsSync as $t,readFileSync as ko}from"fs";import C from"path";import{AsyncLocalStorage as Mo}from"async_hooks";import{createHash as _o,randomBytes as we}from"crypto";import{promises as $e}from"fs";import It from"path";import{randomBytes as Eo}from"crypto";import{spawn as $o}from"child_process";var xe=new Set;function Bt(){return xe.size}function Io(){for(const e of xe){try{e.kill("SIGKILL")}catch{}}}function _(e,t,s={}){const{timeoutMs:i=3e4,maxBytes:o=4*1024*1024}=s;return new Promise(n=>{const a=$o(e,t,{env:s.env??process.env,stdio:[s.input==null?"ignore":"pipe","pipe","pipe"]});xe.add(a);let r="";let c="";let l=false;a.stdout.on("data",h=>{if(r.length<o)r+=h.toString("utf8")});a.stderr.on("data",h=>{if(c.length<o)c+=h.toString("utf8")});if(s.input!=null)a.stdin.end(s.input);const d=setTimeout(()=>{l=true;a.kill("SIGTERM");setTimeout(()=>a.kill("SIGKILL"),2e3).unref?.()},i);a.on("error",h=>{xe.delete(a);clearTimeout(d);n({code:null,stdout:r,stderr:c+String(h),timedOut:l})});a.on("close",h=>{xe.delete(a);clearTimeout(d);n({code:h,stdout:r,stderr:c,timedOut:l})})})}async function L(e,t,s={}){const i=await _(e,t,s);if(i.timedOut)throw new Error(`${e} ${t.slice(0,3).join(" ")} timed out after ${s.timeoutMs??3e4} ms`);if(i.code!==0)throw new Error(`${e} ${t.slice(0,3).join(" ")} failed (${i.code}): ${(i.stderr||i.stdout).trim().slice(0,500)}`);return i.stdout}async function tt(){const e=await L("ps",["-axww","-o","pid=,ppid=,lstart=,stat=,comm="],{timeoutMs:15e3});const t=await L("ps",["-axww","-o","pid=,command="],{timeoutMs:15e3});const s=new Map;for(const o of t.split("\n")){const n=/^\s*(\d+)\s+(.*)$/.exec(o);if(n)s.set(Number(n[1]),n[2])}const i=[];for(const o of e.split("\n")){const n=/^\s*(\d+)\s+(\d+)\s+(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})\s+(\S+)\s+(.*)$/.exec(o);if(!n)continue;const a=Number(n[1]);i.push({pid:a,ppid:Number(n[2]),start:n[3].replace(/\s+/g," "),stat:n[4],comm:n[5].trim(),command:s.get(a)??n[5].trim()})}return i}function Ot(e,t){const s=new Map;const i=new Map;for(const c of e){i.set(c.pid,c);const l=s.get(c.ppid)??[];l.push(c);s.set(c.ppid,l)}const o=[];const n=i.get(t);if(!n)return o;const a=[n];const r=new Set;while(a.length){const c=a.pop();if(r.has(c.pid))continue;r.add(c.pid);o.push(c);for(const l of s.get(c.pid)??[])a.push(l)}return o}async function Ie(e){const t=await _("ps",["-o","lstart=","-p",String(e)],{timeoutMs:5e3});const s=t.stdout.trim().replace(/\s+/g," ");return t.code===0&&s?s:null}async function J(e,t,s=false){if(!Number.isInteger(e)||e<=0)return false;try{process.kill(e,0)}catch(o){if(o.code!=="EPERM")return false}if(!t)return true;const i=await Ie(e);if(i===null)return s;return i===t}function st(e=process.env){return Object.fromEntries(Object.entries(e).filter(([t])=>!t.startsWith("SIMCTL_CHILD_")))}var T=e=>new Promise(t=>setTimeout(t,e));var Be=process.env.BUOY_FARM_DISABLED_ROOT||"/private/var/tmp";function Es(e,t=Be){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return It.join(t,`com.apple.CoreSimulator.SimDevice.${e}`)}function $s(e,t=Be){return It.join(Es(e,t),"disabled.plist")}async function Is(e,t=Be){const s=$s(e,t);let i;try{i=await $e.lstat(s)}catch{return{}}if(!i.isFile())throw new Error(`refusing ${s}: not a regular file`);const o=await L("plutil",["-convert","json","-o","-",s],{timeoutMs:1e4});const n=JSON.parse(o);const a={};for(const[r,c]of Object.entries(n))if(typeof c==="boolean")a[r]=c;return a}function Bo(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function Oo(e){const t=Object.keys(e).sort();let s='<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n';for(const i of t)s+=` <key>${Bo(i)}</key>
|
|
2
2
|
<${e[i]?"true":"false"}/>
|
|
3
|
-
`;return s+"</dict>\n</plist>\n"}async function Bs(e,t,s=Be){const i=Es(e,s);await $e.mkdir(i,{recursive:true});const o=await $e.lstat(i);if(!o.isDirectory())throw new Error(`refusing ${i}: not a directory`);const n=await Is(e,s);const a=It.join(i,"buoy-farm-prior.json");let r={};try{r=JSON.parse(await $e.readFile(a,"utf8"))}catch{}const c={...n};for(const l of t){if(!(l in r))r[l]=l in n?n[l]:null;c[l]=true}await Os(a,JSON.stringify(r,null,2)+"\n");await Os($s(e,s),Oo(c))}async function To(e,t,s=Be){const i=await Is(e,s);return t.every(o=>i[o]===true)}async function Os(e,t){const s=`${e}.${Eo(4).toString("hex")}.tmp`;const i=await $e.open(s,"w",420);try{await i.writeFile(t);await i.sync()}finally{await i.close()}await $e.rename(s,e)}import{promises as
|
|
4
|
-
`)}}}await s(e);return t.digest("hex").slice(0,16)}async function Ro(e){const t=Ts("sha256");const s=O.join(e,"Info.plist");t.update(await z.readFile(s));const i=JSON.parse(await U("plutil",["-convert","json","-o","-",s],{timeoutMs:1e4}));const o=O.join(e,String(i.CFBundleExecutable??""));const n=await z.stat(o);t.update(`${n.size}:${Math.floor(n.mtimeMs)}`);return t.digest("hex").slice(0,16)}async function Po(e){const t=JSON.parse(await U("plutil",["-convert","json","-o","-",O.join(e,"Info.plist")],{timeoutMs:1e4}));if(!t.CFBundleIdentifier)throw new Error(`${e} has no CFBundleIdentifier`);return String(t.CFBundleIdentifier)}async function it(e,t){const s=O.join(e,"Containers/Data/Application");let i;try{i=await z.readdir(s)}catch{return null}for(const o of i){const n=O.join(s,o,".com.apple.mobile_container_manager.metadata.plist");if(!ge(n))continue;const a=await _("plutil",["-extract","MCMMetadataIdentifier","raw","-o","-",n],{timeoutMs:5e3});if(a.code===0&&a.stdout.trim()===t)return O.join(s,o)}return null}function No(e,t){return O.join(e,"Library/Application Support",t,"RCTAsyncLocalStorage_V1")}async function As(e,t,s,i){i();await z.mkdir(t,{recursive:true});const o=["-a","--delete"];for(const n of s)o.push("--exclude",n);o.push(e.replace(/\/?$/,"/"),t.replace(/\/?$/,"/"));i();await U("rsync",o,{timeoutMs:6e4})}async function Rs(e,t,s,i=()=>{}){i();const o=Le(s);if(o)throw new Error(o);const n=[...Ds.map(c=>`react-buoy/${c}`),`react-buoy/${Do}`];if(ge(O.join(s,"Documents")))await As(O.join(s,"Documents"),O.join(e,"Documents"),n,i);if(ge(O.join(s,"AsyncStorage/manifest.json"))){await As(O.join(s,"AsyncStorage"),No(e,t),[],i)}const a=O.join(s,"Preferences",`${t}.plist`);if(ge(a)){i();await z.mkdir(O.join(e,"Library/Preferences"),{recursive:true});await z.copyFile(a,O.join(e,"Library/Preferences",`${t}.plist`))}const r=O.join(e,"Documents/react-buoy");try{for(const c of await z.readdir(r)){i();if(c.startsWith("@buoy~1floating-tool~1"))await z.rm(O.join(r,c),{force:true,recursive:true})}}catch{}}async function ot(e,t=()=>{}){t();const s=[];const i=O.join(e,"Documents/react-buoy");let o=[];try{o=await z.readdir(i)}catch{return s}for(const n of o){if(Ds.includes(n)||n.startsWith("@buoy~1floating-tool~1")){t();await z.rm(O.join(i,n),{force:true,recursive:true});s.push(n)}}return s}async function Ps(e){let t;try{t=await z.readFile(O.join(e,"Documents/react-buoy/@react_buoy_sync_device_id.json"),"utf8")}catch{return null}try{const s=JSON.parse(t);if(typeof s==="string")return s||null;if(typeof s?.value==="string")return s.value||null}catch{}return t.trim()||null}var Ns=2*60*6e4;var Co=10;var xo=5;var Lo=.8;var ye=e=>Math.round(e*10)/10;var Uo=e=>{if(!e.length)return 0;const t=[...e].sort((i,o)=>i-o);const s=t.length>>1;return t.length%2?t[s]:(t[s-1]+t[s])/2};var Cs=class{constructor(e,t,s=1e3,i=Date.now){this.host=e;this.target=t;this.periodMs=s;this.now=i;this.samples=[];this.prev=null;this.pids=[];this.treeAge=0;this.treeKey="";this.sampling=false;this.ticks=0;this.lastProcs=null;this.rates={ours:0,graphics:0,gpuPct:null};this.cpuCount=0}async tick(){if(this.sampling)return;this.sampling=true;try{const e=this.target();if(!e){this.prev=null;this.treeKey="";return}const t=`${e.leaseId}:${e.udids.join(",")}`;if(t!==this.treeKey||++this.treeAge>=Co){const f=await Promise.all(e.udids.map(w=>this.host.simTree(w)));this.pids=f.flat().map(w=>w.pid);this.treeAge=0;if(t!==this.treeKey){this.prev=null;this.lastProcs=null;this.ticks=0;this.rates={ours:0,graphics:0,gpuPct:null}}this.treeKey=t}const s=this.ticks++;const i=await this.host.loadCounters(this.pids,s<2||s%xo===0);this.cpuCount=i.cpuCount;const o=this.now();const n=this.prev;this.prev={...i,at:o,leaseId:e.leaseId};const a=f=>(i.hostTotalMs-f)/Math.max(1,i.cpuCount);if(i.pidsCpuMs!==null&&i.graphicsCpuMs!==null){const f=this.lastProcs;const w=f?a(f.hostTotalMs):0;if(f&&w>0){this.rates.ours=Math.max(0,(i.pidsCpuMs-f.pidsCpuMs)/w);this.rates.graphics=Math.max(0,(i.graphicsCpuMs-f.graphicsCpuMs)/w)}this.rates.gpuPct=i.gpuPct;this.lastProcs={hostTotalMs:i.hostTotalMs,pidsCpuMs:i.pidsCpuMs,graphicsCpuMs:i.graphicsCpuMs}}if(!n||n.leaseId!==e.leaseId)return;const r=a(n.hostTotalMs);if(r<=0)return;const c=Math.max(0,(i.hostBusyMs-n.hostBusyMs)/r);const l=this.rates.ours;const d=this.rates.graphics;this.samples.push({at:Math.round((o+n.at)/2),leaseId:e.leaseId,freeCores:ye(Math.max(0,i.cpuCount-c)),ourCores:ye(l),graphicsCores:ye(d),outsideCores:ye(Math.max(0,c-l-d)),gpuPct:this.rates.gpuPct,memLevel:i.memPressureLevel,swapMB:i.swapUsedMB,thermal:i.thermalLevel});const h=o-Ns;while(this.samples.length&&this.samples[0].at<h)this.samples.shift()}finally{this.sampling=false}}before(e,t){for(let s=this.samples.length-1;s>=0;s--){const i=this.samples[s];if(i.at<t)return i.leaseId===e?i:void 0}return void 0}latest(){return this.samples[this.samples.length-1]}verdicts(e,t,s){const i=this.periodMs/2;const o=t.map(a=>{const r=this.samples.filter(l=>l.at>=a.from-i&&l.at<=a.to+i);const c=r.filter(l=>l.leaseId===e);return{w:a,all:r,mine:c}});const n=Uo(o.filter(a=>a.mine.length).map(a=>Tt(a.mine.map(r=>r.outsideCores))));return o.map(({w:a,all:r,mine:c})=>{const l=Math.max(1,Math.round((a.to-a.from)/this.periodMs));const d={id:a.id,fair:false,seconds:c.length};if(!r.length&&(a.from>this.now()+6e4||a.to<this.now()-Ns)){return{...d,reason:"This run's time doesn't match the Mac's clock. Was the app's clock moved? Put it back and run again."}}if(!c.length||c.length<r.length||c.length<Math.floor(l*Lo)){return{...d,reason:"No load record for all of this run. Hold perf-slot for the whole test."}}const h=c.map(y=>y.outsideCores);const f=c.map(y=>y.gpuPct).filter(y=>y!==null);const w=c.map(y=>y.thermal).filter(y=>y!==null);const v={...d,freeCoresMin:Math.min(...c.map(y=>y.freeCores)),outsideCoresAvg:ye(Tt(h)),outsideCoresMax:Math.max(...h),ourCoresAvg:ye(Tt(c.map(y=>y.ourCores))),graphicsCoresMax:Math.max(...c.map(y=>y.graphicsCores)),gpuMaxPct:f.length?Math.max(...f):null,swapGrowthMB:Math.round(Math.max(0,c[c.length-1].swapMB-(this.before(e,a.from)?.swapMB??c[0].swapMB))),thermalMax:w.length?Math.max(...w):null};if(v.thermalMax!=null&&v.thermalMax>s.maxThermalLevel){return{...v,reason:"The Mac got hot and slowed itself down. Let it cool, then run again."}}if(c.some(y=>y.memLevel>=4)){return{...v,reason:"The Mac ran low on memory. Close apps or stop sims you don't need."}}if(v.swapGrowthMB>s.maxSwapGrowthMB){return{...v,reason:`The Mac swapped ${v.swapGrowthMB} MB of memory to disk.`}}if(v.freeCoresMin<s.minFreeCores){return{...v,reason:`The Mac ran out of spare CPU (${v.freeCoresMin} cores free, needs ${s.minFreeCores}).`}}const k=ye(v.outsideCoresMax-n);if(k>s.maxSwingCores){return{...v,reason:`Other work on the Mac jumped by ${k} cores during this run.`}}return{...v,fair:true}})}};function Tt(e){return e.length?e.reduce((t,s)=>t+s,0)/e.length:0}function xs(e){const t=e.trim().split(":").map(Number);if(!t.length||t.some(s=>!Number.isFinite(s)))return 0;return Math.round(t.reduce((s,i)=>s*60+i,0)*1e3)}import{spawn as Ls}from"child_process";import{randomBytes as jo}from"crypto";import{existsSync as Dt,mkdirSync as Fo,openSync as Go,closeSync as Wo,promises as ve,writeFileSync as Us}from"fs";import Ho from"net";import q from"path";import{createHash as qo}from"crypto";import{createReadStream as Yo,createWriteStream as zo,existsSync as Ue,promises as se,readFileSync as Ko}from"fs";import At from"os";import j from"path";import{Readable as Qo}from"stream";import{pipeline as js}from"stream/promises";var je="https://dl.google.com/android/repository/";var Vo="channel-0";var ne={api:36,tag:"aosp_atd",abi:Jo()};function Jo(){return At.arch()==="arm64"?"arm64-v8a":"x86_64"}function nt(e){return`system-images/android-${e.api}/${e.tag}/${e.abi}/`}function Fs(e){return j.join(e,"sdk")}function Rt(e=process.env){const t=At.homedir();const s=[e.ANDROID_HOME,e.ANDROID_SDK_ROOT,j.join(t,"Library/Android/sdk"),j.join(t,"Android/Sdk")];return[...new Set(s.filter(i=>!!i))]}function Gs(e){try{const t=/Pkg\.Revision=([\d.]+)/.exec(Ko(j.join(e,"emulator/source.properties"),"utf8"));return t?.[1]??null}catch{return null}}function Pt(e,t){const s=e.split(".").map(Number);const i=t.split(".").map(Number);for(let o=0;o<Math.max(s.length,i.length);o++){const n=(s[o]??0)-(i[o]??0);if(n)return n}return 0}var Ws="35.4.9";function Fe(e,t,s=ne){const i=j.join(e,"emulator",process.platform==="win32"?"emulator.exe":"emulator");const o=j.join(e,"platform-tools",process.platform==="win32"?"adb.exe":"adb");const n=Gs(e);if(!Ue(i)||!Ue(o)||!n)return null;return{root:e,source:t,emulator:i,emulatorVersion:n,adb:o,hasImage:Ue(j.join(e,nt(s),"system.img"))}}function Ge(e,t=ne,s="auto"){const i=Fe(Fs(e),"buoy",t);if(s!=="system"&&i?.hasImage&&Pt(i.emulatorVersion,Ws)>=0)return i;if(s==="buoy")return i;for(const o of Rt()){const n=Fe(o,"system",t);if(n?.hasImage&&Pt(n.emulatorVersion,Ws)>=0)return n}return s==="system"?null:i}function Xo(e){return[...e.matchAll(/<remotePackage path="([^"]+)">([\s\S]*?)<\/remotePackage>/g)].map(t=>({path:t[1],body:t[2]}))}function Zo(e){const t=/<revision>([\s\S]*?)<\/revision>/.exec(e)?.[1]??"";return["major","minor","micro"].map(s=>new RegExp(`<${s}>(\\d+)</${s}>`).exec(t)?.[1]).filter(Boolean).join(".")}function en(){return process.platform==="darwin"?"macosx":process.platform==="win32"?"windows":"linux"}function tn(){return At.arch()==="arm64"?"aarch64":"x64"}function Nt(e,t,s,i,o={}){const n=o.os??en();const a=o.arch??tn();let r=null;for(const c of Xo(e)){if(c.path!==t)continue;if(!c.body.includes(`<channelRef ref="${Vo}"/>`))continue;const l=Zo(c.body);const d=/<uses-license ref="([^"]+)"/.exec(c.body)?.[1]??"";for(const h of c.body.matchAll(/<archive>([\s\S]*?)<\/archive>/g)){const f=h[1];const w=/<host-os>([^<]+)<\/host-os>/.exec(f)?.[1];const v=/<host-arch>([^<]+)<\/host-arch>/.exec(f)?.[1];if(w&&w!==n)continue;if(v&&v!==a)continue;const k=/<url>([^<]+)<\/url>/.exec(f)?.[1];const y=/<checksum type="sha1">([0-9a-f]+)<\/checksum>/.exec(f)?.[1];const b=Number(/<size>(\d+)<\/size>/.exec(f)?.[1]??0);if(!k||!y)continue;const E=/<display-name>([^<]+)<\/display-name>/.exec(c.body)?.[1]??t;if(!r||Pt(l,r.rev)>0){r={rev:l,a:{name:`${E} ${l}`,url:s+k,sha1:y,size:b,license:d,dest:i}}}}}return r?.a??null}function Hs(e,t){const s=new RegExp(`<license id="${t}" type="text">([\\s\\S]*?)</license>`).exec(e);return s?s[1].replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&").trim():null}async function qs(e){const t=await fetch(e);if(!t.ok)throw new Error(`GET ${e} failed: ${t.status}`);return t.text()}async function sn(e=ne){const t=await qs(`${je}repository2-3.xml`);const s=`${je}sys-img/${e.tag}/`;const i=await qs(`${s}sys-img2-3.xml`);const o=Nt(t,"emulator",je,"emulator");const n=Nt(t,"platform-tools",je,"platform-tools");const a=Nt(i,`system-images;android-${e.api};${e.tag};${e.abi}`,s,nt(e));const r=[!o&&"emulator",!n&&"platform-tools",!a&&`${e.tag} image for Android ${e.api}`].filter(Boolean);if(r.length)throw new Error(`Google's list has no stable ${r.join(", ")} for this computer.`);const c=[o,n,a];const l={};for(const d of c){if(l[d.license])continue;l[d.license]=Hs(t,d.license)??Hs(i,d.license)??`(See ${je} for license ${d.license}.)`}return{archives:c,licenses:l}}async function Ys(e){const t=qo("sha1");await js(Yo(e),t);return t.digest("hex")}async function on(e,t,s=()=>{}){if(process.platform==="win32")throw new Error("The Android farm can't set up its own SDK on Windows yet.");const i=Fs(e);const o=j.join(e,"download");await se.mkdir(o,{recursive:true});await Promise.all(t.archives.map(async a=>{const r=j.join(i,a.dest);if(a.dest==="emulator"?Gs(i):Ue(j.join(r,a.dest==="platform-tools"?"adb":"system.img"))){s(`${a.name}: already here.`);return}const c=j.join(o,j.basename(a.url));if(!Ue(c)||await Ys(c)!==a.sha1){s(`Downloading ${a.name} (${Math.round(a.size/1024**2)} MB)\u2026`);const h=await fetch(a.url);if(!h.ok||!h.body)throw new Error(`GET ${a.url} failed: ${h.status}`);await js(Qo.fromWeb(h.body),zo(c));const f=await Ys(c);if(f!==a.sha1)throw new Error(`${a.name} failed its checksum (${f}).`)}const l=j.join(o,`unzip-${j.basename(a.url)}`);await se.rm(l,{recursive:true,force:true});await se.mkdir(l,{recursive:true});await U("unzip",["-q",c,"-d",l],{timeoutMs:15*6e4});const[d]=await se.readdir(l);await se.mkdir(j.dirname(r),{recursive:true});await se.rm(r,{recursive:true,force:true});await se.rename(j.join(l,d),r);await se.rm(l,{recursive:true,force:true});await se.rm(c,{force:true});s(`${a.name}: ready.`)}));await se.rm(o,{recursive:true,force:true});const n=Fe(i,"buoy");if(!n)throw new Error(`The SDK at ${i} is not complete after the download.`);return n}import{existsSync as nn,promises as Ct,readFileSync as an}from"fs";import rn from"path";var xt="android-3gb";var zs={"android-3gb":{name:"Low-end Android (3 GB)",ramMB:3072,cores:4,width:720,height:1600,density:320,heapGrowthMB:192,heapMB:512,source:"built in"},"pixel-4a":{name:"Pixel 4a",ramMB:6144,cores:8,width:1080,height:2340,density:440,heapGrowthMB:256,heapMB:512,source:"built in"}};function Ks(e){return rn.join(e,"phones.json")}function Lt(e){const t={...zs};const s=Ks(e);if(nn(s)){try{Object.assign(t,JSON.parse(an(s,"utf8")))}catch{}}return t}async function cn(e,t,s){const i=Ks(e);let o={};try{o=JSON.parse(await Ct.readFile(i,"utf8"))}catch{}o[t]=s;await Ct.mkdir(e,{recursive:true});await Ct.writeFile(i,JSON.stringify(o,null,2)+"\n")}var ln=[1024,1536,2048,3072,4096,6144,8192,12288,16384,24576];function dn(e,t){const s=h=>new RegExp(`\\[${h.replace(/\./g,"\\.")}\\]: \\[([^\\]]*)\\]`).exec(e.props)?.[1]??"";const i=Number(/MemTotal:\s+(\d+)/.exec(e.meminfo)?.[1]??0);const o=i?ln.find(h=>h>=i/1024)??Math.ceil(i/1024/1024)*1024:0;const n=/Override size: (\d+)x(\d+)/.exec(e.wmSize)??/Physical size: (\d+)x(\d+)/.exec(e.wmSize);const a=/Override density: (\d+)/.exec(e.wmDensity)??/Physical density: (\d+)/.exec(e.wmDensity);const r=h=>Number(/^(\d+)m$/i.exec(h)?.[1]??0);const c=s("ro.product.model")||"Android phone";const l={name:c,ramMB:o||3072,cores:Number(e.nproc.trim())||4,width:n?Number(n[1]):1080,height:n?Number(n[2]):2400,density:a?Number(a[1]):420,heapGrowthMB:r(s("dalvik.vm.heapgrowthlimit"))||256,heapMB:r(s("dalvik.vm.heapsize"))||512,source:t};const d=c.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"android-phone";return{id:d,phone:l}}import{promises as Sc,readFileSync as pn}from"fs";import Ut from"os";import jt from"path";import{promises as un,existsSync as Ft,mkdirSync as Gt,readFileSync as Se,renameSync as Wt,writeFileSync as at,rmSync as We,openSync as hn,fsyncSync as mn,closeSync as fn,chmodSync as wn,readdirSync as gn,statSync as Qs}from"fs";import yn from"os";import W from"path";import{createHash as vn,randomBytes as He}from"crypto";function A(){return process.env.BUOY_FARM_HOME||W.join(yn.homedir(),".buoy","farm")}var X=(e=A())=>({home:e,lock:W.join(e,"owner.lock"),lockOwner:W.join(e,"owner.lock","owner.json"),socket:W.join(e,"farmd.sock"),token:W.join(e,"token"),state:W.join(e,"state.json"),journal:W.join(e,"journal.ndjson"),log:W.join(e,"farmd.log"),shots:W.join(e,"shots")});function Ht(e=A()){Gt(e,{recursive:true,mode:448});try{wn(e,448)}catch{}}function Vs(e=A()){const t=X(e).token;try{const s=Se(t,"utf8").trim();if(s)return s}catch{}Ht(e);try{at(t,He(24).toString("hex")+"\n",{mode:384,flag:"wx"})}catch(s){if(s.code!=="EEXIST")throw s}for(let s=0;s<20;s++){const i=Se(t,"utf8").trim();if(i)return i;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,25)}throw new Error(`farm token at ${t} is empty`)}async function qe(e=A()){try{return JSON.parse(await un.readFile(X(e).lockOwner,"utf8"))}catch{return null}}async function Ye(e){return!!e&&await J(e.pid,e.start,true)}async function Sn(e,t){const s=W.join(e,"owner.claim");for(let i=0;i<20;i++){try{Gt(s,{mode:448});at(W.join(s,"pid"),`${process.pid}
|
|
5
|
-
${await Ie(process.pid)??""}`);try{return await t()}finally{We(s,{recursive:true,force:true})}}catch(o){if(o.code!=="EEXIST")throw o;const n=Qs(s,{throwIfNoEntry:false});if(n&&Date.now()-n.mtimeMs>3e4){let a=true;try{const[r,c]=Se(W.join(s,"pid"),"utf8").split("\n");a=!await J(Number(r),c||void 0)}catch{}if(a){try{Wt(s,W.join(e,`owner.claim-dead-${He(4).toString("hex")}`))}catch{}}}await new Promise(a=>setTimeout(a,100))}}return null}async function bn(e,t,s=A()){Ht(s);const i=X(s);const o=await Ie(process.pid)??"";for(let n=0;n<3;n++){try{Gt(i.lock,{mode:448})}catch(r){if(r.code!=="EEXIST")throw r;const c=await qe(s);if(c&&await Ye(c))return null;if(!c){await new Promise(h=>setTimeout(h,300));const d=await qe(s);if(d&&await Ye(d))return null}const l=await Sn(s,async()=>{const d=await qe(s);if(d&&await Ye(d))return false;if(!Ft(i.lock))return true;Wt(i.lock,W.join(s,`owner.stale-${He(4).toString("hex")}`));return true});if(l===false)return null;continue}const a={pid:process.pid,start:o,version:e,host:t,since:new Date().toISOString()};at(i.lockOwner,JSON.stringify(a),{mode:384});kn(s);return{info:a,release(){try{const r=JSON.parse(Se(i.lockOwner,"utf8"));if(r.pid!==process.pid)return}catch{return}try{We(i.socket,{force:true})}catch{}We(i.lock,{recursive:true,force:true})}}}return null}function kn(e){try{for(const t of gn(e)){if(t.startsWith("owner.stale-")||t.startsWith("owner.claim-dead-"))We(W.join(e,t),{recursive:true,force:true})}}catch{}}function Js(){return{version:et,seq:0,farmId:He(6).toString("hex"),nextIndex:1,templates:{},sims:{},orphans:{},leases:{},runs:{},locks:{},requests:{},events:[],settings:{...Et}}}var Xs=e=>vn("sha256").update(JSON.stringify(e)).digest("hex").slice(0,12);function Zs(e,t,s){const i=hn(e,s,384);try{at(i,t);mn(i)}finally{fn(i)}}var ei=class{constructor(e=A()){this.fenced=false;this.pending=[];this.open=new Map;this.p=X(e);Ht(e);this.state=this.load()}get shotsPath(){return this.p.shots}get homeDir(){return this.p.home}fence(){this.fenced=true}assertActive(){if(this.fenced)throw new Error("This farm has stopped.")}load(){let e=Js();if(Ft(this.p.state)){const o=JSON.parse(Se(this.p.state,"utf8"));if(o.version>et){throw new Error(`farm state is version ${o.version}; this farmd understands ${et}. Update Buoy.`)}e={...Js(),...o,settings:{...Et,...o.settings}}}delete e.settings.activeSlots;const t=new Map;const s=new Set;let i=e.seq;if(Ft(this.p.journal)){for(const o of Se(this.p.journal,"utf8").split("\n")){if(!o.trim())continue;let n;try{n=JSON.parse(o)}catch{continue}const{sum:a,...r}=n;if(Xs(r)!==a||n.seq<=e.seq)continue;i=Math.max(i,n.seq);if(n.type==="intent")t.set(n.seq,n);else{t.delete(n.seq);s.add(n.seq)}}}for(const o of e.openIntents??[]){if(!s.has(o.seq)&&!t.has(o.seq))t.set(o.seq,{...o,type:"intent",sum:""})}this.pending=[...t.values()].sort((o,n)=>o.seq-n.seq);for(const o of this.pending)this.open.set(o.seq,o);e.seq=i;e.openIntents=[];return e}intent(e,t){this.assertActive();const s=++this.state.seq;const i={seq:s,at:new Date().toISOString(),type:"intent",op:e,data:t};this.append(i);this.open.set(s,{...i,sum:""});return s}result(e,t,s){this.assertActive();this.open.delete(e);this.pending=this.pending.filter(i=>i.seq!==e);this.save();this.append({seq:e,at:new Date().toISOString(),type:"result",op:t,data:s})}append(e){this.assertActive();Zs(this.p.journal,JSON.stringify({...e,sum:Xs(e)})+"\n","a")}save(){this.assertActive();const e=`${this.p.state}.${He(4).toString("hex")}.tmp`;this.state.openIntents=[...this.open.values()].map(({seq:t,at:s,op:i,data:o})=>({seq:t,at:s,op:i,data:o}));Zs(e,JSON.stringify(this.state,null,1),"w");Wt(e,this.p.state);try{if(Qs(this.p.journal).size>2*1024*1024)We(this.p.journal,{force:true})}catch{}}event(e){this.assertActive();this.state.events.push({at:new Date().toISOString(),...e});if(this.state.events.length>300)this.state.events.splice(0,this.state.events.length-300)}};function Mn(e=A()){try{const t=JSON.parse(Se(X(e).state,"utf8"));return new Set([...Object.keys(t.sims??{}),...Object.keys(t.orphans??{}),...Object.values(t.templates??{}).map(s=>s.udid)])}catch{return new Set}}var de=Number(process.env.BUOY_FARM_ADB_PORT)||5137;var _n=1.3;function ze(e=A()){return jt.join(e,"android")}function ti(e){const t=s=>{const i=s.exec(e);return i?Number(i[1]):null};return{frames:t(/Total frames rendered: (\d+)/)??0,jankyPct:t(/Janky frames: \d+ \(([\d.]+)%\)/),p50:t(/50th percentile: (\d+)ms/),p90:t(/90th percentile: (\d+)ms/),p99:t(/99th percentile: (\d+)ms/)}}function En(e){if(!e.length)return null;const t=[...e].sort((i,o)=>i-o);const s=Math.floor(t.length/2);return t.length%2?t[s]:Math.round((t[s-1]+t[s])/2)}function si(e){if(/Successfully loaded snapshot|Loading snapshot '?default_boot'? .*(done|succeeded)|snapshot.*loaded/i.test(e))return"snapshot";if(/Cold boot|failed to load snapshot|Snapshot load failed|could not load snapshot/i.test(e))return"cold";return"unknown"}async function $n(){const e=Ut.totalmem()/1024**3;let t=Ut.freemem()/1024**3;if(process.platform==="darwin"){const o=await _("vm_stat",[],{timeoutMs:1e4});const n=Number(/page size of (\d+)/.exec(o.stdout)?.[1]??16384);const a=r=>Number(new RegExp(`${r}:\\s+(\\d+)`).exec(o.stdout)?.[1]??0);t=(a("Pages free")+a("Pages inactive")+a("Pages speculative")+a("Pages purgeable"))*n/1024**3}const s=await _("df",["-k",Ut.homedir()],{timeoutMs:1e4});const i=Number((s.stdout.trim().split("\n").pop()??"").split(/\s+/)[3]??0);return{availableGB:t,totalGB:e,freeDiskGB:i/1024**2}}async function In(e){if(process.platform==="darwin"){const i=await _("footprint",[String(e)],{timeoutMs:3e4});const o=/Footprint: ([\d.]+) (MB|GB|KB)/.exec(i.stdout);if(o)return Math.round(Number(o[1])*(o[2]==="GB"?1024:o[2]==="KB"?1/1024:1))}const t=await _("ps",["-o","rss=","-p",String(e)],{timeoutMs:5e3});const s=Number(t.stdout.trim());return s?Math.round(s/1024):null}async function ii(e){const t=await _("ps",["-o","time=","-p",String(e)],{timeoutMs:5e3});const s=/(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+(?:\.\d+)?)/.exec(t.stdout.trim());if(!s)return null;const[,i,o,n,a]=s;return((Number(i??0)*24+Number(o??0))*60+Number(n))*6e4+Number(a)*1e3}function qt(e,t){return oi(t).get(e)??null}function oi(e){const t=new Map;const s=a=>{try{return JSON.parse(pn(a,"utf8"))}catch{return null}};const i=(a,r)=>{for(const c of Object.values(r??{})){const l=a?.[c.templateId];if(!l?.sdkRoot||!Bn(c.pid)||t.has(c.serial))continue;t.set(c.serial,{adb:jt.join(l.sdkRoot,"platform-tools","adb"),port:de})}};const o=s(X(e??A()).state);i(o?.androidTemplates,o?.androidCopies);const n=s(jt.join(ze(e),"state.json"));if(!o?.androidImported)i(n?.templates,n?.copies);return t}function Bn(e){try{process.kill(e,0);return true}catch(t){return t.code==="EPERM"}}var ni=5800;var ai=5998;var ri="BuoyFarm_";var On="sync_device_id|react_buoy_install";var Tn=["files/react-buoy","shared_prefs","app_webview","databases","app_flutter"];var ci=Number(process.env.BUOY_BROKER_PORT)||42831;var li=class{constructor(e={}){this.dir=ze(e.home);this.avdHome=q.join(this.dir,"avd");this.adbPort=e.adbPort??de;this.sdkPref=e.sdk??"auto"}sdkFor(e=ne){const t=Ge(this.dir,e,this.sdkPref);return t?.hasImage?t:null}sdk(e=ne){const t=Ge(this.dir,e,this.sdkPref);if(!t)throw new Error("No Android SDK with an emulator and the slim image yet. Run: npx @buoy-gg/mcp farm android setup");if(!t.hasImage)throw new Error(`The SDK at ${t.root} has no ${nt(e)} image. Run: npx @buoy-gg/mcp farm android setup`);return t}sdkOf(e){const t=this.sdk(e.image);if(t.root!==e.sdkRoot){const s=Ge(this.dir,e.image,t.source==="buoy"?"system":"buoy");if(s?.root===e.sdkRoot)return s}return t}env(e){return{...process.env,ANDROID_HOME:e.root,ANDROID_SDK_ROOT:e.root,ANDROID_AVD_HOME:this.avdHome,ANDROID_ADB_SERVER_PORT:String(this.adbPort)}}run(e,t,s=3e4){return _(e.adb,t,{timeoutMs:s,env:this.env(e)})}adb(e,t,s=3e4){return this.run(this.sdkOf(e),t,s)}async shell(e,t,s,i=3e4){const o=await this.run(e,["-s",t,"shell",s],i);if(o.timedOut)throw new Error(`adb shell on ${t} timed out: ${s}`);if(o.code!==0)throw new Error(`adb shell on ${t} failed: ${s}: ${(o.stderr||o.stdout).trim().slice(0,300)}`);return o.stdout}stale(e){if(!e.usable)return e.note??"not ready";let t;try{t=this.sdkOf(e)}catch(s){return s.message}if(t.root!==e.sdkRoot||t.emulatorVersion!==e.emulatorVersion){return`It was saved with emulator ${e.emulatorVersion}; this SDK has ${t.emulatorVersion}. Snapshots don't load across versions.`}if(!Dt(q.join(this.avdHome,`${e.avd}.avd`,"snapshots","default_boot","ram.bin")))return"Its snapshot is gone.";return null}async freePort(e){const t=s=>new Promise(i=>{const o=Ho.createServer();o.once("error",()=>i(false));o.listen(s,"127.0.0.1",()=>o.close(()=>i(true)))});for(let s=ni;s<=ai;s+=2){if(e.has(s))continue;if(await t(s)&&await t(s+1))return s}throw new Error(`No free emulator port from ${ni} to ${ai}.`)}async launch(e,t,s,i,o){const n=q.join(this.dir,"logs");await ve.mkdir(n,{recursive:true});const a=q.join(n,o);const r=Go(a,"w");const c=["-avd",t,"-port",String(s),"-gpu","host","-no-audio","-no-boot-anim","-camera-back","none","-camera-front","none",...i];const l=Ls(e.emulator,c,{detached:true,stdio:["ignore",r,r],env:this.env(e)});Wo(r);l.unref();if(!l.pid)throw new Error("The emulator did not start.");const d=await Ie(l.pid)??"";return{pid:l.pid,pidStart:d,log:a}}async waitBooted(e,t,s,i){const o=Date.now()+i;while(Date.now()<o){if(!await J(s))throw new Error(`The emulator for ${t} stopped while booting.`);const n=await this.run(e,["-s",t,"shell","getprop","sys.boot_completed"],5e3);if(n.code===0&&n.stdout.trim()==="1")return;await T(250)}throw new Error(`${t} did not finish booting in ${Math.round(i/1e3)} s.`)}async stopEmulator(e,t,s,i){if(e)await this.run(e,["-s",t,"emu","kill"],1e4);for(let o=0;o<300;o++){if(!await J(s,i))return;await T(100)}if(await J(s,i)){try{process.kill(s,"SIGKILL")}catch{}}}async startCopy(e,t,s={}){const i=this.sdkOf(e);await this.run(i,["start-server"],2e4);const o=`emulator-${t}`;const n=["-read-only","-no-snapshot-save",...s.window?[]:["-no-window"]];const a=await this.launch(i,e.avd,t,n,`${o}.log`);return{serial:o,port:t,...a}}async readyCopy(e,t,s={}){const i=this.sdkOf(e);await this.waitBooted(i,t.serial,t.pid,s.timeoutMs??5*6e4);let o="unknown";try{o=si(await ve.readFile(t.log,"utf8"))??"unknown"}catch{}await this.shell(i,t.serial,"setprop debug.hwui.drawing_enabled 1; logcat -c");for(const n of[`${ci}`,...e.reverse??[],...s.reverse??[]]){const[a,r]=n.split(":");const c=await this.run(i,["-s",t.serial,"reverse",`tcp:${a}`,`tcp:${r??a}`],1e4);if(c.code!==0)throw new Error(`Could not link port ${a} on ${t.serial}: ${(c.stderr||c.stdout).trim().slice(0,200)}`)}return{bootedFrom:o}}async stopCopy(e,t){let s=null;try{s=e?this.sdkOf(e):this.sdkFor(ne)}catch{}if(await J(t.pid,t.pidStart))await this.stopEmulator(s,t.serial,t.pid,t.pidStart)}alive(e,t){return J(e,t)}async treePids(e){return Ot(await tt(),e).map(t=>t.pid)}async findEmulators(){const e=[];for(const t of await tt()){const s=/-avd (\S+)/.exec(t.command)?.[1];const i=Number(/-port (\d+)/.exec(t.command)?.[1]);if(!s?.startsWith(ri)||!i)continue;if(e.some(o=>o.port===i))continue;e.push({pid:t.pid,pidStart:t.start,port:i,avd:s})}return e}async screenshot(e,t,s,i=2e4){const o=this.sdkOf(e);const n=await new Promise(a=>{const r=Ls(o.adb,["-s",t,"exec-out","screencap","-p"],{env:this.env(o)});const c=[];r.stdout.on("data",d=>c.push(d));r.on("error",()=>a({code:-1,buf:Buffer.alloc(0)}));const l=setTimeout(()=>r.kill("SIGKILL"),i);r.on("close",d=>{clearTimeout(l);a({code:d,buf:Buffer.concat(c)})})});if(n.code!==0||n.buf.length<100)return false;await ve.mkdir(q.dirname(q.resolve(s)),{recursive:true});await ve.writeFile(s,n.buf);return true}async launchApp(e,t,s){const i=this.sdkOf(e);await this.shell(i,t,`am force-stop ${e.packageName}`);const o=s??e.launchUrl;const n=o?`am start -W -a android.intent.action.VIEW -d ${di(o)} ${e.packageName}`:`am start -W -n ${e.activity}`;const a=await this.shell(i,t,n,12e4);const r=/TotalTime: (\d+)/.exec(a);return{totalMs:r?Number(r[1]):null,output:a.trim()}}async openUrl(e,t,s){await this.shell(this.sdkOf(e),t,`am start -a android.intent.action.VIEW -d ${di(s)} ${e.packageName}`,6e4)}async install(e,t,s){const i=this.sdkOf(e);const o=await this.run(i,["-s",t,"install","-r","-g",q.resolve(s)],10*6e4);if(o.code!==0||!/Success/.test(o.stdout))throw new Error(`${t}: install failed: ${(o.stderr||o.stdout).trim().slice(0,300)}`);await this.shell(i,t,`cmd package compile -m speed -f ${e.packageName}`,10*6e4)}async installId(e,t){const s=await this.run(this.sdkOf(e),["-s",t,"shell",`run-as ${e.packageName} cat files/react-buoy/@react_buoy_sync_device_id.json`],5e3);const i=s.code===0?s.stdout.trim().replace(/^"|"$/g,""):"";return/^[\w-]{4,64}$/.test(i)?i:null}async appMemory(e,t){const s=this.sdkOf(e);const i=Number((await this.shell(s,t,`pidof ${e.packageName}; true`)).trim().split(/\s+/)[0])||null;if(!i)return{pid:null,pssMB:null};const o=await this.shell(s,t,`dumpsys meminfo ${i}`,2e4).catch(()=>"");const n=Number(/TOTAL PSS:\s+(\d+)/.exec(o)?.[1]??/TOTAL\s+(\d+)/.exec(o)?.[1]??0);return{pid:i,pssMB:n?Math.round(n/1024):null}}async memoryKill(e,t,s){const i=await this.shell(this.sdkOf(e),t,"logcat -d -b events -s am_kill:I lowmemorykiller:I; true",15e3).catch(()=>"");for(const o of i.split("\n")){const n=/am_kill\s*:\s*\[\d+,(\d+),([^,]+),-?\d+,([^\]]*)\]/.exec(o);if(n&&Number(n[1])===s&&n[2].trim()===e.packageName&&/low ?mem|lmk|oom|memory/i.test(n[3]))return n[3].trim()}return null}writeAvd(e,t,s){const i=q.join(this.avdHome,`${e}.avd`);Fo(i,{recursive:true});const o={AvdId:e,"avd.ini.displayname":e.replace(/_/g," "),"avd.ini.encoding":"UTF-8","abi.type":s.abi,"hw.cpu.arch":s.abi==="arm64-v8a"?"arm64":"x86_64","image.sysdir.1":nt(s),"tag.id":s.tag,"tag.display":s.tag==="aosp_atd"?"AOSP ATD":"Google ATD",target:`android-${s.api}`,"hw.ramSize":t.ramMB,"hw.cpu.ncore":t.cores,"hw.lcd.width":t.width,"hw.lcd.height":t.height,"hw.lcd.density":t.density,"disk.dataPartition.size":"6G","hw.gpu.enabled":"yes","hw.gpu.mode":"host","hw.keyboard":"yes","hw.audioInput":"no","hw.audioOutput":"no","hw.camera.back":"none","hw.camera.front":"none","fastboot.forceColdBoot":"no","fastboot.forceFastBoot":"yes",showDeviceFrame:"no"};Us(q.join(i,"config.ini"),Object.entries(o).map(([n,a])=>`${n}=${a}`).join("\n")+"\n");Us(q.join(this.avdHome,`${e}.ini`),`avd.ini.encoding=UTF-8
|
|
3
|
+
`;return s+"</dict>\n</plist>\n"}async function Bs(e,t,s=Be){const i=Es(e,s);await $e.mkdir(i,{recursive:true});const o=await $e.lstat(i);if(!o.isDirectory())throw new Error(`refusing ${i}: not a directory`);const n=await Is(e,s);const a=It.join(i,"buoy-farm-prior.json");let r={};try{r=JSON.parse(await $e.readFile(a,"utf8"))}catch{}const c={...n};for(const l of t){if(!(l in r))r[l]=l in n?n[l]:null;c[l]=true}await Os(a,JSON.stringify(r,null,2)+"\n");await Os($s(e,s),Oo(c))}async function To(e,t,s=Be){const i=await Is(e,s);return t.every(o=>i[o]===true)}async function Os(e,t){const s=`${e}.${Eo(4).toString("hex")}.tmp`;const i=await $e.open(s,"w",420);try{await i.writeFile(t);await i.sync()}finally{await i.close()}await $e.rename(s,e)}import{promises as W,existsSync as ge,readFileSync as oc}from"fs";import O from"path";import{createHash as Ts}from"crypto";var Ds=["@react_buoy_sync_device_id.json","@react_buoy_install.json"];var Do="@buoy~1floating-tool~1*";function Le(e){if(!ge(O.join(e,"seed.json")))return`No seed at ${e}. Run: pnpm e2e:pool seed`;if(ge(O.join(e,"Documents/buoy-license"))){for(const t of["buoy-gg-license.json","buoy-gg-device-fingerprint.json"]){if(!ge(O.join(e,"Documents/buoy-license",t)))return`The seed lacks ${t}.`}}return null}async function Ao(e){const t=Ts("sha256");async function s(i){let o;try{o=await W.readdir(i,{withFileTypes:true})}catch{return}o.sort((n,a)=>n.name.localeCompare(a.name));for(const n of o){const a=O.join(i,n.name);if(n.isDirectory())await s(a);else{const r=await W.stat(a);t.update(`${O.relative(e,a)}:${r.size}:${Math.floor(r.mtimeMs)}
|
|
4
|
+
`)}}}await s(e);return t.digest("hex").slice(0,16)}async function Ro(e){const t=Ts("sha256");const s=O.join(e,"Info.plist");t.update(await W.readFile(s));const i=JSON.parse(await L("plutil",["-convert","json","-o","-",s],{timeoutMs:1e4}));const o=O.join(e,String(i.CFBundleExecutable??""));const n=await W.stat(o);t.update(`${n.size}:${Math.floor(n.mtimeMs)}`);return t.digest("hex").slice(0,16)}async function Po(e,t){const s=async i=>{const o=await W.readFile(O.join(i,"Info.plist"));const n=JSON.parse(await L("plutil",["-convert","json","-o","-",O.join(i,"Info.plist")],{timeoutMs:1e4}));const a=await W.stat(O.join(i,String(n.CFBundleExecutable??"")));return`${o.toString("base64")}:${a.size}`};try{return await s(e)===await s(t)}catch{return false}}async function No(e){const t=JSON.parse(await L("plutil",["-convert","json","-o","-",O.join(e,"Info.plist")],{timeoutMs:1e4}));if(!t.CFBundleIdentifier)throw new Error(`${e} has no CFBundleIdentifier`);return String(t.CFBundleIdentifier)}async function it(e,t){const s=O.join(e,"Containers/Data/Application");let i;try{i=await W.readdir(s)}catch{return null}for(const o of i){const n=O.join(s,o,".com.apple.mobile_container_manager.metadata.plist");if(!ge(n))continue;const a=await _("plutil",["-extract","MCMMetadataIdentifier","raw","-o","-",n],{timeoutMs:5e3});if(a.code===0&&a.stdout.trim()===t)return O.join(s,o)}return null}function Co(e,t){return O.join(e,"Library/Application Support",t,"RCTAsyncLocalStorage_V1")}async function As(e,t,s,i){i();await W.mkdir(t,{recursive:true});const o=["-a","--delete"];for(const n of s)o.push("--exclude",n);o.push(e.replace(/\/?$/,"/"),t.replace(/\/?$/,"/"));i();await L("rsync",o,{timeoutMs:6e4})}async function Rs(e,t,s,i=()=>{}){i();const o=Le(s);if(o)throw new Error(o);const n=[...Ds.map(c=>`react-buoy/${c}`),`react-buoy/${Do}`];if(ge(O.join(s,"Documents")))await As(O.join(s,"Documents"),O.join(e,"Documents"),n,i);if(ge(O.join(s,"AsyncStorage/manifest.json"))){await As(O.join(s,"AsyncStorage"),Co(e,t),[],i)}const a=O.join(s,"Preferences",`${t}.plist`);if(ge(a)){i();await W.mkdir(O.join(e,"Library/Preferences"),{recursive:true});await W.copyFile(a,O.join(e,"Library/Preferences",`${t}.plist`))}const r=O.join(e,"Documents/react-buoy");try{for(const c of await W.readdir(r)){i();if(c.startsWith("@buoy~1floating-tool~1"))await W.rm(O.join(r,c),{force:true,recursive:true})}}catch{}}async function ot(e,t=()=>{}){t();const s=[];const i=O.join(e,"Documents/react-buoy");let o=[];try{o=await W.readdir(i)}catch{return s}for(const n of o){if(Ds.includes(n)||n.startsWith("@buoy~1floating-tool~1")){t();await W.rm(O.join(i,n),{force:true,recursive:true});s.push(n)}}return s}async function Ps(e){let t;try{t=await W.readFile(O.join(e,"Documents/react-buoy/@react_buoy_sync_device_id.json"),"utf8")}catch{return null}try{const s=JSON.parse(t);if(typeof s==="string")return s||null;if(typeof s?.value==="string")return s.value||null}catch{}return t.trim()||null}var Ns=2*60*6e4;var xo=10;var Lo=5;var Uo=.8;var ye=e=>Math.round(e*10)/10;var jo=e=>{if(!e.length)return 0;const t=[...e].sort((i,o)=>i-o);const s=t.length>>1;return t.length%2?t[s]:(t[s-1]+t[s])/2};var Cs=class{constructor(e,t,s=1e3,i=Date.now){this.host=e;this.target=t;this.periodMs=s;this.now=i;this.samples=[];this.prev=null;this.pids=[];this.treeAge=0;this.treeKey="";this.sampling=false;this.ticks=0;this.lastProcs=null;this.rates={ours:0,graphics:0,gpuPct:null};this.cpuCount=0}async tick(){if(this.sampling)return;this.sampling=true;try{const e=this.target();if(!e){this.prev=null;this.treeKey="";return}const t=`${e.leaseId}:${e.udids.join(",")}`;if(t!==this.treeKey||++this.treeAge>=xo){const f=await Promise.all(e.udids.map(w=>this.host.simTree(w)));this.pids=f.flat().map(w=>w.pid);this.treeAge=0;if(t!==this.treeKey){this.prev=null;this.lastProcs=null;this.ticks=0;this.rates={ours:0,graphics:0,gpuPct:null}}this.treeKey=t}const s=this.ticks++;const i=await this.host.loadCounters(this.pids,s<2||s%Lo===0);this.cpuCount=i.cpuCount;const o=this.now();const n=this.prev;this.prev={...i,at:o,leaseId:e.leaseId};const a=f=>(i.hostTotalMs-f)/Math.max(1,i.cpuCount);if(i.pidsCpuMs!==null&&i.graphicsCpuMs!==null){const f=this.lastProcs;const w=f?a(f.hostTotalMs):0;if(f&&w>0){this.rates.ours=Math.max(0,(i.pidsCpuMs-f.pidsCpuMs)/w);this.rates.graphics=Math.max(0,(i.graphicsCpuMs-f.graphicsCpuMs)/w)}this.rates.gpuPct=i.gpuPct;this.lastProcs={hostTotalMs:i.hostTotalMs,pidsCpuMs:i.pidsCpuMs,graphicsCpuMs:i.graphicsCpuMs}}if(!n||n.leaseId!==e.leaseId)return;const r=a(n.hostTotalMs);if(r<=0)return;const c=Math.max(0,(i.hostBusyMs-n.hostBusyMs)/r);const l=this.rates.ours;const d=this.rates.graphics;this.samples.push({at:Math.round((o+n.at)/2),leaseId:e.leaseId,freeCores:ye(Math.max(0,i.cpuCount-c)),ourCores:ye(l),graphicsCores:ye(d),outsideCores:ye(Math.max(0,c-l-d)),gpuPct:this.rates.gpuPct,memLevel:i.memPressureLevel,swapMB:i.swapUsedMB,thermal:i.thermalLevel});const h=o-Ns;while(this.samples.length&&this.samples[0].at<h)this.samples.shift()}finally{this.sampling=false}}before(e,t){for(let s=this.samples.length-1;s>=0;s--){const i=this.samples[s];if(i.at<t)return i.leaseId===e?i:void 0}return void 0}latest(){return this.samples[this.samples.length-1]}verdicts(e,t,s){const i=this.periodMs/2;const o=t.map(a=>{const r=this.samples.filter(l=>l.at>=a.from-i&&l.at<=a.to+i);const c=r.filter(l=>l.leaseId===e);return{w:a,all:r,mine:c}});const n=jo(o.filter(a=>a.mine.length).map(a=>Tt(a.mine.map(r=>r.outsideCores))));return o.map(({w:a,all:r,mine:c})=>{const l=Math.max(1,Math.round((a.to-a.from)/this.periodMs));const d={id:a.id,fair:false,seconds:c.length};if(!r.length&&(a.from>this.now()+6e4||a.to<this.now()-Ns)){return{...d,reason:"This run's time doesn't match the Mac's clock. Was the app's clock moved? Put it back and run again."}}if(!c.length||c.length<r.length||c.length<Math.floor(l*Uo)){return{...d,reason:"No load record for all of this run. Hold perf-slot for the whole test."}}const h=c.map(y=>y.outsideCores);const f=c.map(y=>y.gpuPct).filter(y=>y!==null);const w=c.map(y=>y.thermal).filter(y=>y!==null);const v={...d,freeCoresMin:Math.min(...c.map(y=>y.freeCores)),outsideCoresAvg:ye(Tt(h)),outsideCoresMax:Math.max(...h),ourCoresAvg:ye(Tt(c.map(y=>y.ourCores))),graphicsCoresMax:Math.max(...c.map(y=>y.graphicsCores)),gpuMaxPct:f.length?Math.max(...f):null,swapGrowthMB:Math.round(Math.max(0,c[c.length-1].swapMB-(this.before(e,a.from)?.swapMB??c[0].swapMB))),thermalMax:w.length?Math.max(...w):null};if(v.thermalMax!=null&&v.thermalMax>s.maxThermalLevel){return{...v,reason:"The Mac got hot and slowed itself down. Let it cool, then run again."}}if(c.some(y=>y.memLevel>=4)){return{...v,reason:"The Mac ran low on memory. Close apps or stop sims you don't need."}}if(v.swapGrowthMB>s.maxSwapGrowthMB){return{...v,reason:`The Mac swapped ${v.swapGrowthMB} MB of memory to disk.`}}if(v.freeCoresMin<s.minFreeCores){return{...v,reason:`The Mac ran out of spare CPU (${v.freeCoresMin} cores free, needs ${s.minFreeCores}).`}}const k=ye(v.outsideCoresMax-n);if(k>s.maxSwingCores){return{...v,reason:`Other work on the Mac jumped by ${k} cores during this run.`}}return{...v,fair:true}})}};function Tt(e){return e.length?e.reduce((t,s)=>t+s,0)/e.length:0}function xs(e){const t=e.trim().split(":").map(Number);if(!t.length||t.some(s=>!Number.isFinite(s)))return 0;return Math.round(t.reduce((s,i)=>s*60+i,0)*1e3)}import{spawn as Ls}from"child_process";import{randomBytes as Fo}from"crypto";import{existsSync as Dt,mkdirSync as Go,openSync as Wo,closeSync as Ho,promises as ve,writeFileSync as Us}from"fs";import qo from"net";import Y from"path";import{createHash as Yo}from"crypto";import{createReadStream as zo,createWriteStream as Ko,existsSync as Ue,promises as se,readFileSync as Qo}from"fs";import At from"os";import j from"path";import{Readable as Vo}from"stream";import{pipeline as js}from"stream/promises";var je="https://dl.google.com/android/repository/";var Jo="channel-0";var ne={api:36,tag:"aosp_atd",abi:Xo()};function Xo(){return At.arch()==="arm64"?"arm64-v8a":"x86_64"}function nt(e){return`system-images/android-${e.api}/${e.tag}/${e.abi}/`}function Fs(e){return j.join(e,"sdk")}function Rt(e=process.env){const t=At.homedir();const s=[e.ANDROID_HOME,e.ANDROID_SDK_ROOT,j.join(t,"Library/Android/sdk"),j.join(t,"Android/Sdk")];return[...new Set(s.filter(i=>!!i))]}function Gs(e){try{const t=/Pkg\.Revision=([\d.]+)/.exec(Qo(j.join(e,"emulator/source.properties"),"utf8"));return t?.[1]??null}catch{return null}}function Pt(e,t){const s=e.split(".").map(Number);const i=t.split(".").map(Number);for(let o=0;o<Math.max(s.length,i.length);o++){const n=(s[o]??0)-(i[o]??0);if(n)return n}return 0}var Ws="35.4.9";function Fe(e,t,s=ne){const i=j.join(e,"emulator",process.platform==="win32"?"emulator.exe":"emulator");const o=j.join(e,"platform-tools",process.platform==="win32"?"adb.exe":"adb");const n=Gs(e);if(!Ue(i)||!Ue(o)||!n)return null;return{root:e,source:t,emulator:i,emulatorVersion:n,adb:o,hasImage:Ue(j.join(e,nt(s),"system.img"))}}function Ge(e,t=ne,s="auto"){const i=Fe(Fs(e),"buoy",t);if(s!=="system"&&i?.hasImage&&Pt(i.emulatorVersion,Ws)>=0)return i;if(s==="buoy")return i;for(const o of Rt()){const n=Fe(o,"system",t);if(n?.hasImage&&Pt(n.emulatorVersion,Ws)>=0)return n}return s==="system"?null:i}function Zo(e){return[...e.matchAll(/<remotePackage path="([^"]+)">([\s\S]*?)<\/remotePackage>/g)].map(t=>({path:t[1],body:t[2]}))}function en(e){const t=/<revision>([\s\S]*?)<\/revision>/.exec(e)?.[1]??"";return["major","minor","micro"].map(s=>new RegExp(`<${s}>(\\d+)</${s}>`).exec(t)?.[1]).filter(Boolean).join(".")}function tn(){return process.platform==="darwin"?"macosx":process.platform==="win32"?"windows":"linux"}function sn(){return At.arch()==="arm64"?"aarch64":"x64"}function Nt(e,t,s,i,o={}){const n=o.os??tn();const a=o.arch??sn();let r=null;for(const c of Zo(e)){if(c.path!==t)continue;if(!c.body.includes(`<channelRef ref="${Jo}"/>`))continue;const l=en(c.body);const d=/<uses-license ref="([^"]+)"/.exec(c.body)?.[1]??"";for(const h of c.body.matchAll(/<archive>([\s\S]*?)<\/archive>/g)){const f=h[1];const w=/<host-os>([^<]+)<\/host-os>/.exec(f)?.[1];const v=/<host-arch>([^<]+)<\/host-arch>/.exec(f)?.[1];if(w&&w!==n)continue;if(v&&v!==a)continue;const k=/<url>([^<]+)<\/url>/.exec(f)?.[1];const y=/<checksum type="sha1">([0-9a-f]+)<\/checksum>/.exec(f)?.[1];const b=Number(/<size>(\d+)<\/size>/.exec(f)?.[1]??0);if(!k||!y)continue;const E=/<display-name>([^<]+)<\/display-name>/.exec(c.body)?.[1]??t;if(!r||Pt(l,r.rev)>0){r={rev:l,a:{name:`${E} ${l}`,url:s+k,sha1:y,size:b,license:d,dest:i}}}}}return r?.a??null}function Hs(e,t){const s=new RegExp(`<license id="${t}" type="text">([\\s\\S]*?)</license>`).exec(e);return s?s[1].replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&").trim():null}async function qs(e){const t=await fetch(e);if(!t.ok)throw new Error(`GET ${e} failed: ${t.status}`);return t.text()}async function on(e=ne){const t=await qs(`${je}repository2-3.xml`);const s=`${je}sys-img/${e.tag}/`;const i=await qs(`${s}sys-img2-3.xml`);const o=Nt(t,"emulator",je,"emulator");const n=Nt(t,"platform-tools",je,"platform-tools");const a=Nt(i,`system-images;android-${e.api};${e.tag};${e.abi}`,s,nt(e));const r=[!o&&"emulator",!n&&"platform-tools",!a&&`${e.tag} image for Android ${e.api}`].filter(Boolean);if(r.length)throw new Error(`Google's list has no stable ${r.join(", ")} for this computer.`);const c=[o,n,a];const l={};for(const d of c){if(l[d.license])continue;l[d.license]=Hs(t,d.license)??Hs(i,d.license)??`(See ${je} for license ${d.license}.)`}return{archives:c,licenses:l}}async function Ys(e){const t=Yo("sha1");await js(zo(e),t);return t.digest("hex")}async function nn(e,t,s=()=>{}){if(process.platform==="win32")throw new Error("The Android farm can't set up its own SDK on Windows yet.");const i=Fs(e);const o=j.join(e,"download");await se.mkdir(o,{recursive:true});await Promise.all(t.archives.map(async a=>{const r=j.join(i,a.dest);if(a.dest==="emulator"?Gs(i):Ue(j.join(r,a.dest==="platform-tools"?"adb":"system.img"))){s(`${a.name}: already here.`);return}const c=j.join(o,j.basename(a.url));if(!Ue(c)||await Ys(c)!==a.sha1){s(`Downloading ${a.name} (${Math.round(a.size/1024**2)} MB)\u2026`);const h=await fetch(a.url);if(!h.ok||!h.body)throw new Error(`GET ${a.url} failed: ${h.status}`);await js(Vo.fromWeb(h.body),Ko(c));const f=await Ys(c);if(f!==a.sha1)throw new Error(`${a.name} failed its checksum (${f}).`)}const l=j.join(o,`unzip-${j.basename(a.url)}`);await se.rm(l,{recursive:true,force:true});await se.mkdir(l,{recursive:true});await L("unzip",["-q",c,"-d",l],{timeoutMs:15*6e4});const[d]=await se.readdir(l);await se.mkdir(j.dirname(r),{recursive:true});await se.rm(r,{recursive:true,force:true});await se.rename(j.join(l,d),r);await se.rm(l,{recursive:true,force:true});await se.rm(c,{force:true});s(`${a.name}: ready.`)}));await se.rm(o,{recursive:true,force:true});const n=Fe(i,"buoy");if(!n)throw new Error(`The SDK at ${i} is not complete after the download.`);return n}import{existsSync as an,promises as Ct,readFileSync as rn}from"fs";import cn from"path";var xt="android-3gb";var zs={"android-3gb":{name:"Low-end Android (3 GB)",ramMB:3072,cores:4,width:720,height:1600,density:320,heapGrowthMB:192,heapMB:512,source:"built in"},"pixel-4a":{name:"Pixel 4a",ramMB:6144,cores:8,width:1080,height:2340,density:440,heapGrowthMB:256,heapMB:512,source:"built in"}};function Ks(e){return cn.join(e,"phones.json")}function Lt(e){const t={...zs};const s=Ks(e);if(an(s)){try{Object.assign(t,JSON.parse(rn(s,"utf8")))}catch{}}return t}async function ln(e,t,s){const i=Ks(e);let o={};try{o=JSON.parse(await Ct.readFile(i,"utf8"))}catch{}o[t]=s;await Ct.mkdir(e,{recursive:true});await Ct.writeFile(i,JSON.stringify(o,null,2)+"\n")}var dn=[1024,1536,2048,3072,4096,6144,8192,12288,16384,24576];function pn(e,t){const s=h=>new RegExp(`\\[${h.replace(/\./g,"\\.")}\\]: \\[([^\\]]*)\\]`).exec(e.props)?.[1]??"";const i=Number(/MemTotal:\s+(\d+)/.exec(e.meminfo)?.[1]??0);const o=i?dn.find(h=>h>=i/1024)??Math.ceil(i/1024/1024)*1024:0;const n=/Override size: (\d+)x(\d+)/.exec(e.wmSize)??/Physical size: (\d+)x(\d+)/.exec(e.wmSize);const a=/Override density: (\d+)/.exec(e.wmDensity)??/Physical density: (\d+)/.exec(e.wmDensity);const r=h=>Number(/^(\d+)m$/i.exec(h)?.[1]??0);const c=s("ro.product.model")||"Android phone";const l={name:c,ramMB:o||3072,cores:Number(e.nproc.trim())||4,width:n?Number(n[1]):1080,height:n?Number(n[2]):2400,density:a?Number(a[1]):420,heapGrowthMB:r(s("dalvik.vm.heapgrowthlimit"))||256,heapMB:r(s("dalvik.vm.heapsize"))||512,source:t};const d=c.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"android-phone";return{id:d,phone:l}}import{promises as bc,readFileSync as un}from"fs";import Ut from"os";import jt from"path";import{promises as hn,existsSync as Ft,mkdirSync as Gt,readFileSync as Se,renameSync as Wt,writeFileSync as at,rmSync as We,openSync as mn,fsyncSync as fn,closeSync as wn,chmodSync as gn,readdirSync as yn,statSync as Qs}from"fs";import vn from"os";import H from"path";import{createHash as Sn,randomBytes as He}from"crypto";function A(){return process.env.BUOY_FARM_HOME||H.join(vn.homedir(),".buoy","farm")}var X=(e=A())=>({home:e,lock:H.join(e,"owner.lock"),lockOwner:H.join(e,"owner.lock","owner.json"),socket:H.join(e,"farmd.sock"),token:H.join(e,"token"),state:H.join(e,"state.json"),journal:H.join(e,"journal.ndjson"),log:H.join(e,"farmd.log"),shots:H.join(e,"shots")});function Ht(e=A()){Gt(e,{recursive:true,mode:448});try{gn(e,448)}catch{}}function Vs(e=A()){const t=X(e).token;try{const s=Se(t,"utf8").trim();if(s)return s}catch{}Ht(e);try{at(t,He(24).toString("hex")+"\n",{mode:384,flag:"wx"})}catch(s){if(s.code!=="EEXIST")throw s}for(let s=0;s<20;s++){const i=Se(t,"utf8").trim();if(i)return i;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,25)}throw new Error(`farm token at ${t} is empty`)}async function qe(e=A()){try{return JSON.parse(await hn.readFile(X(e).lockOwner,"utf8"))}catch{return null}}async function Ye(e){return!!e&&await J(e.pid,e.start,true)}async function bn(e,t){const s=H.join(e,"owner.claim");for(let i=0;i<20;i++){try{Gt(s,{mode:448});at(H.join(s,"pid"),`${process.pid}
|
|
5
|
+
${await Ie(process.pid)??""}`);try{return await t()}finally{We(s,{recursive:true,force:true})}}catch(o){if(o.code!=="EEXIST")throw o;const n=Qs(s,{throwIfNoEntry:false});if(n&&Date.now()-n.mtimeMs>3e4){let a=true;try{const[r,c]=Se(H.join(s,"pid"),"utf8").split("\n");a=!await J(Number(r),c||void 0)}catch{}if(a){try{Wt(s,H.join(e,`owner.claim-dead-${He(4).toString("hex")}`))}catch{}}}await new Promise(a=>setTimeout(a,100))}}return null}async function kn(e,t,s=A()){Ht(s);const i=X(s);const o=await Ie(process.pid)??"";for(let n=0;n<3;n++){try{Gt(i.lock,{mode:448})}catch(r){if(r.code!=="EEXIST")throw r;const c=await qe(s);if(c&&await Ye(c))return null;if(!c){await new Promise(h=>setTimeout(h,300));const d=await qe(s);if(d&&await Ye(d))return null}const l=await bn(s,async()=>{const d=await qe(s);if(d&&await Ye(d))return false;if(!Ft(i.lock))return true;Wt(i.lock,H.join(s,`owner.stale-${He(4).toString("hex")}`));return true});if(l===false)return null;continue}const a={pid:process.pid,start:o,version:e,host:t,since:new Date().toISOString()};at(i.lockOwner,JSON.stringify(a),{mode:384});Mn(s);return{info:a,release(){try{const r=JSON.parse(Se(i.lockOwner,"utf8"));if(r.pid!==process.pid)return}catch{return}try{We(i.socket,{force:true})}catch{}We(i.lock,{recursive:true,force:true})}}}return null}function Mn(e){try{for(const t of yn(e)){if(t.startsWith("owner.stale-")||t.startsWith("owner.claim-dead-"))We(H.join(e,t),{recursive:true,force:true})}}catch{}}function Js(){return{version:et,seq:0,farmId:He(6).toString("hex"),nextIndex:1,templates:{},sims:{},orphans:{},leases:{},runs:{},locks:{},requests:{},events:[],settings:{...Et}}}var Xs=e=>Sn("sha256").update(JSON.stringify(e)).digest("hex").slice(0,12);function Zs(e,t,s){const i=mn(e,s,384);try{at(i,t);fn(i)}finally{wn(i)}}var ei=class{constructor(e=A()){this.fenced=false;this.pending=[];this.open=new Map;this.p=X(e);Ht(e);this.state=this.load()}get shotsPath(){return this.p.shots}get homeDir(){return this.p.home}fence(){this.fenced=true}assertActive(){if(this.fenced)throw new Error("This farm has stopped.")}load(){let e=Js();if(Ft(this.p.state)){const o=JSON.parse(Se(this.p.state,"utf8"));if(o.version>et){throw new Error(`farm state is version ${o.version}; this farmd understands ${et}. Update Buoy.`)}e={...Js(),...o,settings:{...Et,...o.settings}}}delete e.settings.activeSlots;const t=new Map;const s=new Set;let i=e.seq;if(Ft(this.p.journal)){for(const o of Se(this.p.journal,"utf8").split("\n")){if(!o.trim())continue;let n;try{n=JSON.parse(o)}catch{continue}const{sum:a,...r}=n;if(Xs(r)!==a||n.seq<=e.seq)continue;i=Math.max(i,n.seq);if(n.type==="intent")t.set(n.seq,n);else{t.delete(n.seq);s.add(n.seq)}}}for(const o of e.openIntents??[]){if(!s.has(o.seq)&&!t.has(o.seq))t.set(o.seq,{...o,type:"intent",sum:""})}this.pending=[...t.values()].sort((o,n)=>o.seq-n.seq);for(const o of this.pending)this.open.set(o.seq,o);e.seq=i;e.openIntents=[];return e}intent(e,t){this.assertActive();const s=++this.state.seq;const i={seq:s,at:new Date().toISOString(),type:"intent",op:e,data:t};this.append(i);this.open.set(s,{...i,sum:""});return s}result(e,t,s){this.assertActive();this.open.delete(e);this.pending=this.pending.filter(i=>i.seq!==e);this.save();this.append({seq:e,at:new Date().toISOString(),type:"result",op:t,data:s})}append(e){this.assertActive();Zs(this.p.journal,JSON.stringify({...e,sum:Xs(e)})+"\n","a")}save(){this.assertActive();const e=`${this.p.state}.${He(4).toString("hex")}.tmp`;this.state.openIntents=[...this.open.values()].map(({seq:t,at:s,op:i,data:o})=>({seq:t,at:s,op:i,data:o}));Zs(e,JSON.stringify(this.state,null,1),"w");Wt(e,this.p.state);try{if(Qs(this.p.journal).size>2*1024*1024)We(this.p.journal,{force:true})}catch{}}event(e){this.assertActive();this.state.events.push({at:new Date().toISOString(),...e});if(this.state.events.length>300)this.state.events.splice(0,this.state.events.length-300)}};function _n(e=A()){try{const t=JSON.parse(Se(X(e).state,"utf8"));return new Set([...Object.keys(t.sims??{}),...Object.keys(t.orphans??{}),...Object.values(t.templates??{}).map(s=>s.udid)])}catch{return new Set}}var de=Number(process.env.BUOY_FARM_ADB_PORT)||5137;var En=1.3;function ze(e=A()){return jt.join(e,"android")}function ti(e){const t=s=>{const i=s.exec(e);return i?Number(i[1]):null};return{frames:t(/Total frames rendered: (\d+)/)??0,jankyPct:t(/Janky frames: \d+ \(([\d.]+)%\)/),p50:t(/50th percentile: (\d+)ms/),p90:t(/90th percentile: (\d+)ms/),p99:t(/99th percentile: (\d+)ms/)}}function $n(e){if(!e.length)return null;const t=[...e].sort((i,o)=>i-o);const s=Math.floor(t.length/2);return t.length%2?t[s]:Math.round((t[s-1]+t[s])/2)}function si(e){if(/Successfully loaded snapshot|Loading snapshot '?default_boot'? .*(done|succeeded)|snapshot.*loaded/i.test(e))return"snapshot";if(/Cold boot|failed to load snapshot|Snapshot load failed|could not load snapshot/i.test(e))return"cold";return"unknown"}async function In(){const e=Ut.totalmem()/1024**3;let t=Ut.freemem()/1024**3;if(process.platform==="darwin"){const o=await _("vm_stat",[],{timeoutMs:1e4});const n=Number(/page size of (\d+)/.exec(o.stdout)?.[1]??16384);const a=r=>Number(new RegExp(`${r}:\\s+(\\d+)`).exec(o.stdout)?.[1]??0);t=(a("Pages free")+a("Pages inactive")+a("Pages speculative")+a("Pages purgeable"))*n/1024**3}const s=await _("df",["-k",Ut.homedir()],{timeoutMs:1e4});const i=Number((s.stdout.trim().split("\n").pop()??"").split(/\s+/)[3]??0);return{availableGB:t,totalGB:e,freeDiskGB:i/1024**2}}async function Bn(e){if(process.platform==="darwin"){const i=await _("footprint",[String(e)],{timeoutMs:3e4});const o=/Footprint: ([\d.]+) (MB|GB|KB)/.exec(i.stdout);if(o)return Math.round(Number(o[1])*(o[2]==="GB"?1024:o[2]==="KB"?1/1024:1))}const t=await _("ps",["-o","rss=","-p",String(e)],{timeoutMs:5e3});const s=Number(t.stdout.trim());return s?Math.round(s/1024):null}async function ii(e){const t=await _("ps",["-o","time=","-p",String(e)],{timeoutMs:5e3});const s=/(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+(?:\.\d+)?)/.exec(t.stdout.trim());if(!s)return null;const[,i,o,n,a]=s;return((Number(i??0)*24+Number(o??0))*60+Number(n))*6e4+Number(a)*1e3}function qt(e,t){return oi(t).get(e)??null}function oi(e){const t=new Map;const s=a=>{try{return JSON.parse(un(a,"utf8"))}catch{return null}};const i=(a,r)=>{for(const c of Object.values(r??{})){const l=a?.[c.templateId];if(!l?.sdkRoot||!On(c.pid)||t.has(c.serial))continue;t.set(c.serial,{adb:jt.join(l.sdkRoot,"platform-tools","adb"),port:de})}};const o=s(X(e??A()).state);i(o?.androidTemplates,o?.androidCopies);const n=s(jt.join(ze(e),"state.json"));if(!o?.androidImported)i(n?.templates,n?.copies);return t}function On(e){try{process.kill(e,0);return true}catch(t){return t.code==="EPERM"}}var ni=5800;var ai=5998;var ri="BuoyFarm_";var Tn="sync_device_id|react_buoy_install";var Dn=["files/react-buoy","shared_prefs","app_webview","databases","app_flutter"];var ci=Number(process.env.BUOY_BROKER_PORT)||42831;var li=class{constructor(e={}){this.dir=ze(e.home);this.avdHome=Y.join(this.dir,"avd");this.adbPort=e.adbPort??de;this.sdkPref=e.sdk??"auto"}sdkFor(e=ne){const t=Ge(this.dir,e,this.sdkPref);return t?.hasImage?t:null}sdk(e=ne){const t=Ge(this.dir,e,this.sdkPref);if(!t)throw new Error("No Android SDK with an emulator and the slim image yet. Run: npx @buoy-gg/mcp farm android setup");if(!t.hasImage)throw new Error(`The SDK at ${t.root} has no ${nt(e)} image. Run: npx @buoy-gg/mcp farm android setup`);return t}sdkOf(e){const t=this.sdk(e.image);if(t.root!==e.sdkRoot){const s=Ge(this.dir,e.image,t.source==="buoy"?"system":"buoy");if(s?.root===e.sdkRoot)return s}return t}env(e){return{...process.env,ANDROID_HOME:e.root,ANDROID_SDK_ROOT:e.root,ANDROID_AVD_HOME:this.avdHome,ANDROID_ADB_SERVER_PORT:String(this.adbPort)}}run(e,t,s=3e4){return _(e.adb,t,{timeoutMs:s,env:this.env(e)})}adb(e,t,s=3e4){return this.run(this.sdkOf(e),t,s)}async shell(e,t,s,i=3e4){const o=await this.run(e,["-s",t,"shell",s],i);if(o.timedOut)throw new Error(`adb shell on ${t} timed out: ${s}`);if(o.code!==0)throw new Error(`adb shell on ${t} failed: ${s}: ${(o.stderr||o.stdout).trim().slice(0,300)}`);return o.stdout}stale(e){if(!e.usable)return e.note??"not ready";let t;try{t=this.sdkOf(e)}catch(s){return s.message}if(t.root!==e.sdkRoot||t.emulatorVersion!==e.emulatorVersion){return`It was saved with emulator ${e.emulatorVersion}; this SDK has ${t.emulatorVersion}. Snapshots don't load across versions.`}if(!Dt(Y.join(this.avdHome,`${e.avd}.avd`,"snapshots","default_boot","ram.bin")))return"Its snapshot is gone.";return null}async freePort(e){const t=s=>new Promise(i=>{const o=qo.createServer();o.once("error",()=>i(false));o.listen(s,"127.0.0.1",()=>o.close(()=>i(true)))});for(let s=ni;s<=ai;s+=2){if(e.has(s))continue;if(await t(s)&&await t(s+1))return s}throw new Error(`No free emulator port from ${ni} to ${ai}.`)}async launch(e,t,s,i,o){const n=Y.join(this.dir,"logs");await ve.mkdir(n,{recursive:true});const a=Y.join(n,o);const r=Wo(a,"w");const c=["-avd",t,"-port",String(s),"-gpu","host","-no-audio","-no-boot-anim","-camera-back","none","-camera-front","none",...i];const l=Ls(e.emulator,c,{detached:true,stdio:["ignore",r,r],env:this.env(e)});Ho(r);l.unref();if(!l.pid)throw new Error("The emulator did not start.");const d=await Ie(l.pid)??"";return{pid:l.pid,pidStart:d,log:a}}async waitBooted(e,t,s,i){const o=Date.now()+i;while(Date.now()<o){if(!await J(s))throw new Error(`The emulator for ${t} stopped while booting.`);const n=await this.run(e,["-s",t,"shell","getprop","sys.boot_completed"],5e3);if(n.code===0&&n.stdout.trim()==="1")return;await T(250)}throw new Error(`${t} did not finish booting in ${Math.round(i/1e3)} s.`)}async stopEmulator(e,t,s,i){if(e)await this.run(e,["-s",t,"emu","kill"],1e4);for(let o=0;o<300;o++){if(!await J(s,i))return;await T(100)}if(await J(s,i)){try{process.kill(s,"SIGKILL")}catch{}}}async startCopy(e,t,s={}){const i=this.sdkOf(e);await this.run(i,["start-server"],2e4);const o=`emulator-${t}`;const n=["-read-only","-no-snapshot-save",...s.window?[]:["-no-window"]];const a=await this.launch(i,e.avd,t,n,`${o}.log`);return{serial:o,port:t,...a}}async readyCopy(e,t,s={}){const i=this.sdkOf(e);await this.waitBooted(i,t.serial,t.pid,s.timeoutMs??5*6e4);let o="unknown";try{o=si(await ve.readFile(t.log,"utf8"))??"unknown"}catch{}await this.shell(i,t.serial,"setprop debug.hwui.drawing_enabled 1; logcat -c");for(const n of[`${ci}`,...e.reverse??[],...s.reverse??[]]){const[a,r]=n.split(":");const c=await this.run(i,["-s",t.serial,"reverse",`tcp:${a}`,`tcp:${r??a}`],1e4);if(c.code!==0)throw new Error(`Could not link port ${a} on ${t.serial}: ${(c.stderr||c.stdout).trim().slice(0,200)}`)}return{bootedFrom:o}}async stopCopy(e,t){let s=null;try{s=e?this.sdkOf(e):this.sdkFor(ne)}catch{}if(await J(t.pid,t.pidStart))await this.stopEmulator(s,t.serial,t.pid,t.pidStart)}alive(e,t){return J(e,t)}async treePids(e){return Ot(await tt(),e).map(t=>t.pid)}async findEmulators(){const e=[];for(const t of await tt()){const s=/-avd (\S+)/.exec(t.command)?.[1];const i=Number(/-port (\d+)/.exec(t.command)?.[1]);if(!s?.startsWith(ri)||!i)continue;if(e.some(o=>o.port===i))continue;e.push({pid:t.pid,pidStart:t.start,port:i,avd:s})}return e}async screenshot(e,t,s,i=2e4){const o=this.sdkOf(e);const n=await new Promise(a=>{const r=Ls(o.adb,["-s",t,"exec-out","screencap","-p"],{env:this.env(o)});const c=[];r.stdout.on("data",d=>c.push(d));r.on("error",()=>a({code:-1,buf:Buffer.alloc(0)}));const l=setTimeout(()=>r.kill("SIGKILL"),i);r.on("close",d=>{clearTimeout(l);a({code:d,buf:Buffer.concat(c)})})});if(n.code!==0||n.buf.length<100)return false;await ve.mkdir(Y.dirname(Y.resolve(s)),{recursive:true});await ve.writeFile(s,n.buf);return true}async launchApp(e,t,s){const i=this.sdkOf(e);await this.shell(i,t,`am force-stop ${e.packageName}`);const o=s??e.launchUrl;const n=o?`am start -W -a android.intent.action.VIEW -d ${di(o)} ${e.packageName}`:`am start -W -n ${e.activity}`;const a=await this.shell(i,t,n,12e4);const r=/TotalTime: (\d+)/.exec(a);return{totalMs:r?Number(r[1]):null,output:a.trim()}}async openUrl(e,t,s){await this.shell(this.sdkOf(e),t,`am start -a android.intent.action.VIEW -d ${di(s)} ${e.packageName}`,6e4)}async install(e,t,s){const i=this.sdkOf(e);const o=await this.run(i,["-s",t,"install","-r","-g",Y.resolve(s)],10*6e4);if(o.code!==0||!/Success/.test(o.stdout))throw new Error(`${t}: install failed: ${(o.stderr||o.stdout).trim().slice(0,300)}`);await this.shell(i,t,`cmd package compile -m speed -f ${e.packageName}`,10*6e4)}async installId(e,t){const s=await this.run(this.sdkOf(e),["-s",t,"shell",`run-as ${e.packageName} cat files/react-buoy/@react_buoy_sync_device_id.json`],5e3);const i=s.code===0?s.stdout.trim().replace(/^"|"$/g,""):"";return/^[\w-]{4,64}$/.test(i)?i:null}async appMemory(e,t){const s=this.sdkOf(e);const i=Number((await this.shell(s,t,`pidof ${e.packageName}; true`)).trim().split(/\s+/)[0])||null;if(!i)return{pid:null,pssMB:null};const o=await this.shell(s,t,`dumpsys meminfo ${i}`,2e4).catch(()=>"");const n=Number(/TOTAL PSS:\s+(\d+)/.exec(o)?.[1]??/TOTAL\s+(\d+)/.exec(o)?.[1]??0);return{pid:i,pssMB:n?Math.round(n/1024):null}}async memoryKill(e,t,s){const i=await this.shell(this.sdkOf(e),t,"logcat -d -b events -s am_kill:I lowmemorykiller:I; true",15e3).catch(()=>"");for(const o of i.split("\n")){const n=/am_kill\s*:\s*\[\d+,(\d+),([^,]+),-?\d+,([^\]]*)\]/.exec(o);if(n&&Number(n[1])===s&&n[2].trim()===e.packageName&&/low ?mem|lmk|oom|memory/i.test(n[3]))return n[3].trim()}return null}writeAvd(e,t,s){const i=Y.join(this.avdHome,`${e}.avd`);Go(i,{recursive:true});const o={AvdId:e,"avd.ini.displayname":e.replace(/_/g," "),"avd.ini.encoding":"UTF-8","abi.type":s.abi,"hw.cpu.arch":s.abi==="arm64-v8a"?"arm64":"x86_64","image.sysdir.1":nt(s),"tag.id":s.tag,"tag.display":s.tag==="aosp_atd"?"AOSP ATD":"Google ATD",target:`android-${s.api}`,"hw.ramSize":t.ramMB,"hw.cpu.ncore":t.cores,"hw.lcd.width":t.width,"hw.lcd.height":t.height,"hw.lcd.density":t.density,"disk.dataPartition.size":"6G","hw.gpu.enabled":"yes","hw.gpu.mode":"host","hw.keyboard":"yes","hw.audioInput":"no","hw.audioOutput":"no","hw.camera.back":"none","hw.camera.front":"none","fastboot.forceColdBoot":"no","fastboot.forceFastBoot":"yes",showDeviceFrame:"no"};Us(Y.join(i,"config.ini"),Object.entries(o).map(([n,a])=>`${n}=${a}`).join("\n")+"\n");Us(Y.join(this.avdHome,`${e}.ini`),`avd.ini.encoding=UTF-8
|
|
6
6
|
path=${i}
|
|
7
7
|
path.rel=avd/${e}.avd
|
|
8
8
|
target=android-${s.api}
|
|
9
|
-
`);return i}async buildTemplate(e,t){const s=
|
|
9
|
+
`);return i}async buildTemplate(e,t){const s=Y.resolve(e.apk);if(!Dt(s)||!s.endsWith(".apk"))throw new Error(`No APK at ${s}.`);const i=e.image??ne;const o=this.sdk(i);const n=e.phoneId??xt;const a=Lt(this.dir)[n];if(!a)throw new Error(`No phone "${n}". Try: farm android phone list`);const r=Fo(3).toString("hex");const c=`${ri}${r}`;const l=await ve.stat(s);const d={id:r,avd:c,apk:s,apkSize:l.size,apkMtimeMs:l.mtimeMs,packageName:"",activity:"",phoneId:n,phone:a,image:i,sdkRoot:o.root,emulatorVersion:o.emulatorVersion,createdAt:new Date().toISOString(),usable:false,note:"Being built.",...e.reverse?.length?{reverse:e.reverse}:{},...e.launchUrl?{launchUrl:e.launchUrl}:{}};const h=this.writeAvd(c,a,i);await this.run(o,["start-server"],2e4);const f=await this.freePort(new Set((await this.findEmulators()).map(y=>y.port)));const w=`emulator-${f}`;const v=Date.now();t(`Template ${r}: first boot of ${a.name} (${i.tag}, Android ${i.api})\u2026`);const k=await this.launch(o,c,f,["-no-window","-no-snapshot-load","-no-snapshot-save"],`template-${r}.log`);try{await this.waitBooted(o,w,k.pid,e.bootTimeoutMs??10*6e4);t(`Booted in ${((Date.now()-v)/1e3).toFixed(1)} s. Setting up\u2026`);const y=(g,S)=>this.shell(o,w,g,S);await y("setprop debug.hwui.drawing_enabled 1");await this.setHeap(o,w,a,t);await y("settings put global package_verifier_enable 0; settings put system screen_off_timeout 2147483647; svc power stayon true; input keyevent 82");const b=new Set((await y("pm list packages -3")).split("\n").map(g=>g.trim()).filter(Boolean));t("Installing the app\u2026");const E=await this.run(o,["-s",w,"install","-r","-g",s],10*6e4);if(E.code!==0||!/Success/.test(E.stdout))throw new Error(`Install failed: ${(E.stderr||E.stdout).trim().slice(0,400)}`);const N=(await y("pm list packages -3")).split("\n").map(g=>g.trim()).filter(Boolean);const D=N.filter(g=>!b.has(g)).map(g=>g.replace(/^package:/,""));if(D.length!==1)throw new Error(`Could not tell which package the APK installed (${D.join(", ")||"none"}).`);d.packageName=D[0];d.activity=(await y(`cmd package resolve-activity --brief -c android.intent.category.LAUNCHER ${d.packageName}`)).trim().split("\n").pop().trim();if(!d.activity.includes("/"))throw new Error(`${d.packageName} has no launcher screen.`);t("Compiling the app ahead of time, so every copy times the same code\u2026");await y(`cmd package compile -m speed -f ${d.packageName}`,10*6e4);for(const g of[`${ci}`,...e.reverse??[]]){const[S,M]=g.split(":");await this.run(o,["-s",w,"reverse",`tcp:${S}`,`tcp:${M??S}`],1e4)}await y(`am start -W -n ${d.activity}`,12e4);await T(8e3);await y(`am force-stop ${d.packageName}`);const m=await this.wipeIdentity(o,w,d.packageName);if(m.length)t(`Removed the app's Buoy id so each copy makes its own (${m.length} file${m.length===1?"":"s"}).`);t("Saving the snapshot\u2026");const u=await this.run(o,["-s",w,"emu","avd","snapshot","save","default_boot"],5*6e4);if(u.code!==0||/KO/.test(u.stdout))throw new Error(`Snapshot save failed: ${(u.stdout+u.stderr).trim().slice(0,300)}`)}catch(y){await this.stopEmulator(o,w,k.pid,k.pidStart);await this.removeTemplateFiles(d).catch(()=>{});throw y}await this.stopEmulator(o,w,k.pid,k.pidStart);if(!Dt(Y.join(h,"snapshots","default_boot","ram.bin"))){await this.removeTemplateFiles(d).catch(()=>{});throw new Error("No snapshot was saved.")}d.usable=true;d.note=void 0;return d}async removeTemplateFiles(e){await ve.rm(Y.join(this.avdHome,`${e.avd}.avd`),{recursive:true,force:true});await ve.rm(Y.join(this.avdHome,`${e.avd}.ini`),{force:true})}async setHeap(e,t,s,i){const o=(c,l)=>this.shell(e,t,c,l);const n=(await o("getprop dalvik.vm.heapgrowthlimit; getprop dalvik.vm.heapsize")).split("\n").map(c=>c.trim());if(n[0]===`${s.heapGrowthMB}m`&&n[1]===`${s.heapMB}m`)return;const a=await this.run(e,["-s",t,"root"],3e4);if(a.code!==0||/cannot run as root/i.test(a.stdout+a.stderr)){i(`Could not set the heap limit on this image (${(a.stdout+a.stderr).trim()}). Apps keep ${n[0]}.`);return}await T(1500);await this.run(e,["-s",t,"wait-for-device"],6e4);await o(`setprop dalvik.vm.heapgrowthlimit ${s.heapGrowthMB}m && setprop dalvik.vm.heapsize ${s.heapMB}m && stop && start`);const r=Date.now()+3*6e4;for(;;){const c=await this.run(e,["-s",t,"shell","service check package; pidof system_server"],5e3);if(/Service package: found/.test(c.stdout)&&/\d/.test(c.stdout.split("\n")[1]??""))break;if(Date.now()>r)throw new Error("Android's framework did not come back after setting the heap limit.");await T(500)}await T(5e3);await this.run(e,["-s",t,"unroot"],3e4);await T(1500);await this.run(e,["-s",t,"wait-for-device"],6e4)}async wipeIdentity(e,t,s){const i=await this.run(e,["-s",t,"root"],3e4);if(i.code!==0||/cannot run as root/i.test(i.stdout+i.stderr)){throw new Error(`Can't clear the app's Buoy id on this image (${(i.stdout+i.stderr).trim()}). Copies would share one id. Use the aosp_atd image.`)}await T(1500);await this.run(e,["-s",t,"wait-for-device"],6e4);const o=`/data/data/${s}`;const n=Dn.map(d=>`${o}/${d}`).join(" ");const a="-name '*sync_device_id*' -o -name '*react_buoy_install*' -o -name '@buoy~1floating-tool~1*'";const r=async()=>(await this.shell(e,t,`find ${n} \\( ${a} \\) 2>/dev/null; grep -rlE '${Tn}' ${n} 2>/dev/null; true`)).split("\n").map(d=>d.trim()).filter((d,h,f)=>d.startsWith(`${o}/`)&&f.indexOf(d)===h);const c=await r();for(const d of c)await this.shell(e,t,`rm -rf '${d.replace(/'/g,"")}'`);const l=await r();await this.run(e,["-s",t,"unroot"],3e4);await T(1500);await this.run(e,["-s",t,"wait-for-device"],6e4);if(l.length)throw new Error(`Could not clear the app's Buoy id (${l.join(", ")}).`);return c}async bench(e,t,s={}){const i=this.sdkOf(e);const o=[];for(let h=0;h<(s.launches??3);h++){const f=await this.launchApp(e,t.serial);if(f.totalMs!=null)o.push(f.totalMs)}await T(2e3);await this.shell(i,t.serial,`dumpsys gfxinfo ${e.packageName} reset`);const n=await ii(t.pid);const a=Date.now();await T((s.seconds??15)*1e3);const r=await ii(t.pid);const c=ti(await this.shell(i,t.serial,`dumpsys gfxinfo ${e.packageName}`));const l=await this.shell(i,t.serial,`dumpsys meminfo ${e.packageName}`);const d=Number(/TOTAL PSS:\s+(\d+)/.exec(l)?.[1]??/TOTAL\s+(\d+)/.exec(l)?.[1]??0);return{serial:t.serial,appStartMs:$n(o),appStarts:o,frames:c,appMemoryMB:Math.round(d/1024),macMemoryMB:await Bn(t.pid),macCpuPct:n!=null&&r!=null?Math.round((r-n)/(Date.now()-a)*100):null}}};function di(e){return`'${e.replace(/'/g,"'\\''")}'`}import{execFile as An}from"child_process";import{promises as x,readFileSync as Rn}from"fs";import Yt from"os";import K from"path";import{promisify as Pn}from"util";var Nn=`// BuoyPhoneProfile: makes an app on the iOS Simulator act like a real iPhone.
|
|
10
10
|
//
|
|
11
11
|
// A simulator app is a plain Mac process. It sees the Mac's RAM and cores and
|
|
12
12
|
// nothing ever stops it from growing. The farm loads this library into the app
|
|
@@ -365,9 +365,9 @@ static void buoyPhoneProfileInit(void) {
|
|
|
365
365
|
startWatchdog();
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
|
-
`;var Nn="499c4e3de0f8";var Cn='// PhoneProbe: a tiny app `farm phone measure` puts on a real iPhone for a few\n// seconds. It prints what the phone reports, then quits:\n// physicalMemory the RAM iOS reports\n// cpus the CPU count\n// footprint this app\'s own footprint right now\n// available os_proc_available_memory(): how much more this app may use\n// footprint + available is the per-app limit (jetsam) on this phone.\n\n#import <UIKit/UIKit.h>\n#import <mach/mach.h>\n#import <os/proc.h>\n\nstatic uint64_t footprintBytes(void) {\n task_vm_info_data_t info;\n mach_msg_type_number_t count = TASK_VM_INFO_COUNT;\n if (task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count) != KERN_SUCCESS) return 0;\n return info.phys_footprint;\n}\n\n// iOS 27 ends apps that don\'t use scenes, so the window comes from a scene delegate.\n@interface ProbeSceneDelegate : UIResponder <UIWindowSceneDelegate>\n@property (nonatomic, strong) UIWindow *window;\n@end\n\n@implementation ProbeSceneDelegate\n- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)options {\n self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];\n self.window.rootViewController = [UIViewController new];\n self.window.rootViewController.view.backgroundColor = UIColor.systemTealColor;\n [self.window makeKeyAndVisible];\n}\n@end\n\n@interface ProbeDelegate : UIResponder <UIApplicationDelegate>\n@end\n\n@implementation ProbeDelegate\n- (UISceneConfiguration *)application:(UIApplication *)app configurationForConnectingSceneSession:(UISceneSession *)session options:(UISceneConnectionOptions *)options {\n UISceneConfiguration *c = [[UISceneConfiguration alloc] initWithName:@"Default" sessionRole:session.role];\n c.delegateClass = ProbeSceneDelegate.class;\n return c;\n}\n- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts {\n NSProcessInfo *pi = NSProcessInfo.processInfo;\n fprintf(stdout, "BUOY_PROBE physicalMemory=%llu cpus=%lu footprint=%llu available=%zu\\n",\n pi.physicalMemory, (unsigned long)pi.processorCount, footprintBytes(), os_proc_available_memory());\n fflush(stdout);\n dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{ exit(0); });\n return YES;\n}\n@end\n\nint main(int argc, char *argv[]) {\n @autoreleasepool {\n return UIApplicationMain(argc, argv, nil, NSStringFromClass(ProbeDelegate.class));\n }\n}\n';var rt=Rn(Dn);var pi={"iphone-se-2nd-gen":{name:"iPhone SE (2nd generation)",productType:"iPhone12,8",ramMB:2957,cpus:6,limitMB:2098,warnMB:1707,warnSource:"measured (plain memory test); the phone warned at 1,134 MB in a photos test",ios:"26.5",measuredAt:"2026-10-14"}};var ui="iphone-se-2nd-gen";var zt="off";function Z(){return process.env.BUOY_FARM_HOME||K.join(Yt.homedir(),".buoy","farm")}function ct(e=Z()){return K.join(e,"phone")}function hi(e=Z()){return K.join(ct(e),"phones.json")}function mi(e){const t=e;const s=(i,o,n)=>typeof i==="number"&&Number.isInteger(i)&&i>=o&&i<=n;return!!t&&typeof t==="object"&&typeof t.name==="string"&&t.name.length>0&&t.name.length<=100&&typeof t.productType==="string"&&s(t.ramMB,256,1<<20)&&s(t.cpus,1,256)&&s(t.limitMB,64,t.ramMB)&&s(t.warnMB,32,t.limitMB-1)}function Oe(e=Z()){const t={...pi};try{const s=JSON.parse(An(hi(e),"utf8"));for(const[i,o]of Object.entries(s?.phones??{})){if(/^[a-z0-9][a-z0-9-]{0,63}$/.test(i)&&mi(o))t[i]=o}}catch{}return t}async function fi(e,t,s=Z()){if(!/^[a-z0-9][a-z0-9-]{0,63}$/.test(e))throw new Error(`Bad phone id ${e}.`);if(!mi(t))throw new Error(`Bad phone numbers for ${e}.`);const i=hi(s);let o={phones:{}};try{o=JSON.parse(await x.readFile(i,"utf8"));if(!o||typeof o.phones!=="object")o={phones:{}}}catch{}o.phones[e]=t;await x.mkdir(K.dirname(i),{recursive:true});const n=`${i}.${process.pid}.tmp`;await x.writeFile(n,JSON.stringify(o,null,2)+"\n");await x.rename(n,i)}function wi(e){return Math.max(3072,e.limitMB+1024)}function gi(e,t,s){const i=t.capMB>e.limitMB?t.capMB:wi(e);return{BUOY_PROFILE_NAME:e.name,BUOY_PROFILE_RAM_MB:String(e.ramMB),BUOY_PROFILE_CPUS:String(e.cpus),BUOY_PROFILE_LIMIT_MB:String(e.limitMB),BUOY_PROFILE_WARN_MB:String(e.warnMB),BUOY_PROFILE_ON_LIMIT:t.onLimit,BUOY_PROFILE_CAP_MB:String(i),BUOY_PROFILE_EVENTS:s}}function Kt(...e){const t=new Set;const s=[];for(const i of e){for(const o of(i??"").split(":")){const n=o.trim();if(n&&!t.has(n)){t.add(n);s.push(n)}}}return s.join(":")}function Ke(e,t=Z()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return K.join(ct(t),`${e}.launch.json`)}function lt(e,t=Z()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return K.join(ct(t),`${e}.events.jsonl`)}async function yi(e,t=Z()){const s=Ke(e.udid,t);await x.mkdir(K.dirname(s),{recursive:true});const i=`${s}.${process.pid}.tmp`;await x.writeFile(i,JSON.stringify(e,null,2)+"\n");await x.rename(i,s)}async function dt(e,t=Z()){await x.rm(Ke(e,t),{force:true})}async function vi(e,t=Z()){try{const s=JSON.parse(await x.readFile(Ke(e,t),"utf8"));if(s?.udid!==e||typeof s.dylib!=="string"||!s.vars||typeof s.vars!=="object")return null;await x.access(s.dylib);return s}catch{return null}}async function xn(e){try{const{stdout:t}=await rt("xcrun",["simctl","spawn",e,"launchctl","getenv","DYLD_INSERT_LIBRARIES"],{timeout:1e4});return t.trim()}catch{return""}}async function Qt(e,t,s,i=Z(),o=xn){const n=await vi(e,i);if(!n||n.bundleId!==t){return s?{SIMCTL_CHILD_DYLD_INSERT_LIBRARIES:Kt(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await o(e),s)}:{}}const a={};for(const[r,c]of Object.entries(n.vars))a[`SIMCTL_CHILD_${r}`]=c;a.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES=Kt(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await o(e),s,n.dylib);return a}function Si(e){const t=[];for(const s of e.split("\n")){if(!s.trim())continue;try{const i=JSON.parse(s);if(i&&typeof i.event==="string"&&typeof i.pid==="number")t.push(i)}catch{}}return t}function bi(e){let t=-1;for(let n=e.length-1;n>=0;n--)if(e[n].event==="profile_loaded"){t=n;break}if(t<0)return{loaded:false,ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};const s=e[t];const i=e.slice(t).filter(n=>n.pid===s.pid);const o={pid:s.pid,loaded:true,ignored:Array.isArray(s.ignored)?s.ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};for(const n of i){o.peakMB=Math.max(o.peakMB,n.footprintMB??0,typeof n.peakMB==="number"?n.peakMB:0);o.lastFootprintMB=n.footprintMB;if(n.event==="memory_warning")o.memoryWarnings++;if(n.event==="over_limit")o.overLimit=true;if(n.event==="jetsam_kill"){o.overLimit=true;o.ended="limit"}if(n.event==="safety_kill"){o.overLimit=true;o.ended="safety"}}return o}async function pt(e,t=Z()){const s=lt(e,t);try{const i=await x.open(s,"r");try{const{size:o}=await i.stat();const n=Math.min(o,256*1024);const a=Buffer.alloc(n);await i.read(a,0,n,o-n);let r=a.toString("utf8");if(n<o)r=r.slice(r.indexOf("\n")+1);return bi(Si(r))}finally{await i.close()}}catch{return null}}async function Ln(){try{const{stdout:e}=await rt("xcodebuild",["-version"],{timeout:3e4});return e.trim().split("\n").pop().replace(/^Build version\s*/,"").replace(/[^\w.-]/g,"")||"unknown"}catch{return"unknown"}}async function ki(e){const t=K.join(e,"BuoyPhoneProfile.dylib");try{await x.access(t);return t}catch{}const s=await x.mkdtemp(K.join(Yt.tmpdir(),"buoy-phone-profile-"));try{const i=K.join(s,"BuoyPhoneProfile.m");await x.writeFile(i,Pn);const o=K.join(s,"BuoyPhoneProfile.dylib");await rt("xcrun",["--sdk","iphonesimulator","clang","-arch","arm64","-arch","x86_64","-mios-simulator-version-min=13.0","-fobjc-arc","-O2","-Wall","-Werror","-dynamiclib","-framework","Foundation","-install_name","@rpath/BuoyPhoneProfile.dylib",i,"-o",o],{timeout:18e4,maxBuffer:8*1024*1024,env:{...process.env,TMPDIR:s}});await rt("codesign",["-f","-s","-",o],{timeout:6e4});await x.mkdir(e,{recursive:true});const n=`${t}.${process.pid}`;await x.copyFile(o,n);await x.rename(n,t);return t}finally{await x.rm(s,{recursive:true,force:true}).catch(()=>void 0)}}var Mi=class{constructor(e=K.join(Yt.homedir(),".buoy","phone-profile")){this.root=e;this.built=null;this.key="";this.error="";this.failedAt=0}dylib(){if(process.platform!=="darwin"){this.error="Phone profiles need a Mac.";return Promise.resolve(null)}const e=process.env.DEVELOPER_DIR??"";if(this.built&&this.key===e)return this.built;if(this.key===e&&this.failedAt&&Date.now()-this.failedAt<5*6e4)return Promise.resolve(null);if(this.key!==e)this.failedAt=0;this.key=e;this.error="";this.built=Ln().then(t=>ki(K.join(this.root,`${t}-${Nn}`))).catch(t=>{const s=t;this.error=(s.stderr||s.message||"build failed").trim().split("\n").slice(-3).join(" ").slice(0,300);this.built=null;this.failedAt=Date.now();return null});return this.built}lastError(){return this.error}};var Te=1024**3;var Vt="Buoy Farm ";var Jt=".buoy-farm.json";var Un=120;var jn=180;var Xt=9e4;var _i=500;var Fn=10*6e4;var Gn=7*24*36e5;var Ei=200;var Wn=3;var ae=["done","failed","stopped","interrupted"];var $i=["setting-up","working","waiting","passed","failed","needs-help"];var Hn={now:()=>performance.now()};var qn=10*6e4;function re(e){return Math.round((e.phone.ramMB/1024+_n)*10)/10}var Ii=class{constructor(){this.tails=new Map}run(e,t){const s=this.tails.get(e)??Promise.resolve();const i=s.catch(()=>{}).then(t);this.tails.set(e,i);i.finally(()=>{if(this.tails.get(e)===i)this.tails.delete(e)}).catch(()=>{});return i}};var Yn=class{constructor(){this.counts=new Map}get size(){return this.counts.size}has(e){return this.counts.has(e)}add(e){this.counts.set(e,(this.counts.get(e)??0)+1)}delete(e){const t=(this.counts.get(e)??0)-1;if(t>0)this.counts.set(e,t);else this.counts.delete(e)}};var zn=class{constructor(e){this.free=e;this.waiting=[]}async use(e){if(this.free>0)this.free--;else await new Promise(t=>this.waiting.push(t));try{return await e()}finally{const t=this.waiting.shift();if(t)t();else this.free++}}};var Bi=class{constructor(e){this.phoneFailed=new Map;this.simLock=new Ii;this.deadlines=new Map;this.reservations=new Map;this.busy=new Yn;this.ticking=false;this.lastTrimAt=0;this.hardDeadlines=new Map;this.acquiring=new Set;this.budgetLock=new Ii;this.labels=new Map;this.copyMemAt=new Map;this.inflight=new Map;this.workContext=new Mo;this.activeWork=0;this.lockQueues=new Map;this.fileTasks=new Set;this.checkActive=()=>this.store.assertActive();this.shotsInFlight=0;this.pendingBg=0;this.host=new Proxy(e.host,{get:(s,i)=>{const o=Reflect.get(s,i);if(typeof o!=="function")return o;return async(...n)=>{this.store.assertActive();const a=await o.apply(s,n);this.store.assertActive();return a}}});this.store=e.store;this.clock=e.clock??Hn;this.log=e.log??(()=>{});this.pollMs=e.pollMs??1e3;this.readyTimeoutMs=e.readyTimeoutMs??12e4;this.phoneBuilder=e.phoneBuilder??new Mi;this.phoneLoadTimeoutMs=e.phoneLoadTimeoutMs??1e4;this.bootGate=new zn(this.state.settings.maxConcurrentBoots);this.android=e.android??new li({home:this.store.homeDir});const t={loadCounters:(s,i)=>this.host.loadCounters(s,i),simTree:async s=>{const i=this.copies[s];if(!i)return this.host.simTree(s);return(await this.android.treePids(i.pid)).map(o=>({pid:o,ppid:0,start:"",stat:"",comm:"",command:""}))}};this.meter=new Cs(t,()=>{const s=this.state.locks["perf-slot"];const i=s&&this.state.leases[s.leaseId];return i?{leaseId:i.id,udids:i.sims}:null})}async createTemplate(e){return this.work(void 0,()=>this.createTemplateWork(e))}async cloneSim(e,t){return this.work(void 0,()=>this.cloneSimWork(e,t))}async bootSim(e,t){return this.work(t,()=>this.bootSimWork(e,t))}async parkSim(e,t="idle"){return this.work(void 0,()=>this.parkSimWork(e,t))}async deleteSim(e,t){return this.work(t,()=>this.deleteSimWork(e,t))}async rebuildSim(e,t){return this.work(void 0,()=>this.simLock.run(`rebuild:${e}`,()=>this.rebuildSimWork(e,t)))}async fastReset(e,t){return this.work(void 0,()=>this.fastResetWork(e,t))}async claimOne(e,t){return this.work(e.id,()=>this.claimOneWork(e,t))}async action(e,t,s,i,o,n){this.touch(this.liveLease(e,t,n));const a=this.state.locks["perf-slot"];if(i==="screenshot"&&a&&a.leaseId!==e)throw new p("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);return this.work(i==="reset"||i==="rebuild"?void 0:e,()=>this.actionWork(e,t,s,i,o,n))}async work(e,t){this.store.assertActive();if(this.workContext.getStore()?.active)return t();while(this.state.locks["perf-slot"]&&this.state.locks["perf-slot"].leaseId!==e){await T(this.pollMs);this.store.assertActive()}const s={active:true,leaseId:e};this.activeWork++;try{return await this.workContext.run(s,t)}finally{s.active=false;this.activeWork--}}async files(e){this.checkActive();const t=e();this.fileTasks.add(t);try{const s=await t;this.checkActive();return s}finally{this.fileTasks.delete(t)}}fence(){this.store.fence()}async settleFiles(){await Promise.allSettled([...this.fileTasks])}get state(){return this.store.state}async recover(){for(const[o,n]of Object.entries(this.state.locks)){if(!this.state.leases[n.leaseId])delete this.state.locks[o]}const e=this.store.pending.filter(o=>o.op==="copy-start"||o.op==="copy-stop").map(({seq:o,op:n,data:a})=>({seq:o,op:n,data:a}));let t;try{t=await this.host.listDevices()}catch(o){if(Object.keys(this.state.sims).length||Object.keys(this.state.templates).length)this.log(`recover: iOS sims not checked: ${o.message}`);for(const n of this.store.pending)this.store.result(n.seq,n.op,{recovered:true});await this.recoverLeases();await this.recoverAndroid(e);this.store.save();return}const s=new Map(t.map(o=>[o.udid,o]));for(const o of Object.values(this.state.sims)){const n=s.get(o.udid);if(!n){this.store.event({kind:"lost",message:`${o.name} was deleted outside the farm.`,udid:o.udid});if(o.leaseId)this.dropFromLease(o.leaseId,o.udid);await this.files(()=>this.forgetPhone(o.udid)).catch(()=>void 0);delete this.state.sims[o.udid];continue}if(n.state==="Booted"){o.power=o.power==="frozen"?"frozen":"awake";if(o.power!=="frozen"){const a=this.state.templates[o.templateId];const r=await this.health(o.udid,a?Ee(a.profile):[]);o.profile=r.profile;o.lastCheckAt=new Date().toISOString();if(!r.ok||a?.profile==="slim"&&r.profile==="unknown"){o.health="suspect";o.lastError=r.faults.join("; ")||"Slim state is not known."}}}else{o.power=o.power==="parked"?"parked":"cold"}if(["creating","booting","thawing","deleting"].includes(o.power))o.power=n.state==="Booted"?"awake":"cold"}for(const o of Object.values(this.state.templates)){const n=s.get(o.udid);if(!n){o.usable=false;o.note="Its sim was deleted outside the farm."}else if(!$t(o.appPath)){o.note="Its app build is gone. Clones still work; rebuild the template for a new build."}}for(const o of Object.keys(this.state.orphans)){if(!s.has(o))delete this.state.orphans[o]}for(const o of this.store.pending){if(o.op==="clone"){const n=o.data?.name;const a=t.find(r=>r.name===n&&!this.state.sims[r.udid]);if(a){this.state.orphans[a.udid]={udid:a.udid,name:a.name,cloneIntentName:n};this.store.event({kind:"orphan",message:`Found ${a.name} with no record after a crash. Held for review. Use farm delete to remove it.`,udid:a.udid})}}if(o.op!=="rebuild")this.store.result(o.seq,o.op,{recovered:true})}const i=new Set;for(const o of[...this.store.pending]){if(o.op!=="rebuild")continue;const{udid:n,name:a,templateId:r}=o.data;if(Object.values(this.state.orphans).some(c=>c.name===a))continue;i.add(n);this.busy.add(n);this.background(`resume rebuild ${a}`,()=>this.work(void 0,async()=>{try{const c=Object.values(this.state.sims).find(l=>l.name===a&&l.udid!==n);if(!c){if(this.state.sims[n]){const l=this.state.sims[n];if(l.leaseId)this.dropFromLease(l.leaseId,n);l.leaseId=void 0;await this.deleteSim(n)}await this.cloneSim(r,a)}this.store.result(o.seq,"rebuild",{recovered:true})}finally{this.busy.delete(n)}}))}await this.recoverLeases();await this.recoverAndroid(e);for(const o of Object.values(this.state.sims)){if(o.health==="dirty"&&!o.leaseId&&!i.has(o.udid)){this.busy.add(o.udid);this.background(`resume reset ${o.name}`,async()=>{try{await this.rebuildSim(o.udid)}finally{this.busy.delete(o.udid)}})}}this.store.save()}async recoverLeases(){for(const e of Object.values(this.state.leases)){this.deadlines.set(e.id,this.clock.now()+Xt);const t=Date.parse(e.createdAt)+e.ttlMs-Date.now();this.hardDeadlines.set(e.id,this.clock.now()+Math.max(t,0));if(t<=0)this.background(`expire ${e.id}`,()=>this.endLease(e.id,"ran out of time while the farm was down","full","deadline"))}}async dataPath(e){const t=await this.host.dataPathOf?.(e);if(t)return t;const s=(await this.host.listDevices()).find(i=>i.udid===e);if(!s)throw new p("NOT_FOUND",`No sim ${e} on this Mac.`);return s.dataPath}async isOwned(e){const t=this.state.sims[e]??Object.values(this.state.templates).find(i=>i.udid===e);if(!t)return false;let s;try{s=await this.dataPath(e)}catch(i){if(i instanceof p&&i.code==="NOT_FOUND")return false;throw i}try{const i=JSON.parse(await H.readFile(C.join(s,Jt),"utf8"));return i.farmId===this.state.farmId}catch{return false}}async assertOwned(e){if(!await this.isOwned(e)){throw new p("NOT_OWNED",`Sim ${e} is not a farm sim. The farm never changes sims it did not make.`)}}async writeMarker(e,t){const s=await this.dataPath(e);await this.files(async()=>{await H.mkdir(s,{recursive:true});this.checkActive();await H.writeFile(C.join(s,Jt),JSON.stringify({farmId:this.state.farmId,...t})+"\n")})}farmAwakeGB(){const e=this.state.settings;let t=0;for(const s of Object.values(this.state.sims)){if(["awake","booting","frozen","thawing"].includes(s.power))t+=s.profile==="slim"?e.slimSimGB:e.stockSimGB}for(const s of Object.values(this.copies)){const i=this.androidTemplates[s.templateId];t+=i?re(i):4.3}return t}async budget(e={ramGB:0,diskGB:0}){const t=this.state.settings;const s=await this.host.hostStats();let i=0;let o=0;for(const w of this.reservations.values()){i+=w.ramGB;o+=w.diskGB}for(const w of Object.values(this.copies)){const v=this.androidTemplates[w.templateId];if(v&&(w.state==="starting"||Date.now()-Date.parse(w.startedAt)<6e4))i+=re(v)}const n=s.totalMemBytes/Te;const a=Qn(n)*(s.memPressure==="warn"?2:1);const r=s.availableMemBytes/Te-i;const c=n-Kn(n,t.reserveRamGB)-this.farmAwakeGB()-i;const l=s.freeDiskBytes/Te-o-t.reserveDiskGB;const d=Math.max(0,Math.floor(Math.min(r-a,c,l/t.cloneDiskGB*t.slimSimGB)/t.slimSimGB));const h=(w,v)=>({ok:false,code:w,reason:v,stats:s,roomFor:0});if(s.memPressure==="critical")return h("RAM_LOW","This Mac is low on memory. New sims will wait.");if(e.diskGB>0&&l-e.diskGB<0){return h("DISK_LOW",`Need ${t.reserveDiskGB} GB free after this. This Mac has ${Math.floor(s.freeDiskBytes/Te)} GB.`)}if(e.ramGB>0){if(r-e.ramGB<a||c-e.ramGB<0){return h("RAM_LOW",`Not enough free memory for another sim (${Math.max(0,Math.floor(r))} GB free).`)}}const f=this.state.locks["perf-slot"];if(f&&(!this.workContext.getStore()?.active||this.workContext.getStore()?.leaseId!==f.leaseId))return h("HOST_BUSY","A speed test is running. New sims will wait.");return{ok:true,stats:s,roomFor:d,ramRoomGB:Math.max(0,Math.min(r-a,c))}}async withReservation(e,t){const s=we(4).toString("hex");await this.budgetLock.run("budget",async()=>{const i=await this.budget(e);if(!i.ok)throw new p(i.code,i.reason,void 0,true);this.reservations.set(s,e)});try{return await t()}finally{this.reservations.delete(s)}}async health(e,t=[]){const s=[];let i;try{i=await this.host.simTree(e)}catch(r){this.checkActive();return{ok:false,profile:"unknown",faults:[`Could not read sim processes: ${r.message}`],procs:0}}if(i.length===0)return{ok:false,profile:"unknown",faults:["launchd_sim is not running"],procs:0};const o=new Set(i.map(r=>r.comm.split("/").pop()));for(const r of["SpringBoard","backboardd"])if(!o.has(r))s.push(`${r} is not running`);const n=Oi(await this.host.launchdLogTail(e));if(n)s.push(`${n} keeps crashing`);if(s.length===0){const r=await this.host.spawn(e,["launchctl","getenv","OS_ACTIVITY_MODE"],1e4);if(r.code===0&&r.stdout.trim())s.push(`OS_ACTIVITY_MODE=${r.stdout.trim()} is set for the whole sim (it was booted with SIMCTL_CHILD_OS_ACTIVITY_MODE)`)}if(s.length===0){const r=C.join(this.store.shotsPath,`${e}.png`);await this.files(()=>H.mkdir(C.dirname(r),{recursive:true}));if(!await this.host.screenshot(e,r,1e4))s.push("screenshot did not come back")}let a=i.length<Un?"slim":i.length>jn?"stock":"unknown";if(s.length===0&&t.length){const r=await this.host.spawn(e,["launchctl","print-disabled","system"],2e4);if(r.code===0&&!r.timedOut){const c=new Set([...r.stdout.matchAll(/"([^"]+)" => (?:disabled|true)/g)].map(d=>d[1]));const l=t.filter(d=>!c.has(d)).length;a=l===0?"slim":l===t.length?"stock":"unknown"}else{a="unknown"}}return{ok:s.length===0,profile:a,faults:s,procs:i.length}}async waitHealthy(e,t=[]){const s=performance.now()+this.readyTimeoutMs;let i=await this.health(e,t);while(!i.ok&&performance.now()<s){await T(this.pollMs);i=await this.health(e,t)}return i}async createTemplateWork(e){if(!["slim","stock"].includes(e.profile)||!Number.isInteger(e.metroPort)||e.metroPort<1||e.metroPort>65535){throw new p("BAD_REQUEST","Use slim or stock and a port from 1 to 65535.")}const t=e.seedDir==null?null:Le(e.seedDir);if(t)throw new p("NO_SEED",t,"Run: pnpm e2e:pool seed");if(!$t(C.join(e.appPath,"Info.plist"))){throw new p("BAD_REQUEST",`No app build at ${e.appPath}.`,"Build the app first.")}const s=await Po(e.appPath);const i=`t-${we(3).toString("hex")}`;const o=e.name??`${Vt}Template ${i.slice(2)}`;return this.withReservation({ramGB:this.state.settings.stockSimGB,diskGB:4},async()=>{const n=this.store.intent("create-template",{id:i,name:o});const a=await this.host.create(o,e.deviceType,e.runtime);const r={id:i,udid:a,name:o,bundleId:s,appPath:e.appPath,appHash:await Ro(e.appPath),seedDir:e.seedDir??null,seedHash:e.seedDir?await Ao(e.seedDir):null,launchUrl:e.launchUrl,deviceType:e.deviceType,runtime:e.runtime,xcodeBuild:await this.host.xcodeBuild(),profile:e.profile,profileHash:_s(e.profile),metroPort:e.metroPort,createdAt:new Date().toISOString(),usable:false,note:"Being built."};this.state.templates[i]=r;await this.writeMarker(a,{template:i});this.store.result(n,"create-template",{udid:a});try{await this.buildTemplate(r,e.warmLaunch!==false);r.usable=true;r.note=void 0;this.store.event({kind:"template",message:`Template ${o} is ready.`,udid:a})}catch(c){r.note=`Build failed: ${c.message}`;this.store.event({kind:"template-failed",message:r.note,udid:a});await this.host.shutdown(a).catch(()=>{});throw c}finally{this.store.save()}return r})}async buildTemplate(e,t){const s=Ee(e.profile);if(s.length)await this.files(()=>Bs(e.udid,s,this.host.disabledRoot));await this.host.boot(e.udid);const i=await this.waitHealthy(e.udid,s);if(!i.ok)throw new Error(`template did not come up: ${i.faults.join("; ")}`);if(e.profile==="slim"&&i.profile!=="slim")throw new Error(`slim did not apply (${i.procs} processes)`);await this.host.install(e.udid,e.appPath);const o=await this.host.appContainer(e.udid,e.bundleId);if(!o)throw new Error("app installed but has no data container");if(e.seedDir)await this.files(()=>Rs(o,e.bundleId,e.seedDir,this.checkActive));else await this.files(()=>ot(o,this.checkActive));await this.setPrefs(e.udid,e);if(t){await this.launchApp(e.udid,e,{phone:false});await T(Math.min(15e3,this.pollMs*15));await this.host.terminate(e.udid,e.bundleId)}await this.host.shutdown(e.udid);await this.files(()=>ot(o,this.checkActive))}async launchApp(e,t,s={}){if(s.phone===false){await this.host.launch(e,t.bundleId)}else{await this.launchAsPhone(e,t.bundleId)}if(t.launchUrl)await this.host.openUrl(e,t.launchUrl)}async launchAsPhone(e,t){const s=this.store.homeDir;const i=this.state.settings;const o=b=>{const E=this.state.sims[e];if(E)E.phone=b};const n=async b=>{const E=await this.host.spawn(b,["launchctl","getenv","DYLD_INSERT_LIBRARIES"],1e4).catch(()=>null);return E&&E.code===0?E.stdout.trim():""};const a=async b=>{await this.files(()=>dt(e,s));const E=await Qt(e,t,void 0,s,n);await this.host.launch(e,t,E);o(b)};if(i.phoneProfile===zt)return a({status:"off"});const r=Oe(s);const c=r[i.phoneProfile]?i.phoneProfile:ui;const l=r[c];const d=c!==i.phoneProfile?`No phone "${i.phoneProfile}" on this Mac, so it uses ${l.name}. `:"";const h=this.phoneFailed.get(t);if(h!==void 0&&performance.now()-h<qn){return a({status:"fallback",phoneId:c,phoneName:l.name,note:`${d}This app did not start with the phone profile a moment ago, so it runs as a plain simulator app. The farm tries again in a few minutes.`})}const f=await this.phoneBuilder.dylib();if(!f){const b=this.phoneBuilder.lastError()||"unknown error";this.log(`phone profile: could not build the library: ${b}`);return a({status:"fallback",phoneId:c,phoneName:l.name,note:`${d}The phone profile could not be built (${b}), so the app runs as a plain simulator app.`})}const w=lt(e,s);await this.files(async()=>{await H.mkdir(C.dirname(w),{recursive:true});await H.writeFile(w,"");await yi({udid:e,bundleId:t,dylib:f,phoneId:c,vars:gi(l,{phone:c,onLimit:i.phoneOnLimit,capMB:i.phoneCapMB},w),writtenAt:new Date().toISOString()},s)});const v=await Qt(e,t,void 0,s,n);await this.host.launch(e,t,v);const k=performance.now()+this.phoneLoadTimeoutMs;let y=null;for(;;){y=await pt(e,s);if(y?.loaded||performance.now()>=k)break;await T(Math.min(250,Math.max(10,this.pollMs/4)))}if(y?.loaded){this.phoneFailed.delete(t);const b=y.ignored.length?` Ignored bad settings: ${y.ignored.join(", ")}.`:"";o({status:"on",phoneId:c,phoneName:l.name,pid:y.pid,note:(d+b).trim()||void 0});return}this.log(`phone profile: ${t} on ${e} did not report it; launching without it`);this.phoneFailed.set(t,performance.now());await this.host.terminate(e,t);await a({status:"fallback",phoneId:c,phoneName:l.name,note:`${d}The app did not start with the phone profile, so it runs as a plain simulator app.`})}async setPrefs(e,t){const s=["defaults","write","com.apple.keyboard.preferences"];await this.host.spawn(e,[...s,"AutomaticMinimizationEnabled","-bool","true"]);await this.host.spawn(e,[...s,"DidShowContinuousPathIntroduction","-bool","true"]);await this.host.spawn(e,["defaults","write",t.bundleId,"RCT_jsLocation",`localhost:${t.metroPort}`])}async nextName(){const e=await this.host.listDevices();const t=new Set([...e.map(i=>i.name),...Object.values(this.state.sims).map(i=>i.name)]);let s=this.state.nextIndex;while(t.has(`${Vt}${String(s).padStart(2,"0")}`))s++;this.state.nextIndex=s+1;return`${Vt}${String(s).padStart(2,"0")}`}template(e){const t=this.state.templates[e];if(!t)throw new p("NO_TEMPLATE",`No template ${e}.`,"Run sim_farm_setup to make one.");if(!t.usable)throw new p("NO_TEMPLATE",`Template ${t.name} can't be used: ${t.note??"not ready"}.`);return t}async cloneSimWork(e,t,s){t??=await this.nextName();const i=t;const o=this.template(e);return this.withReservation({ramGB:0,diskGB:this.state.settings.cloneDiskGB},()=>this.simLock.run(`template:${o.id}`,async()=>{await this.assertOwned(o.udid);const n=this.store.intent("clone",{templateId:e,name:i});const a=await this.host.clone(o.udid,i);const r={udid:a,name:i,templateId:e,power:"creating",profile:"unknown",health:"ok",bootEpoch:0,failures:0,createdAt:new Date().toISOString()};this.state.sims[a]=r;this.store.result(n,"clone",{udid:a});await this.writeMarker(a,{template:o.id});const c=await it(await this.dataPath(a),o.bundleId);if(c)await this.files(()=>ot(c,this.checkActive));const l=s?this.state.leases[s.leaseId]:void 0;if(l&&!this.leaseOverdue(l)){r.leaseId=l.id;l.sims=l.sims.map(d=>d===s.udid?a:d)}r.power="cold";this.store.event({kind:"clone",message:`Made ${t}.`,udid:a});this.store.save();return r}))}async bootSimWork(e,t){await this.assertOwned(e);const s=this.sim(e);if(s.power==="frozen"||s.power==="thawing"){throw new p("BAD_REQUEST",`${s.name} is frozen. Thaw it first.`)}const i=this.state.templates[s.templateId];const o=this.state.settings;const n={ramGB:i?.profile==="slim"?o.slimSimGB:o.stockSimGB,diskGB:0};return this.withReservation(n,()=>this.bootGate.use(()=>this.simLock.run(e,async()=>{this.assertLeaseState(s,t);if(s.power==="awake")return s;const a=i?Ee(i.profile):[];if(a.length&&!await To(e,a,this.host.disabledRoot)){await this.files(()=>Bs(e,a,this.host.disabledRoot))}s.power="booting";s.bootEpoch++;this.store.save();await this.host.boot(e);const r=await this.waitHealthy(e,i?.profile==="slim"?a:[]);s.lastCheckAt=new Date().toISOString();s.profile=r.profile;s.power="awake";if(!r.ok){s.failures++;s.health=s.failures>=2?"broken":"suspect";s.lastError=r.faults.join("; ");this.store.event({kind:"unhealthy",message:`${s.name}: ${s.lastError}`,udid:e});this.store.save();throw new p("HOST_ERROR",`${s.name} did not come up: ${s.lastError}`,"The farm will rebuild it.",true)}s.failures=0;if(s.health==="suspect")s.health="ok";s.idleSince=new Date().toISOString();if(i?.profile==="slim"&&r.profile==="unknown"){s.health="suspect";s.lastError="can't tell if slim applied";this.store.event({kind:"unknown-profile",message:`${s.name}: can't tell if slim worked. Held for now.`,udid:e});this.store.save();throw new p("HOST_ERROR",`${s.name}: can't tell if slim applied.`,"The farm will rebuild it.",true)}if(i?.profile==="slim"&&r.profile==="stock"){this.store.event({kind:"stock",message:`${s.name}: slim didn't work here (${r.profile}). It uses full RAM.`,udid:e});const c=await this.budget({ramGB:o.stockSimGB-o.slimSimGB,diskGB:0});if(!c.ok){s.health="suspect";s.lastError="slim did not apply and there is no RAM for a full sim";await this.host.shutdown(e).catch(()=>{});s.power="parked";this.store.save();throw new p("RAM_LOW",`${s.name}: slim didn't apply and there's no room for a full sim.`,void 0,true)}}this.store.save();return s})))}assertLeaseState(e,t){if(!this.state.sims[e.udid])throw new p("NOT_FOUND",`${e.name} is gone.`);if(t){const s=this.state.leases[t];if(e.leaseId!==t||!s||this.leaseOverdue(s)){throw new p("LEASE_EXPIRED",`${e.name} is no longer in lease ${t}.`)}}else if(e.leaseId){throw new p("BAD_REQUEST",`${e.name} is in use. End its lease first.`)}}async parkSimWork(e,t="idle"){await this.assertOwned(e);const s=this.sim(e);await this.simLock.run(e,async()=>{this.assertLeaseState(s);if(this.busy.has(e))throw new p("BAD_REQUEST",`${s.name} is being reset.`);if(s.power==="frozen"||s.power==="thawing")throw new p("BAD_REQUEST",`${s.name} is frozen. Wake it before parking.`);if(s.power!=="awake")return;this.busy.add(e);try{await this.host.shutdown(e);s.power="parked"}finally{this.busy.delete(e)}this.store.event({kind:"park",message:`Parked ${s.name} (${t}).`,udid:e});this.store.save()})}sim(e){const t=this.state.sims[e];if(!t)throw new p("NOT_OWNED",`Sim ${e} is not a farm sim.`);return t}async deleteSimWork(e,t){const s=this.state.orphans[e];if(s){await this.simLock.run(e,async()=>{const o=(await this.host.listDevices()).find(r=>r.udid===e);if(!o)throw new p("NOT_FOUND","This sim is gone.");let n=false;try{const r=JSON.parse(await H.readFile(C.join(o.dataPath,Jt),"utf8"));n=r.farmId===this.state.farmId}catch(r){n=r.code==="ENOENT"&&o.name===s.cloneIntentName}if(!n)throw new p("NOT_OWNED","This sim has no valid farm mark.");const a=this.store.intent("delete",{udid:e});await this.host.shutdown(e);await this.host.delete(e);await this.files(()=>H.rm(C.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`),{recursive:true,force:true}));await this.files(()=>this.forgetPhone(e));delete this.state.orphans[e];this.store.result(a,"delete",{})});return}await this.assertOwned(e);const i=this.sim(e);await this.simLock.run(e,async()=>{this.assertLeaseState(i,t);if(i.power==="frozen"||i.power==="thawing")throw new p("BAD_REQUEST",`${i.name} is frozen. Wake it first.`);i.power="deleting";const o=this.store.intent("delete",{udid:e});await this.host.shutdown(e).catch(()=>{});await this.host.delete(e);await this.files(()=>H.rm(C.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`),{recursive:true,force:true}));await this.files(()=>this.forgetPhone(e));delete this.state.sims[e];this.store.result(o,"delete",{});this.store.event({kind:"delete",message:`Deleted ${i.name}.`,udid:e})})}async rebuildSimWork(e,t){const s=this.sim(e);const{name:i,templateId:o}=s;await this.assertOwned(e);await this.simLock.run(e,async()=>{this.assertLeaseState(s,t);this.busy.add(e)});const n=this.store.intent("rebuild",{udid:e,name:i,templateId:o});try{await this.deleteSim(e,t);const a=await this.cloneSimWork(o,i,t?{udid:e,leaseId:t}:void 0);this.store.result(n,"rebuild",{udid:a.udid});return a}finally{this.busy.delete(e)}}async fastResetWork(e,t){await this.assertOwned(e);const s=this.sim(e);const i=this.template(s.templateId);await this.simLock.run(e,async()=>{this.assertLeaseState(s,t);if(s.power==="frozen"||s.power==="thawing")throw new p("BAD_REQUEST",`${s.name} is frozen. Wake it first.`);this.busy.add(e);try{if(s.power==="awake")await this.host.terminate(e,i.bundleId);const o=await it(await this.dataPath(e),i.bundleId);if(!o)throw new Error("app container not found");if(i.seedDir)await this.files(()=>Rs(o,i.bundleId,i.seedDir,this.checkActive));else await this.files(()=>ot(o,this.checkActive));s.health="ok";this.store.save()}finally{this.busy.delete(e)}})}async grow(e,t){if(!Number.isInteger(t)||t<1||t>64)throw new p("BAD_REQUEST","count must be 1 to 64.");const s=[];for(let i=0;i<t;i++)s.push(await this.cloneSim(e));return s}async shrink(e,t){if(!Number.isInteger(t)||t<1||t>64)throw new p("BAD_REQUEST","count must be 1 to 64.");const s=["cold","parked","awake"];const i=Object.values(this.state.sims).filter(n=>n.templateId===e&&!n.leaseId&&s.includes(n.power)&&!this.busy.has(n.udid)).sort((n,a)=>s.indexOf(n.power)-s.indexOf(a.power));const o=[];for(const n of i.slice(0,t)){try{await this.deleteSim(n.udid);o.push(n.name)}catch(a){if(!(a instanceof p))throw a}}return o}async removeTemplate(e){if(this.androidTemplates[e])return this.work(void 0,()=>this.removeAndroidTemplate(e));return this.work(void 0,async()=>{const t=this.state.templates[e];if(!t)throw new p("NO_TEMPLATE",`No template ${e}.`);const s=Object.values(this.state.sims).filter(a=>a.templateId===e);const i=s.filter(a=>a.leaseId||this.busy.has(a.udid));if(i.length){throw new p("HOST_BUSY",`${i.map(a=>a.name).join(", ")} ${i.length===1?"is":"are"} in use.`,"End those uses first.",true)}const o=[];for(const a of s){await this.deleteSim(a.udid);o.push(a.name)}const n=(await this.host.listDevices()).some(a=>a.udid===t.udid);if(n){await this.assertOwned(t.udid);await this.simLock.run(t.udid,async()=>{const a=this.store.intent("delete",{udid:t.udid});await this.host.shutdown(t.udid).catch(()=>{});await this.host.delete(t.udid);await this.files(()=>H.rm(C.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${t.udid}`),{recursive:true,force:true}));this.store.result(a,"delete",{})})}delete this.state.templates[e];o.push(t.name);this.store.event({kind:"delete",message:`Removed template ${t.name} and its ${s.length} sim(s).`});return{removed:o}})}leaseOverdue(e){const t=this.clock.now();if(t>(this.hardDeadlines.get(e.id)??Infinity))return true;if(e.owner.kind==="person")return false;return!this.acquiring.has(e.id)&&t>(this.deadlines.get(e.id)??0)}leaseDeadline(e){return Math.min(this.deadlines.get(e.id)??0,this.hardDeadlines.get(e.id)??Infinity)}async acquire(e){if(e.ttlMs!==void 0&&(typeof e.ttlMs!=="number"||!Number.isFinite(e.ttlMs)||e.ttlMs<=0)){throw new p("BAD_REQUEST","ttlMs must be a finite number above zero.")}const t=this.androidTemplates[e.templateId];if(t&&this.android.stale(t)){throw new p("NO_TEMPLATE",`Android template ${t.id} can't start copies: ${this.android.stale(t)}`,`Make a new one: farm android template --apk ${t.apk}`)}const s=t?void 0:this.template(e.templateId);const i=t??s;if(!Number.isInteger(e.count)||e.count<1||e.count>64)throw new p("BAD_REQUEST","count must be 1 to 64.");const o=e.owner.kind==="person"?this.state.settings.personLeaseHours*36e5:24*36e5;const n=Math.min(Math.max(e.ttlMs??15*6e4,3e4),o);const a=e.runId?this.ownRun(e.runId,e.owner.clientId):this.newRun(e,i.id,n);const r=Date.parse(a.deadlineAt)-Date.now();if(r<3e4)throw new p("RUN_ENDED","This run is almost out of time.","Ask for more with sim_farm_extend, or finish it and start a new run.");const c={id:`l-${we(4).toString("hex")}`,epoch:1,owner:e.owner,purpose:e.purpose,runId:a.id,templateId:i.id,sims:[],createdAt:new Date().toISOString(),ttlMs:Math.min(n,r)};a.leases.push(c.id);a.lastActivityAt=c.createdAt;if(e.labels?.length)this.labels.set(c.id,e.labels);this.state.leases[c.id]=c;this.hardDeadlines.set(c.id,this.clock.now()+c.ttlMs);this.acquiring.add(c.id);const l=()=>this.deadlines.set(c.id,this.clock.now()+Math.min(c.ttlMs,Xt));l();const d=performance.now()+(e.waitMs??0);const h=()=>{if(!this.state.leases[c.id])throw new p("LEASE_EXPIRED","The lease ended while sims were being prepared.");if(this.leaseOverdue(c))throw new p("LEASE_EXPIRED","The lease ran out of time while sims were being prepared.")};try{while(c.sims.length<e.count){h();l();const f=t?await this.claimCopy(c,t):await this.claimOne(c,s);h();if(f)continue;if(performance.now()>=d)break;if(!c.sims.length&&a.state==="active"&&!Object.keys(a.lanes).length)a.state="waiting";await T(Math.max(this.pollMs,2e3))}if(c.sims.length<e.count&&!e.allowPartial){const f=await this.budget({ramGB:t?re(t):this.state.settings.slimSimGB,diskGB:0});throw new p(f.code??"HOST_BUSY",`Got ${c.sims.length} of ${e.count} ${t?"copies":"sims"}. ${f.reason??"Others are in use."}`,"Try again later or ask for fewer.",true)}if(c.sims.length===0)throw new p("HOST_BUSY","No sim is free right now.","Try again later.",true);if(e.launch)await this.work(c.id,async()=>{h();if(t)await Promise.all(c.sims.map(f=>this.launchCopy(f)));else await Promise.all(c.sims.map(f=>this.launchApp(f,s)));l();if(t)await this.checkCopyIdentities(c,6e4);else await this.checkIdentities(c,6e4);h()});l();this.store.event({kind:"lease",message:`${e.owner.label} took ${c.sims.length} sim(s) for ${a.goal}.`,leaseId:c.id,runId:a.id});this.note(a,"farm",`Got ${c.sims.length} sim${c.sims.length===1?"":"s"}.`);this.store.save();return{lease:c,run:a,sims:c.sims.map(f=>this.state.sims[f]??this.copyView(this.copies[f]))}}catch(f){if(this.state.leases[c.id])this.note(a,"farm",`Could not get sims: ${f.message}`);await this.endLease(c.id,"acquire failed","fast","acquire-failed").catch(()=>{});throw f}finally{this.acquiring.delete(c.id);this.labels.delete(c.id)}}async claimOneWork(e,t){const s=Object.values(this.state.sims).filter(a=>a.templateId===t.id&&!a.leaseId&&a.health==="ok"&&!this.busy.has(a.udid));const i=s.find(a=>a.power==="awake")??s.find(a=>a.power==="parked"||a.power==="cold");const o=async a=>{const r=await this.isOwned(a.udid);return this.simLock.run(a.udid,async()=>{if(a.leaseId||this.busy.has(a.udid)||a.health!=="ok"||!this.state.sims[a.udid])return null;if(!r){a.health="broken";a.lastError="farm marker missing";this.store.save();return null}if(!this.state.leases[e.id]||this.leaseOverdue(e))return null;a.leaseId=e.id;e.sims.push(a.udid);a.idleSince=void 0;this.addLane(e,a);this.store.save();return a})};if(i?.power==="awake")return o(i);let n=null;try{if(i){n=await o(i)}else{const a=await this.budget({ramGB:t.profile==="slim"?this.state.settings.slimSimGB:this.state.settings.stockSimGB,diskGB:this.state.settings.cloneDiskGB});if(!a.ok)return null;n=await o(await this.cloneSim(t.id))}if(!n)return null;await this.bootSim(n.udid,e.id);return n}catch(a){if(n)this.unclaim(e,n.udid);if(a instanceof p&&(a.code==="RAM_LOW"||a.code==="DISK_LOW"||a.code==="HOST_BUSY"||a.code==="LEASE_EXPIRED")){return null}if(n&&this.state.sims[n.udid]?.health!=="ok"){const r=n.udid;this.background(`rebuild ${r}`,()=>this.rebuildSim(r))}throw a}}unclaim(e,t){e.sims=e.sims.filter(o=>o!==t);const s=e.runId?this.state.runs[e.runId]:void 0;if(s?.lanes[t])delete s.lanes[t];const i=this.state.sims[t];if(i&&i.leaseId===e.id)i.leaseId=void 0;this.store.save()}async checkIdentities(e,t=6e4){const s=this.template(e.templateId);const i=performance.now()+t;const o=new Map;for(;;){for(const a of e.sims){if(o.has(a))continue;const r=await it(await this.dataPath(a),s.bundleId);const c=r?await Ps(r):null;if(!c)continue;const l=[...o.entries()].find(([,h])=>h===c);if(l)throw new p("ID_CLASH",`${this.state.sims[a].name} and ${this.state.sims[l[0]].name} share one Buoy ID.`);const d=Object.values(this.state.sims).find(h=>h.udid!==a&&!e.sims.includes(h.udid)&&h.buoyDeviceId===c);if(d)throw new p("ID_CLASH",`${this.state.sims[a].name} and ${d.name} share one Buoy ID.`);o.set(a,c);this.state.sims[a].buoyDeviceId=c}if(o.size===e.sims.length||performance.now()>=i)break;await T(Math.max(this.pollMs,1e3))}const n=e.sims.filter(a=>!o.has(a));if(n.length){this.store.event({kind:"no-id",message:`No Buoy ID yet on ${n.map(a=>this.state.sims[a].name).join(", ")}. Is the app loading? Check Metro.`,leaseId:e.id})}}liveLease(e,t,s){const i=this.state.leases[e];if(!i){const o=this.runOfLease(e);if(o&&(o.endedBy==="user"||o.endedBy==="deadline"))this.assertRunOpen(o);throw new p("LEASE_EXPIRED",`Lease ${e} has ended.`,"Acquire new sims.")}if(i.epoch!==t)throw new p("STALE_EPOCH",`Lease ${e} is at epoch ${i.epoch}, not ${t}.`);if(s&&i.owner.clientId!==s)throw new p("NOT_OWNED",`Lease ${e} belongs to ${i.owner.label}.`);if(this.leaseOverdue(i)){const o=this.overdueEnd(i);this.background(`expire ${i.id}`,()=>this.endLease(i.id,"expired","full",o));if(o==="deadline")throw new p("RUN_ENDED","This run's time expired.","Ask for more time with sim_farm_extend before the end next time. Start a new run to go on.");throw new p("LEASE_EXPIRED",`Lease ${e} has expired.`,"Acquire new sims.")}return i}renew(e,t,s){const i=this.liveLease(e,t,s);const o=Math.min(this.clock.now()+Math.min(i.ttlMs,Xt),this.hardDeadlines.get(i.id)??Infinity);this.deadlines.set(i.id,o);return{expiresInMs:Math.round(o-this.clock.now())}}async release(e,t,s="fast",i,o=false){if(s!=="fast"&&s!=="full")throw new p("BAD_REQUEST","Use fast or full reset.");this.liveLease(e,t,i);return this.endLease(e,"released",s,o?null:"released")}async endLease(e,t,s,i="released"){const o=this.state.leases[e];if(!o)return{};o.epoch++;delete this.state.leases[e];this.deadlines.delete(e);this.hardDeadlines.delete(e);for(const[c,l]of Object.entries(this.state.locks))if(l.leaseId===e)delete this.state.locks[c];const n=new Set(o.sims.filter(c=>{const l=this.state.sims[c];return l?.bootEpoch===0&&l.power==="cold"&&l.health==="ok"}));const a={};const r=o.sims.filter(c=>this.copies[c]?.leaseId===e);for(const c of r){const l=this.copies[c];l.leaseId=void 0;l.state="stopping";a[l.name]="closing"}if(r.length)this.background(`close copies of ${e}`,()=>this.work(void 0,async()=>{for(const c of r)await this.stopCopy(c,`${o.owner.label}'s lease ended`)}));for(const c of o.sims){const l=this.state.sims[c];if(!l||l.leaseId!==e)continue;l.leaseId=void 0;l.health="dirty";l.idleSince=new Date().toISOString();this.busy.add(c);a[l.name]="resetting";void this.files(()=>dt(c,this.store.homeDir)).catch(()=>void 0)}this.store.event({kind:"lease-end",message:`${o.owner.label}'s ${o.purpose} ended (${t}).`,leaseId:e,runId:o.runId});this.settleRun(o,i);this.store.save();this.background(`reset after ${e}`,()=>this.work(void 0,async()=>{for(const c of o.sims){if(!this.state.sims[c])continue;try{const l=Date.parse(o.createdAt)-2e3;for(const d of await this.host.clientsOf(c)){if(Date.parse(d.start)>=l)await this.host.kill(d.pid,d.start)}if(s==="full"&&!n.has(c)||this.state.sims[c].failures>0)await this.rebuildSim(c);else await this.fastReset(c)}catch(l){this.store.event({kind:"reset-failed",message:`${this.state.sims[c]?.name??c}: ${l.message}. Rebuilding.`,udid:c});await this.rebuildSim(c).catch(()=>{})}finally{this.busy.delete(c)}}}));return a}dropFromLease(e,t){const s=this.state.leases[e];if(s)s.sims=s.sims.filter(i=>i!==t)}async actionWork(e,t,s,i,o,n){const a=this.liveLease(e,t,n);const r={};for(const c of s){if(!a.sims.includes(c))throw new p("NOT_OWNED",`${c} is not in lease ${e}.`);if(this.copies[c]){r[c]=await this.copyAction(a,c,i,o,()=>this.liveLease(e,t,n));continue}if(i==="install")throw new p("BAD_REQUEST","install is for Android copies. Rebuild the iOS template for a new app build.");await this.assertOwned(c);this.liveLease(e,t,n);const l=this.template(a.templateId);switch(i){case"screenshot":{const d=this.state.locks["perf-slot"];if(d&&d.leaseId!==a.id)throw new p("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);const h=C.join(this.store.shotsPath,`${c}.png`);r[c]=await this.host.screenshot(c,h)?h:null;break}case"open-url":if(!o)throw new p("BAD_REQUEST","open-url needs a url.");await this.host.openUrl(c,o);r[c]="opened";break;case"launch":await this.launchApp(c,l);r[c]="launched";break;case"reset":await this.fastReset(c,a.id);r[c]="reset";break;case"rebuild":{const d=await this.rebuildSim(c,a.id);if(!this.state.leases[a.id])throw new p("LEASE_EXPIRED",`Lease ${e} ended during the rebuild.`);d.leaseId=a.id;a.sims=a.sims.map(w=>w===c?d.udid:w);const h=a.runId?this.state.runs[a.runId]:void 0;const f=h?.lanes[c];if(h&&f){delete h.lanes[c];h.lanes[d.udid]={...f,udid:d.udid,name:d.name,state:"setting-up",note:"Rebuilt"}}await this.bootSim(d.udid,a.id);r[c]=d.udid;break}default:throw new p("BAD_REQUEST",`Unknown action ${i}.`)}}this.store.save();return r}async lock(e,t,s,i=0,o){if(typeof e!=="string"||!/^[a-z0-9-]{1,40}$/.test(e))throw new p("BAD_REQUEST","Lock names are short lowercase words.");this.touch(this.liveLease(t,s,o));if(this.state.locks[e]?.leaseId===t)return{held:true};const n=this.lockQueues.get(e)??[];this.lockQueues.set(e,n);const a={};n.push(a);const r=performance.now()+i;try{for(;;){this.store.assertActive();this.liveLease(t,s,o);const c=this.state.locks[e];const l=e==="perf-slot"&&(this.activeWork>0||this.busy.size>0||this.pendingBg>0);if(n[0]===a&&!c&&!l){this.state.locks[e]={name:e,leaseId:t,since:new Date().toISOString()};this.store.save();return{held:true}}if(performance.now()>=r){return{held:false,holder:c?this.state.leases[c.leaseId]?.owner.label??"someone":"the farm (work is still running)",since:c?.since}}await T(Math.max(1,Math.min(this.pollMs,r-performance.now())))}}finally{n.splice(n.indexOf(a),1);if(!n.length)this.lockQueues.delete(e)}}fairness(e,t,s,i){this.liveLease(e,t,i);const o=this.state.settings;const n=Vn(o,this.meter.cpuCount);const a=this.meter.verdicts(e,s,n);return{rules:n,fair:a.filter(r=>r.fair).length,unfair:a.filter(r=>!r.fair).length,runs:a}}unlock(e,t,s,i){this.liveLease(t,s,i);if(this.state.locks[e]?.leaseId===t)delete this.state.locks[e];this.store.save();return{held:false}}async shot(e){if(this.state.locks["perf-slot"])throw new p("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(this.shotsInFlight>=Wn)throw new p("HOST_BUSY","Taking other pictures. Try again in a moment.",void 0,true);this.shotsInFlight++;try{return await this.work(void 0,()=>this.shotWork(e))}finally{this.shotsInFlight--}}async shotWork(e){if(this.copies[e])return this.copyShot(e);await this.assertOwned(e);const t=this.sim(e);if(this.state.locks["perf-slot"])throw new p("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(t.power!=="awake")throw new p("BAD_REQUEST",`${t.name} is ${t.power}.`);const s=C.join(this.store.shotsPath,`${e}.png`);await this.files(()=>H.mkdir(C.dirname(s),{recursive:true}));if(!await this.host.screenshot(e,s,8e3))throw new p("HOST_ERROR",`No still from ${t.name} in time.`,void 0,true);return{file:s,at:new Date().toISOString()}}async show(e){if(this.copies[e])throw new p("BAD_REQUEST","Android copies run with no window, and can't get one later. Use its pictures in Details.");return this.work(this.state.sims[e]?.leaseId,()=>this.showWork(e))}async showWork(e){await this.assertOwned(e);const t=this.sim(e);if(t.power==="frozen"||t.power==="thawing")throw new p("BAD_REQUEST",`${t.name} is paused. Wake it first.`);if(t.power!=="awake")await this.bootSim(e,t.leaseId);await this.host.showWindow(e);return"shown"}async tick(){if(this.ticking)return;this.ticking=true;try{for(const s of Object.values(this.state.leases)){const i=s.owner.kind!=="person"&&!await J(s.owner.pid,s.owner.pidStart,true);if(i)await this.endLease(s.id,"its owner exited","full","owner-exited");else if(this.leaseOverdue(s))await this.endLease(s.id,"no renewal","full",this.overdueEnd(s))}await this.tickRuns();await this.tickCopies().catch(s=>this.log(`copies: ${s.message}`));await this.tickPhones().catch(s=>this.log(`phones: ${s.message}`));const e=this.state.settings.parkAfterMinutes;if(e>0){for(const s of Object.values(this.state.sims)){if(s.power!=="awake"||s.leaseId||this.busy.has(s.udid)||!s.idleSince)continue;if(Date.now()-Date.parse(s.idleSince)>e*6e4){await this.parkSim(s.udid).catch(i=>this.log(`park ${s.name}: ${i.message}`))}}}await this.trimIdle().catch(s=>this.log(`trim: ${s.message}`));const t=Object.keys(this.state.requests);if(t.length>_i)for(const s of t.slice(0,t.length-_i))delete this.state.requests[s]}finally{this.ticking=false}}async trimIdle(e=Date.now()){const t=this.state.settings.trimIdleHours;if(!(t>0)||e-this.lastTrimAt<Fn)return[];this.lastTrimAt=e;const s=o=>Date.parse(o.idleSince??o.createdAt);const i=[];for(const o of Object.keys(this.state.templates)){const n=Object.values(this.state.sims).filter(d=>d.templateId===o);if(n.some(d=>d.leaseId||this.busy.has(d.udid)))continue;const a=n.every(d=>e-s(d)>Gn);const r=a?0:this.state.settings.keepWarmPerTemplate;const c=n.filter(d=>(d.power==="parked"||d.power==="cold")&&e-s(d)>t*36e5).sort((d,h)=>s(h)-s(d));const l=Math.max(0,n.length-r);for(const d of c.slice(c.length-Math.min(l,c.length))){try{await this.deleteSim(d.udid);i.push(d.name)}catch(h){if(!(h instanceof p))throw h}}}if(i.length)this.store.event({kind:"trim",message:`Deleted ${i.length} idle cop${i.length===1?"y":"ies"}: ${i.join(", ")}.`});return i}async drain(e=10*6e4){const t=performance.now()+e;while((this.busy.size>0||this.pendingBg>0||this.activeWork>0)&&performance.now()<t)await T(200);return this.busy.size===0&&this.pendingBg===0&&this.activeWork===0}idle(){return Object.keys(this.state.leases).length===0&&Object.keys(this.copies).length===0&&this.busy.size===0&&this.activeWork===0&&this.pendingBg===0&&this.acquiring.size===0}async parkAllIdle(){for(const e of Object.values(this.state.sims)){if(e.power==="awake"&&!e.leaseId&&!this.busy.has(e.udid))await this.parkSim(e.udid,"farm stopping").catch(()=>{})}}background(e,t){this.pendingBg++;this.workContext.exit(t).catch(s=>{this.log(`${e}: ${s.message}`);try{this.store.event({kind:"error",message:`${e}: ${s.message}`})}catch{}}).finally(()=>this.pendingBg--)}async once(e,t,s,i){if(!t)return i();const o=`${e}:${t}`;const n=_o("sha256").update(JSON.stringify(s)).digest("hex").slice(0,16);const a=this.state.requests[o];if(a){if(a.hash!==n)throw new p("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return a.result}const r=this.inflight.get(o);if(r){if(r.hash!==n)throw new p("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return r.promise}const c=i();this.inflight.set(o,{hash:n,promise:c});try{const l=await c;this.state.requests[o]={hash:n,result:l,at:new Date().toISOString()};this.store.save();return l}finally{this.inflight.delete(o)}}get copies(){return this.state.androidCopies??={}}get androidTemplates(){return this.state.androidTemplates??={}}copyView(e){const t=e.leaseId?this.state.leases[e.leaseId]:void 0;const s=t?.runId?this.state.runs[t.runId]:void 0;return{udid:e.id,name:e.name,platform:"android",templateId:e.templateId,serial:e.serial,state:e.state,startedAt:e.startedAt,readyMs:e.readyMs,bootedFrom:e.bootedFrom,leaseId:e.leaseId,owner:t?.owner.label,purpose:s?.goal??t?.purpose,runId:s?.id,lane:s?.lanes[e.id]?.label,buoyDeviceId:e.buoyDeviceId,phone:e.phone,lastError:e.lastError}}copyTemplate(e){const t=this.androidTemplates[e.templateId];if(!t)throw new p("NO_TEMPLATE",`${e.name}'s template is gone.`);return t}async claimCopy(e,t){return this.work(e.id,async()=>{try{return await this.withReservation({ramGB:re(t),diskGB:1},()=>this.bootGate.use(()=>this.startCopyWork(t,e)))}catch(s){if(s instanceof p&&(s.code==="RAM_LOW"||s.code==="DISK_LOW"||s.code==="HOST_BUSY"||s.code==="LEASE_EXPIRED"))return null;throw s}})}async startCopyWork(e,t,s={}){if(t&&(!this.state.leases[t.id]||this.leaseOverdue(t)))throw new p("LEASE_EXPIRED","The lease ended while copies were being prepared.");const i=await this.android.freePort(new Set(Object.values(this.copies).map(c=>c.port)));const o=`a-${we(3).toString("hex")}`;this.state.nextCopyIndex=(this.state.nextCopyIndex??0)+1;const n={id:o,name:`Buoy Android ${this.state.nextCopyIndex}`,templateId:e.id,serial:`emulator-${i}`,port:i,pid:0,pidStart:"",log:"",state:"starting",startedAt:new Date().toISOString(),leaseId:t?.id,window:s.window||void 0};this.copies[o]=n;const a=this.store.intent("copy-start",{id:o,port:i,avd:e.avd});const r=performance.now();try{Object.assign(n,await this.android.startCopy(e,i,s))}catch(c){delete this.copies[o];this.store.result(a,"copy-start",{failed:true});throw new p("HOST_ERROR",`Could not start a copy: ${c.message}`)}this.store.result(a,"copy-start",{id:o,pid:n.pid});if(t){t.sims.push(o);this.addLane(t,{udid:o,name:n.name})}this.store.save();try{const c=await this.android.readyCopy(e,n);n.bootedFrom=c.bootedFrom;n.readyMs=Math.round(performance.now()-r);n.state="ready";if(c.bootedFrom==="cold"){this.store.event({kind:"copy-cold",message:`${n.name} booted from scratch: the template's snapshot did not load. Make a new template.`})}}catch(c){n.lastError=c.message;if(t)this.unclaim(t,o);await this.stopCopy(o,"it did not start").catch(()=>{});throw new p("HOST_ERROR",`${n.name} did not start: ${c.message}`)}this.store.save();return n}async stopCopy(e,t){const s=this.copies[e];if(!s)return;s.state="stopping";const i=this.store.intent("copy-stop",{id:e,pid:s.pid,pidStart:s.pidStart,serial:s.serial});try{if(s.pid)await this.android.stopCopy(this.androidTemplates[s.templateId],s)}finally{delete this.copies[e];this.copyMemAt.delete(e);this.store.result(i,"copy-stop",{id:e});this.store.event({kind:"copy-closed",message:`${s.name} closed (${t}).`})}}async launchCopy(e,t){const s=this.copies[e];const i=await this.android.launchApp(this.copyTemplate(s),s.serial,t);s.phone=void 0;this.copyMemAt.delete(e);return i.totalMs}async checkCopyIdentities(e,t=6e4){const s=performance.now()+t;const i=new Map;for(;;){for(const n of e.sims){const a=this.copies[n];if(!a||i.has(n))continue;const r=await this.android.installId(this.copyTemplate(a),a.serial).catch(()=>null);if(!r)continue;const c=[...i.entries()].find(([,l])=>l===r)??Object.values(this.copies).filter(l=>l.id!==n&&!e.sims.includes(l.id)&&l.buoyDeviceId===r).map(l=>[l.id,r])[0];if(c)throw new p("ID_CLASH",`${a.name} and ${this.copies[c[0]]?.name??c[0]} share one Buoy ID.`,"Make a new template: its saved id was not cleared.");i.set(n,r);a.buoyDeviceId=r}if(i.size===e.sims.length||performance.now()>=s)break;await T(Math.max(this.pollMs,1e3))}const o=e.sims.filter(n=>!i.has(n));if(o.length){this.store.event({kind:"no-id",message:`No Buoy ID yet on ${o.map(n=>this.copies[n]?.name??n).join(", ")}. Is the app loading? Check Metro. A release build's id can't be read.`,leaseId:e.id})}}async copyAction(e,t,s,i,o){const n=this.copies[t];if(n.state!=="ready"||!await this.android.alive(n.pid,n.pidStart))throw new p("NOT_FOUND",`${n.name} is not running.`,"Release it and take a new copy.");o();const a=this.copyTemplate(n);switch(s){case"screenshot":{const r=this.state.locks["perf-slot"];if(r&&r.leaseId!==e.id)throw new p("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);const c=C.join(this.store.shotsPath,`${t}.png`);return await this.android.screenshot(a,n.serial,c)?c:null}case"open-url":if(!i)throw new p("BAD_REQUEST","open-url needs a url.");await this.android.openUrl(a,n.serial,i);return"opened";case"launch":{const r=await this.launchCopy(t,i);return r!=null?{launched:true,startMs:r}:"launched"}case"install":if(!i||!i.endsWith(".apk")||!$t(i))throw new p("BAD_REQUEST","install needs the path of an .apk on this Mac.");await this.android.install(a,n.serial,i);return"installed";case"reset":case"rebuild":return this.replaceCopy(e,t);default:throw new p("BAD_REQUEST",`Unknown action ${s}.`)}}async replaceCopy(e,t){const s=this.copies[t];const i=this.copyTemplate(s);await this.stopCopy(t,"reset");e.sims=e.sims.filter(r=>r!==t);const o=e.runId?this.state.runs[e.runId]:void 0;const n=o?.lanes[t];if(o&&n)delete o.lanes[t];const a=await this.withReservation({ramGB:re(i),diskGB:1},()=>this.startCopyWork(i,e));if(o&&n)o.lanes[a.id]={...n,udid:a.id,name:a.name,state:"setting-up",note:"Reset"};this.store.save();return a.id}async copyShot(e){const t=this.copies[e];if(this.state.locks["perf-slot"])throw new p("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(t.state!=="ready")throw new p("BAD_REQUEST",`${t.name} is ${t.state}.`);const s=C.join(this.store.shotsPath,`${e}.png`);await this.files(()=>H.mkdir(C.dirname(s),{recursive:true}));if(!await this.android.screenshot(this.copyTemplate(t),t.serial,s,8e3))throw new p("HOST_ERROR",`No still from ${t.name} in time.`,void 0,true);return{file:s,at:new Date().toISOString()}}async copyBench(e,t,s,i,o){const n=this.liveLease(e,t,o);if(!n.sims.includes(s)||!this.copies[s])throw new p("NOT_OWNED",`${s} is not a copy in lease ${e}.`);this.touch(n);const a=this.copies[s];return this.work(n.id,()=>this.android.bench(this.copyTemplate(a),a,i))}async dropCopies(e,t,s,i){const o=this.liveLease(e,t,i);for(const a of s)if(!o.sims.includes(a)||!this.copies[a])throw new p("NOT_OWNED",`${a} is not a copy in lease ${e}.`);const n=[];await this.work(void 0,async()=>{for(const a of s){n.push(this.copies[a]?.name??a);const r=o.runId?this.state.runs[o.runId]:void 0;if(r?.lanes[a])r.lanes[a].gone=true;this.dropFromLease(o.id,a);await this.stopCopy(a,"closed by its owner")}});if(!this.state.leases[o.id]?.sims.length)await this.endLease(o.id,"its copies were closed","fast","released");this.store.save();return n}async createAndroidTemplate(e){return this.work(void 0,async()=>{const t={phone:{ramMB:4096}};return this.withReservation({ramGB:re(t),diskGB:4},async()=>{this.store.event({kind:"template",message:`Making an Android template from ${C.basename(e.apk)}\u2026`});let s;try{s=await this.android.buildTemplate(e,o=>{this.log(o);this.store.event({kind:"template-progress",message:o})})}catch(o){this.store.event({kind:"template-failed",message:`Android template failed: ${o.message}`});throw new p("HOST_ERROR",`Android template failed: ${o.message}`)}s.usable=false;s.note="Checking that a copy starts from the snapshot.";this.androidTemplates[s.id]=s;this.store.save();let i=null;try{i=await this.startCopyWork(s);s.copyReadyMs=i.readyMs;if(i.bootedFrom==="cold")throw new Error("Copies cold boot instead of loading the snapshot.");s.usable=true;s.note=void 0;this.store.event({kind:"template",message:`Android template ${s.id} is ready: ${s.packageName}, copies start in ${((i.readyMs??0)/1e3).toFixed(1)} s.`})}catch(o){s.note=`Build failed: ${o.message}`;this.store.event({kind:"template-failed",message:s.note});throw new p("HOST_ERROR",s.note)}finally{if(i)await this.stopCopy(i.id,"template check").catch(()=>{});this.store.save()}return s})})}async removeAndroidTemplate(e){const t=this.androidTemplates[e];const s=Object.values(this.copies).filter(i=>i.templateId===e);if(s.length)throw new p("HOST_BUSY",`Template ${e} still has copies running: ${s.map(i=>i.name).join(", ")}.`,"Close them first.");await this.android.removeTemplateFiles(t);delete this.androidTemplates[e];this.store.event({kind:"template-removed",message:`Removed Android template ${e}.`});this.store.save();return{removed:[]}}async recoverAndroid(e){await this.importLegacyAndroid();for(const s of e){if(s.op==="copy-stop"){const i=s.data;await this.android.stopCopy(this.androidTemplates[this.copies[i.id]?.templateId??""],i).catch(()=>{});delete this.copies[i.id]}if(s.op==="copy-start"){const i=s.data;const o=this.copies[i.id];if(o&&!o.pid)delete this.copies[i.id]}}for(const s of Object.values(this.copies)){const i=s.pid&&await this.android.alive(s.pid,s.pidStart);const o=s.leaseId?this.state.leases[s.leaseId]:void 0;if(!i){this.lostCopy(s,"its emulator stopped while the farm was down");continue}if(!o)await this.stopCopy(s.id,"no one held it after a restart").catch(()=>{})}const t=new Set(Object.values(this.copies).map(s=>s.port));for(const s of await this.android.findEmulators().catch(()=>[])){if(t.has(s.port))continue;this.store.event({kind:"orphan-copy",message:`Closed a farm emulator on port ${s.port} that had no record.`});await this.android.stopCopy(Object.values(this.androidTemplates).find(i=>i.avd===s.avd),{serial:`emulator-${s.port}`,pid:s.pid,pidStart:s.pidStart}).catch(()=>{})}}lostCopy(e,t){delete this.copies[e.id];this.copyMemAt.delete(e.id);this.store.event({kind:"lost",message:`${e.name} is gone: ${t}.`,udid:e.id});const s=e.leaseId?this.state.leases[e.leaseId]:void 0;if(!s)return;this.dropFromLease(s.id,e.id);const i=s.runId?this.state.runs[s.runId]:void 0;if(i?.lanes[e.id]){i.lanes[e.id].gone=true;this.note(i,"farm",`${i.lanes[e.id].label??e.name} stopped by itself (${t}).`,e.id)}if(!s.sims.length)this.background(`end ${s.id}`,()=>this.endLease(s.id,"its copies are gone","fast","lost"))}async importLegacyAndroid(){if(this.state.androidImported)return;const e=C.join(ze(this.store.homeDir),"state.json");let t=null;try{t=JSON.parse(ko(e,"utf8"))}catch{}for(const i of Object.values(t?.templates??{}))if(!this.androidTemplates[i.id])this.androidTemplates[i.id]=i;const s=[];for(const i of Object.values(t?.copies??{})){if(!await this.android.alive(i.pid,i.pidStart))continue;this.state.nextCopyIndex=(this.state.nextCopyIndex??0)+1;s.push({id:`a-${we(3).toString("hex")}`,name:`Buoy Android ${this.state.nextCopyIndex}`,templateId:i.templateId,serial:i.serial,port:i.port,pid:i.pid,pidStart:i.pidStart,log:i.log,state:"ready",startedAt:i.startedAt,readyMs:i.readyMs,bootedFrom:i.bootedFrom,window:i.window||void 0})}if(s.length){const i={clientId:"c-moved",kind:"person",pid:0,label:"You (farm android)"};const o=this.state.settings.personLeaseHours*36e5;const n={templateId:s[0].templateId,count:s.length,purpose:"Copies started before this update",goal:"Copies started before this update",owner:i,ttlMs:o};const a=this.newRun(n,s[0].templateId,o);const r={id:`l-${we(4).toString("hex")}`,epoch:1,owner:i,purpose:n.purpose,runId:a.id,templateId:s[0].templateId,sims:[],createdAt:new Date().toISOString(),ttlMs:o};a.leases.push(r.id);this.state.leases[r.id]=r;this.hardDeadlines.set(r.id,this.clock.now()+o);for(const c of s){c.leaseId=r.id;this.copies[c.id]=c;r.sims.push(c.id);this.addLane(r,{udid:c.id,name:c.name})}}this.state.androidImported=true;if(t)this.store.event({kind:"android-moved",message:`Moved ${Object.keys(t.templates??{}).length} Android template(s) and ${s.length} running cop${s.length===1?"y":"ies"} into the farm.`});this.store.save()}async tickCopies(){for(const e of Object.values(this.copies)){if(e.state!=="ready")continue;if(!await this.android.alive(e.pid,e.pidStart)){this.lostCopy(e,"its emulator stopped");this.store.save();continue}const t=e.leaseId?this.state.leases[e.leaseId]:void 0;if(!t||this.state.locks["perf-slot"]||Date.now()-(this.copyMemAt.get(e.id)??0)<5e3)continue;this.copyMemAt.set(e.id,Date.now());const s=this.androidTemplates[e.templateId];if(!s)continue;const i=await this.android.appMemory(s,e.serial).catch(()=>null);if(i?.pid){if(e.phone?.pid!==i.pid)e.phone={...e.phone,pid:i.pid,peakMB:i.pssMB??void 0,overLimit:void 0,killedFor:void 0};else if(i.pssMB&&i.pssMB>(e.phone.peakMB??0))e.phone.peakMB=i.pssMB}else if(e.phone?.pid&&!e.phone.killedFor){const o=await this.android.memoryKill(s,e.serial,e.phone.pid).catch(()=>null);if(o){e.phone.killedFor=o;e.phone.overLimit=true;const n=t.runId?this.state.runs[t.runId]:void 0;if(n&&e.phone.notedPid!==e.phone.pid){e.phone.notedPid=e.phone.pid;this.note(n,"farm",`${n.lanes[e.id]?.label??e.name}: Android closed the app for memory (${o}), like ${s.phone.name} would. Peak ${e.phone.peakMB??"?"} MB.`,e.id)}}}}}newRun(e,t,s){const i=Date.now();const o=e.owner.agent?this.state.settings.runCapMinutes*6e4:24*36e5;const n=(e.goal??"").trim()||e.purpose;const a={id:`r-${we(4).toString("hex")}`,goal:n.slice(0,200),kind:e.kind?.slice(0,40),owner:e.owner,templateId:t,state:"active",createdAt:new Date(i).toISOString(),deadlineAt:new Date(i+Math.min(s,o)).toISOString(),capAt:new Date(i+o).toISOString(),leases:[],lanes:{},notes:[],lastActivityAt:new Date(i).toISOString(),auto:!e.goal};this.state.runs[a.id]=a;return a}touch(e){const t=e.runId?this.state.runs[e.runId]:void 0;if(t)t.lastActivityAt=new Date().toISOString()}runOfLease(e){return Object.values(this.state.runs).find(t=>t.leases.includes(e))}assertRunOpen(e){if(e.state==="stopping"||e.state==="stopped"){if(e.endedBy==="user")throw new p("RUN_STOPPED","The person stopped this run in Buoy Desktop (reason=user).","Tell them: You stopped this run in Sims. Start a new run only if they ask.");if(e.endedBy==="deadline")throw new p("RUN_ENDED","This run ran out of time.","Start a new run to go on. Ask for more time with sim_farm_extend before the end next time.")}if(ae.includes(e.state)||e.state==="stopping")throw new p("RUN_ENDED",`This run has ended (${e.state}).`,"Start a new run.")}ownRun(e,t){const s=this.state.runs[e];if(!s)throw new p("NOT_FOUND",`No run ${e}.`,"Use the runId that sim_farm_acquire returned.");if(t&&s.owner.clientId!==t)throw new p("NOT_OWNED",`Run ${e} belongs to ${s.owner.label}.`);this.assertRunOpen(s);return s}async forgetPhone(e){await H.rm(Ke(e,this.store.homeDir),{force:true});await H.rm(lt(e,this.store.homeDir),{force:true})}async tickPhones(){for(const e of Object.values(this.state.sims)){if(e.phone?.status!=="on"||e.power!=="awake")continue;const t=e.leaseId?this.state.leases[e.leaseId]:void 0;const s=t?.runId?this.state.runs[t.runId]:void 0;if(!s||ae.includes(s.state))continue;const i=await pt(e.udid,this.store.homeDir);if(!i?.overLimit||!i.pid||e.phone.notedPid===i.pid)continue;e.phone.notedPid=i.pid;const o=s.lanes[e.udid]?.label??e.name;const n=e.phone.phoneName??"the phone";this.note(s,"farm",i.ended==="safety"?`${o}: the app used ${i.peakMB} MB and hit the farm's safety cap, so it was closed. ${n} would have closed it sooner.`:i.ended==="limit"?`${o}: the app went past ${n}'s memory limit (${i.peakMB} MB) and was closed, like on a real phone.`:`${o}: the app went past ${n}'s memory limit (${i.peakMB} MB). A real phone would close it.`,e.udid);this.store.save()}}note(e,t,s,i){e.notes.push({at:new Date().toISOString(),by:t,text:s.slice(0,500),udid:i});if(e.notes.length>Ei)e.notes.splice(0,e.notes.length-Ei)}addLane(e,t){const s=e.runId?this.state.runs[e.runId]:void 0;if(!s)return;const i=this.labels.get(e.id)?.[e.sims.length-1];s.lanes[t.udid]={udid:t.udid,name:t.name,label:i?.slice(0,80),state:"setting-up",at:new Date().toISOString()};if(s.state==="waiting")s.state="active"}overdueEnd(e){return this.clock.now()>=(this.hardDeadlines.get(e.id)??Infinity)?"deadline":"lost"}settleRun(e,t){const s=e.runId?this.state.runs[e.runId]:void 0;if(!s)return;for(const o of e.sims)if(s.lanes[o])s.lanes[o].gone=true;if(ae.includes(s.state)||s.state==="stopping"||t===null)return;const i=s.leases.some(o=>this.state.leases[o]);switch(t){case"released":if(!i)this.endRun(s,this.outcomeOf(s)==="failed"?"failed":"done","released");return;case"acquire-failed":if(!i&&!Object.keys(s.lanes).length)this.endRun(s,"failed","acquire-failed");return;case"user":return this.endRun(s,"stopped","user","You stopped this run.");case"deadline":return this.endRun(s,"stopped","deadline","Ran out of time. The sims were taken back.");case"owner-exited":return this.endRun(s,"interrupted","owner-exited","The chat closed. Cleaning up.");case"lost":return this.endRun(s,"interrupted","lost","Lost touch with the chat. Cleaning up.");case"finished":return this.endRun(s,"done","finished")}}endRun(e,t,s,i,o="full"){if(ae.includes(e.state))return;e.state="stopping";e.endedBy=s;if(i)this.note(e,s==="user"?"person":"farm",i);for(const n of e.leases){if(this.state.leases[n])void this.endLease(n,i??s,s==="finished"||s==="released"?o:"full",null)}for(const n of Object.values(e.lanes))n.gone=true;e.state=t;e.endedAt=new Date().toISOString();e.outcome=e.outcome??this.outcomeOf(e);this.store.event({kind:"run-end",message:`${e.owner.label}: "${e.goal}" ${t==="done"?"finished":t}.`,runId:e.id})}outcomeOf(e){const t=Object.values(e.lanes);const s=t.filter(o=>o.state==="passed").length;const i=t.filter(o=>o.state==="failed").length;if(!s&&!i)return"unknown";if(i&&s)return"mixed";return i?"failed":"passed"}report(e,t){const s=this.ownRun(e.runId,t);const i=new Date().toISOString();if(e.sequence!==void 0){if(!Number.isInteger(e.sequence)||e.sequence<0)throw new p("BAD_REQUEST","sequence must be a whole number.");if(s.lastSeq!==void 0&&e.sequence<=s.lastSeq)return{applied:false,reason:"already have this report",...this.timeLeft(s)};s.lastSeq=e.sequence}if(e.state!==void 0&&!$i.includes(e.state))throw new p("BAD_REQUEST",`state must be one of ${$i.join(", ")}.`);if(e.udid!==void 0){const o=s.lanes[e.udid];if(!o)throw new p("NOT_FOUND",`That sim is not in run ${s.id}.`,"Use a UDID from this run's sims.");if(e.state)o.state=e.state;if(e.state==="passed"||e.state==="failed")o.resultSource=e.resultSource==="test"?"test":"agent";if(e.note!==void 0)o.note=String(e.note).slice(0,200);o.at=i}if(e.counts!==void 0){const o=e.counts;const n=a=>a===void 0||Number.isInteger(a)&&a>=0;if(!o||typeof o!=="object"||!n(o.done)||!n(o.total)||!n(o.passed)||!n(o.failed)||o.done>o.total){throw new p("BAD_REQUEST","counts needs whole numbers, with done no more than total.")}s.counts={done:o.done,total:o.total,passed:o.passed,failed:o.failed}}if(e.note)this.note(s,"agent",String(e.note),e.udid);s.lastReportAt=s.lastActivityAt=i;this.store.save();return{applied:true,...this.timeLeft(s)}}extend(e,t,s){const i=this.ownRun(e,s);if(!Number.isFinite(t)||t<=0)throw new p("BAD_REQUEST","minutes must be above zero.");const o=Date.parse(i.deadlineAt);const n=Date.now()+t*6e4;const a=Math.min(n,Date.parse(i.capAt));if(a<=o){return{granted:false,partly:false,reason:"This run is at its time limit.",...this.timeLeft(i)}}i.deadlineAt=new Date(a).toISOString();i.lastActivityAt=new Date().toISOString();for(const r of i.leases){const c=this.state.leases[r];if(!c)continue;c.ttlMs=a-Date.parse(c.createdAt);this.hardDeadlines.set(r,this.clock.now()+(a-Date.now()))}this.note(i,"farm",`More time: until ${new Date(a).toLocaleTimeString()}.`);this.store.save();return{granted:true,partly:a<n,...this.timeLeft(i)}}finish(e,t,s,i){const o=this.state.runs[e];if(o?.endedBy==="finished"&&o.owner.clientId===i)return{state:o.state,outcome:o.outcome};const n=this.ownRun(e,i);if(t&&!["passed","failed","mixed","unknown"].includes(t))throw new p("BAD_REQUEST","outcome must be passed, failed, mixed or unknown.");n.outcome=t??this.outcomeOf(n);if(s)n.summary=s.slice(0,600);this.endRun(n,n.outcome==="failed"?"failed":"done","finished",void 0,"fast");this.store.save();return{state:n.state,outcome:n.outcome}}stopRun(e,t="you"){const s=this.state.runs[e];if(!s)throw new p("NOT_FOUND",`No run ${e}.`);if(ae.includes(s.state))return{state:s.state};this.endRun(s,"stopped","user",t==="you"?"You stopped this run.":`Stopped by ${t}.`);this.store.save();return{state:s.state}}stopAll(e="you"){const t=Object.values(this.state.runs).filter(s=>!ae.includes(s.state));for(const s of t)this.stopRun(s.id,e);return{stopped:t.length}}timeLeft(e){return{deadlineAt:e.deadlineAt,capAt:e.capAt,secondsLeft:Math.max(0,Math.round((Date.parse(e.deadlineAt)-Date.now())/1e3))}}async tickRuns(){const e=Date.now();for(const o of Object.values(this.state.runs)){if(ae.includes(o.state))continue;if(e>=Date.parse(o.deadlineAt)){this.endRun(o,"stopped","deadline","Ran out of time. The sims were taken back.");this.store.save()}else if(!o.leases.some(n=>this.state.leases[n])&&!await J(o.owner.pid,o.owner.pidStart,true)){this.endRun(o,"interrupted","owner-exited","The chat closed.");this.store.save()}}const t=this.state.settings.keepRunsHours*36e5;const s=Object.values(this.state.runs).filter(o=>ae.includes(o.state)).sort((o,n)=>Date.parse(n.endedAt??n.createdAt)-Date.parse(o.endedAt??o.createdAt));let i=false;s.forEach((o,n)=>{if(n>=200||e-Date.parse(o.endedAt??o.createdAt)>t){delete this.state.runs[o.id];i=true}});if(i)this.store.save()}async status(e={}){const t=await this.budget();const s=await Promise.all(Object.values(this.state.sims).map(async o=>{const n=o.leaseId?this.state.leases[o.leaseId]:void 0;const a=n?.runId?this.state.runs[n.runId]:void 0;const r=o.phone?.status==="on"?await pt(o.udid,this.store.homeDir):null;const c=o.phone&&{...o.phone,notedPid:void 0,...r?.loaded?{pid:r.pid,peakMB:r.peakMB,memoryWarnings:r.memoryWarnings,overLimit:r.overLimit,ended:r.ended}:{}};return{...o,phone:c,owner:n?.owner.label,purpose:a?.goal??n?.purpose,runId:a?.id,lane:a?.lanes[o.udid]?.label,busy:this.busy.has(o.udid)}}));const i={farmId:this.state.farmId,counts:{pool:s.length,awake:s.filter(o=>o.power==="awake").length,leased:s.filter(o=>o.leaseId).length},budget:{roomForMoreSims:t.roomFor,freeDiskGB:Math.floor(t.stats.freeDiskBytes/Te),availableMemGB:Math.floor(t.stats.availableMemBytes/Te),memPressure:t.stats.memPressure,loadAvg1:Number(t.stats.loadAvg1.toFixed(1)),cpuCount:t.stats.cpuCount},templates:Object.values(this.state.templates),sims:s,android:{templates:Object.values(this.androidTemplates).map(o=>{const n=this.android.stale(o);return{...o,platform:"android",costGB:re(o),roomFor:Math.floor((t.ramRoomGB??0)/re(o)),...n&&o.usable?{usable:false,note:n}:{}}}),copies:Object.values(this.copies).map(o=>this.copyView(o))},orphans:Object.values(this.state.orphans),leases:Object.values(this.state.leases).map(o=>({...o,expiresInMs:Math.max(0,Math.round(this.leaseDeadline(o)-this.clock.now()))})),runs:Object.values(this.state.runs).sort((o,n)=>Date.parse(n.createdAt)-Date.parse(o.createdAt)).map(o=>({...o,notes:o.notes.slice(-50),secondsLeft:ae.includes(o.state)?0:Math.max(0,Math.round((Date.parse(o.deadlineAt)-Date.now())/1e3))})),locks:Object.values(this.state.locks),recentEvents:this.state.events.slice(-25),settings:this.state.settings};if(e.outside){const o=new Set([...Object.keys(this.state.sims),...Object.keys(this.state.orphans),...Object.values(this.state.templates).map(n=>n.udid)]);i.outsideSims=(await this.host.listDevices()).filter(n=>!o.has(n.udid)&&n.available).map(n=>({udid:n.udid,name:n.name,state:n.state,runtime:n.runtime}))}return i}setSettings(e){const t=this.state.settings;const s={parkAfterMinutes:[0,24*60],maxConcurrentBoots:[1,8],reserveRamGB:[4,512],reserveDiskGB:[5,2048],slimSimGB:[.5,16],stockSimGB:[1,32],cloneDiskGB:[.1,32],runCapMinutes:[5,24*60],keepRunsHours:[1,24*14],fairMinFreeShare:[0,1],fairMaxSwingShare:[.02,1],fairMaxThermalLevel:[0,4],fairMaxSwapGrowthMB:[0,65536],personLeaseHours:[1,24],phoneCapMB:[0,1048576],trimIdleHours:[0,24*30],keepWarmPerTemplate:[0,64]};if(!e||typeof e!=="object")throw new p("BAD_REQUEST","settings must be an object.");if("phoneProfile"in e){const o=Oe(this.store.homeDir);if(e.phoneProfile!==zt&&!(typeof e.phoneProfile==="string"&&o[e.phoneProfile])){throw new p("BAD_REQUEST",`phoneProfile must be "off" or one of: ${Object.keys(o).join(", ")}.`)}}if("phoneOnLimit"in e&&e.phoneOnLimit!=="warn"&&e.phoneOnLimit!=="kill"){throw new p("BAD_REQUEST",`phoneOnLimit must be "warn" or "kill".`)}if("phoneCapMB"in e&&(!Number.isInteger(e.phoneCapMB)||e.phoneCapMB!==0&&e.phoneCapMB<3072)){throw new p("BAD_REQUEST","phoneCapMB must be 0 (automatic) or a whole number of MB, at least 3072.")}for(const[o,n]of Object.entries(e)){if(!(o in t)||typeof n!==typeof t[o])throw new p("BAD_REQUEST",`Bad setting ${o}.`);const a=s[o];if(a&&(typeof n!=="number"||!Number.isFinite(n)||n<a[0]||n>a[1])){throw new p("BAD_REQUEST",`${o} must be ${a[0]} to ${a[1]}.`)}}const i=["phoneProfile","phoneOnLimit","phoneCapMB"].some(o=>o in e&&e[o]!==t[o]);for(const[o,n]of Object.entries(e))t[o]=n;this.store.save();if(i){for(const o of Object.keys(this.state.sims))void dt(o,this.store.homeDir).catch(()=>void 0)}return t}};function Kn(e,t){return Math.min(t,Math.max(4,Math.round(e*.375)))}function Qn(e){return Math.min(4,Math.max(1.5,e*.1))}function Vn(e,t){const s=i=>Math.round(i*10)/10;return{minFreeCores:s(Math.max(2,e.fairMinFreeShare*t)),maxSwingCores:s(Math.max(1,e.fairMaxSwingShare*t)),maxThermalLevel:e.fairMaxThermalLevel,maxSwapGrowthMB:e.fairMaxSwapGrowthMB}}function Oi(e,t=Date.now()-3*6e4){const s=new Map;for(const i of e.split("\n")){const o=/^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d)\.\d+ \((?:user|system)\/\d*\/?([\w.-]+) \[\d+\]\) <\w+>: exited due to (SIG\w+)/.exec(i);if(!o||o[3]==="SIGKILL"||o[3]==="SIGTERM")continue;const n=Date.parse(o[1].replace(" ","T"));if(!(n>=t))continue;const a=s.get(o[2])??[];a.push(n);s.set(o[2],a)}for(const[i,o]of s){o.sort((n,a)=>n-a);for(let n=2;n<o.length;n++)if(o[n]-o[n-2]<=6e4)return i.replace(/^com\.apple\./,"")}return null}import{promises as Ti}from"fs";import De from"os";import Ae from"path";var Di=6e4;var Zt=class{constructor(){this.disabledRoot=Be;this.devicesDir=Ae.join(De.homedir(),"Library/Developer/CoreSimulator/Devices");this.graphicsPids=[];this.loadCalls=0}simctl(e,t=Di){return U("xcrun",["simctl",...e],{timeoutMs:t})}async listDevices(){const e=JSON.parse(await this.simctl(["list","devices","--json"],3e4));const t=[];for(const[s,i]of Object.entries(e.devices??{})){for(const o of i){t.push({udid:o.udid,name:o.name,state:o.state,runtime:s,deviceType:o.deviceTypeIdentifier,dataPath:o.dataPath??Ae.join(this.devicesDir,o.udid,"data"),available:o.isAvailable!==false})}}return t}async dataPathOf(e){if(!/^[0-9A-F-]{36}$/i.test(e))return null;const t=Ae.join(this.devicesDir,e);try{await Ti.access(Ae.join(t,"device.plist"));return Ae.join(t,"data")}catch{return null}}async create(e,t,s){return(await this.simctl(["create",e,t,s])).trim()}async clone(e,t){return(await this.simctl(["clone",e,t])).trim()}async boot(e){const t=await _("xcrun",["simctl","boot",e],{timeoutMs:Di,env:st()});if(t.code!==0&&!/current state: Booted/i.test(t.stderr)){throw new Error(`simctl boot ${e} failed: ${t.stderr.trim().slice(0,300)}`)}}async shutdown(e){const t=await _("xcrun",["simctl","shutdown",e],{timeoutMs:9e4});if(t.code!==0&&!/current state: Shutdown/i.test(t.stderr)){throw new Error(`simctl shutdown ${e} failed: ${t.stderr.trim().slice(0,300)}`)}}async delete(e){await this.simctl(["delete",e],9e4)}async install(e,t){await this.simctl(["install",e,t],18e4)}async launch(e,t,s){await U("xcrun",["simctl","launch","--terminate-running-process",e,t],{timeoutMs:6e4,env:s&&Object.keys(s).length?{...process.env,...s}:void 0})}async terminate(e,t){await _("xcrun",["simctl","terminate",e,t],{timeoutMs:2e4})}async openUrl(e,t){await this.simctl(["openurl",e,t],2e4)}async screenshot(e,t,s=1e4){const i=await _("xcrun",["simctl","io",e,"screenshot",t],{timeoutMs:s});return i.code===0&&!i.timedOut}spawn(e,t,s=2e4){return _("xcrun",["simctl","spawn",e,...t],{timeoutMs:s})}async appContainer(e,t){const s=await _("xcrun",["simctl","get_app_container",e,t,"data"],{timeoutMs:2e4});return s.code===0?s.stdout.trim()||null:null}async simTree(e){const t=await tt();const s=t.find(i=>/launchd_sim/.test(i.command)&&i.command.includes(e));return s?Ot(t,s.pid):[]}async signal(e,t){for(const s of e){try{process.kill(s,t)}catch{}}}async launchdLogTail(e,t=256*1024){const s=Ae.join(this.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`,"launchd.log");try{const i=await Ti.open(s,"r");try{const{size:o}=await i.stat();const n=Math.max(0,o-t);const a=Buffer.alloc(o-n);await i.read(a,0,a.length,n);return a.toString("utf8")}finally{await i.close()}}catch{return""}}async hostStats(){const[e,t,s,i]=await Promise.all([U("vm_stat",[],{timeoutMs:1e4}),_("sysctl",["-n","kern.memorystatus_vm_pressure_level"],{timeoutMs:5e3}),_("sysctl",["-n","vm.swapusage"],{timeoutMs:5e3}),U("df",["-k",De.homedir()],{timeoutMs:1e4})]);const o=Number(/page size of (\d+)/.exec(e)?.[1]??16384);const n=h=>Number(new RegExp(`${h}:\\s+(\\d+)`).exec(e)?.[1]??0);const a=n("Pages free")+n("Pages inactive")+n("Pages speculative")+n("Pages purgeable");const r=Number(t.stdout.trim());const c=/used = ([\d.]+)M/.exec(s.stdout)?.[1];const l=i.trim().split("\n").pop()??"";const d=Number(l.split(/\s+/)[3]??0);return{totalMemBytes:De.totalmem(),availableMemBytes:a*o,memPressure:r>=4?"critical":r>=2?"warn":"normal",swapUsedBytes:c?Number(c)*1024*1024:0,freeDiskBytes:d*1024,cpuCount:De.cpus().length,loadAvg1:De.loadavg()[0]}}async showWindow(e){const t=await _("open",["-b","com.apple.iphonesimulator","--args","-CurrentDeviceUDID",e],{timeoutMs:2e4});if(t.code===0)return;await U("open",["-b","com.apple.dt.Devices"],{timeoutMs:2e4})}async loadCounters(e,t=true){if(t&&this.loadCalls++%10===0){const k=await _("pgrep",["-x","SimMetalHost"],{timeoutMs:5e3});this.graphicsPids=k.stdout.split("\n").map(Number).filter(y=>y>0)}const s=De.cpus();let i=0;let o=0;for(const k of s){const y=k.times;const b=y.user+y.nice+y.sys+y.idle+y.irq;o+=b;i+=b-y.idle}const n=[...e,...this.graphicsPids];const[a,r,c,l]=await Promise.all([t&&n.length?_("ps",["-o","pid=,time=","-p",n.join(",")],{timeoutMs:5e3}):null,t?_("ioreg",["-r","-c","IOAccelerator","-d","1"],{timeoutMs:5e3}):null,_("sysctl",["-n","kern.memorystatus_vm_pressure_level","vm.swapusage"],{timeoutMs:5e3}),_("notifyutil",["-g","com.apple.system.thermalpressurelevel"],{timeoutMs:5e3})]);const d=new Set(this.graphicsPids);let h=0;let f=0;for(const k of a?.stdout.split("\n")??[]){const y=/^\s*(\d+)\s+(\S+)/.exec(k);if(!y)continue;const b=xs(y[2]);if(d.has(Number(y[1])))f+=b;else h+=b}const w=r&&/"Device Utilization %"=(\d+)/.exec(r.stdout);const v=/thermalpressurelevel\s+(\d+)/.exec(l.stdout);return{hostBusyMs:i,hostTotalMs:o,cpuCount:s.length,pidsCpuMs:t?h:null,graphicsCpuMs:t?f:null,gpuPct:w?Number(w[1]):null,memPressureLevel:Number(c.stdout.trim().split("\n")[0])||1,swapUsedMB:Number(/used = ([\d.]+)M/.exec(c.stdout)?.[1]??0),thermalLevel:v?Number(v[1]):null}}async xcodeBuild(){const e=await _("xcodebuild",["-version"],{timeoutMs:2e4});return e.stdout.trim().replace(/\s+/g," ")||"unknown"}async clientsOf(e){const t=await tt();const s=new Set((await this.simTree(e)).map(o=>o.pid));const i=new Set(Ot(t,process.pid).map(o=>o.pid));return t.filter(o=>!i.has(o.pid)&&!s.has(o.pid)&&o.command.includes(e)&&!/launchd_sim|CoreSimulator/.test(o.command))}async kill(e,t){if(!await J(e,t))return;try{process.kill(e,"SIGTERM")}catch{return}for(let s=0;s<20;s++){await new Promise(i=>setTimeout(i,100));if(!await J(e,t))return}if(await J(e,t)){try{process.kill(e,"SIGKILL")}catch{}}}};import Jn from"net";import{chmodSync as Xn,rmSync as Zn,appendFileSync as Ai,mkdirSync as ea}from"fs";import{timingSafeEqual as ta}from"crypto";var sa=["android"];var Ri=new Set(["setup","grow","shrink","acquire","release","action","lock","unlock","park","wake","reset","rebuild","delete","endLease","settings","deleteFarm","show","removeTemplate","report","extend","finish","stopRun","stopAll","bench","dropCopies"]);async function Pi(e={}){const t=e.home??A();const s=X(t);if(Buffer.byteLength(s.socket)>103){const n=new p("BAD_REQUEST",`The farm folder's path is too long for macOS (${Buffer.byteLength(s.socket)} of 103 bytes).`,"Set BUOY_FARM_HOME to a shorter folder.");try{ea(t,{recursive:true});Ai(s.log,`${new Date().toISOString()} ${n.message} ${n.next}
|
|
369
|
-
`)}catch{}throw n}const i=await
|
|
370
|
-
`)}catch{}e.log?.(n)};try{return await ia(e,t,i,o)}catch(n){i.release();throw n}}async function ia(e,t,s,i){const o=X(t);const n=Vs(t);const a=new ei(t);const r=new Bi({host:e.host??new Zt,store:a,log:i,pollMs:e.pollMs,readyTimeoutMs:e.readyTimeoutMs,phoneBuilder:e.phoneBuilder});await r.recover();Zn(o.socket,{force:true});const c=new Set;let l=Date.now();let d=false;let h=0;const f=Jn.createServer(m=>{c.add(m);m.on("close",()=>c.delete(m));m.on("error",()=>{});let u="";m.on("data",g=>{u+=g.toString("utf8");if(u.length>1024*1024)return m.destroy();let S;while((S=u.indexOf("\n"))>=0){const M=u.slice(0,S);u=u.slice(S+1);if(M.trim())void v(m,M)}})});const w=(m,u,g)=>{if(!m.destroyed)m.write(JSON.stringify({id:u,...g})+"\n")};async function v(m,u){l=Date.now();let g;try{g=JSON.parse(u)}catch{return w(m,-1,{ok:false,error:{code:"BAD_REQUEST",message:"Not JSON."}})}h++;try{if(!g||typeof g!=="object")throw new p("BAD_REQUEST","Use a request object.");if(!oa(g.token,n))throw new p("UNAUTHORIZED","Bad farm token.");if(!g.client||typeof g.client.clientId!=="string"||!g.client.clientId.trim())throw new p("BAD_REQUEST","Each call needs a clientId.");if(d&&!["hello","status"].includes(g.method)){throw new p("HOST_BUSY","The farm is stopping.","Try again in a few seconds.",true)}if(g.v>te&&Ri.has(g.method)){throw new p("FARMD_VERSION",`This farm runs protocol ${te}; the client speaks ${g.v}.`,"Ask it to stop when idle (method shutdown), then start a newer farm.")}if(g.v<te&&Ri.has(g.method)){throw new p("READ_ONLY",`This client speaks farm protocol ${g.v}; the farm runs ${te}.`,"Update Buoy to change the farm.")}na(g);const S=await k(g);w(m,g.id,{ok:true,result:S})}catch(S){const M=S instanceof p?S.toJSON():{code:"HOST_ERROR",message:S.message,retryable:false};w(m,g?.id??-1,{ok:false,error:M})}finally{h--}}async function k(m){const u=m.params??{};const g=m.client;const S=M=>r.once(g.clientId,m.requestId,{m:m.method,q:u},M);switch(m.method){case"hello":return{protocol:te,features:sa,pid:process.pid,host:s.info.host,farmId:r.state.farmId};case"status":return r.status({outside:u.outside});case"budget":return r.budget({ramGB:u.ramGB??0,diskGB:u.diskGB??0});case"setup":return S(()=>u.platform==="android"?r.createAndroidTemplate(u):r.createTemplate(u));case"grow":return S(async()=>(await r.grow(u.templateId,u.count)).map(M=>M.name));case"shrink":return S(()=>r.shrink(u.templateId,u.count));case"removeTemplate":return S(()=>r.removeTemplate(u.templateId));case"acquire":return S(async()=>{const{lease:M,run:I,sims:R}=await r.acquire({...u,owner:g});return{runId:I.id,leaseId:M.id,epoch:M.epoch,deadlineAt:I.deadlineAt,capAt:I.capAt,sims:R.map($=>"platform"in $?{udid:$.udid,name:$.name,platform:$.platform,serial:$.serial,buoyDeviceId:$.buoyDeviceId}:{udid:$.udid,name:$.name,buoyDeviceId:$.buoyDeviceId,profile:$.profile})}});case"renew":return r.renew(u.leaseId,u.epoch,g.clientId);case"release":return S(()=>r.release(u.leaseId,u.epoch,u.reset,g.clientId,u.keepRun===true));case"action":return S(()=>r.action(u.leaseId,u.epoch,u.udids,u.action,u.arg,g.clientId));case"lock":return r.lock(u.name,u.leaseId,u.epoch,u.waitMs??0,g.clientId);case"unlock":return r.unlock(u.name,u.leaseId,u.epoch,g.clientId);case"fairness":return r.fairness(u.leaseId,u.epoch,u.windows,g.clientId);case"dropCopies":return S(()=>r.dropCopies(u.leaseId,u.epoch,u.udids,g.clientId));case"bench":return r.copyBench(u.leaseId,u.epoch,u.udid,{seconds:u.seconds,launches:u.launches},g.clientId);case"park":return S(async()=>(await r.parkSim(u.udid,"asked"),"parked"));case"wake":return S(async()=>(await r.bootSim(u.udid)).power);case"reset":return S(async()=>(await r.fastReset(u.udid),"reset"));case"rebuild":return S(async()=>(await r.rebuildSim(u.udid)).udid);case"delete":return S(async()=>(await r.deleteSim(u.udid),"deleted"));case"endLease":{const M=r.state.leases[u.leaseId];if(!M)return{};if(M.owner.clientId!==g.clientId&&u.force!==true){throw new p("NOT_OWNED",`Lease ${u.leaseId} belongs to ${M.owner.label}.`,"Pass force: true only after a person confirms.")}return S(()=>r.endLease(u.leaseId,`ended by ${g.label}`,"full",M.owner.clientId===g.clientId?"released":"user"))}case"report":return S(async()=>r.report(u,g.clientId));case"extend":return S(async()=>r.extend(u.runId,u.minutes,g.clientId));case"finish":return S(async()=>r.finish(u.runId,u.outcome,u.summary,g.clientId));case"stopRun":{const M=r.state.runs[u.runId];if(M&&M.owner.clientId!==g.clientId&&u.force!==true){throw new p("NOT_OWNED",`Run ${u.runId} belongs to ${M.owner.label}.`,"Pass force: true only after a person confirms.")}return S(async()=>r.stopRun(u.runId))}case"stopAll":if(u.force!==true)throw new p("BAD_REQUEST","stopAll needs force: true, after a person confirms.");return S(async()=>r.stopAll());case"settings":return r.setSettings(u);case"shot":return r.shot(u.udid);case"show":return r.show(u.udid);case"shutdown":if(u.ifIdle!==false&&(!r.idle()||h>1))return{stopping:false,reason:"busy"};void N("asked to stop");return{stopping:true};default:throw new p("BAD_REQUEST",`Unknown method ${m.method}.`)}}await new Promise((m,u)=>{f.once("error",u);f.listen(o.socket,()=>m())});Xn(o.socket,384);i(`farmd ${te} up, pid ${process.pid}, host ${e.hostLabel??"node"}`);const y=setInterval(()=>{void r.tick().catch(u=>i(`tick: ${u.message}`));const m=Date.now()-l;if(e.idleExitMs&&c.size===0&&m>e.idleExitMs){void(async()=>{await r.parkAllIdle();if(r.idle()&&c.size===0&&h===0&&Date.now()-l>e.idleExitMs)await N("idle")})()}},e.tickMs??5e3);y.unref?.();const b=setInterval(()=>void r.meter.tick().catch(m=>i(`meter: ${m.message}`)),1e3);b.unref?.();let E=null;function N(m,u=10*6e4){if(!E)E=D(m,u);return E}async function D(m,u){if(d)return;d=true;clearInterval(y);clearInterval(b);i(`farmd stopping: ${m}`);const g=Date.now()+u;while(h>0&&Date.now()<g)await new Promise(I=>setTimeout(I,200));const S=await r.drain(Math.max(0,g-Date.now()));let M=false;if(h>0||!S||Bt()>0){a.save();r.fence();M=true;i(`farmd stopping with ${h} call(s) and ${Bt()} child process(es) still running; killing them`);Io();const I=Date.now()+5e3;while(Bt()>0&&Date.now()<I)await new Promise(R=>setTimeout(R,100))}await r.settleFiles();if(!M&&h===0){try{await r.parkAllIdle()}catch{}}for(const I of c)I.destroy();await new Promise(I=>f.close(()=>I()));if(!M){a.save();r.fence()}s.release();e.onExit?.(m)}return{engine:r,owner:s,stop:N}}function oa(e,t){if(typeof e!=="string"||e.length!==t.length)return false;return ta(Buffer.from(e),Buffer.from(t))}function na(e){const t=a=>{throw new p("BAD_REQUEST",a)};const s=e.params??{};if(typeof s!=="object"||Array.isArray(s))t("Params must be an object.");const i=(a,r=false)=>{if(r&&s[a]===void 0)return;if(typeof s[a]!=="string"||!s[a].trim())t(`${a} must be text.`)};const o=a=>{if(s[a]!==void 0&&typeof s[a]!=="boolean")t(`${a} must be true or false.`)};const n=(a,r,c,l=false,d=false)=>{const h=s[a];if(d&&h===void 0)return;if(typeof h!=="number"||!Number.isFinite(h)||h<r||h>c||l&&!Number.isInteger(h))t(`${a} must be a number from ${r} to ${c}.`)};switch(e.method){case"status":o("outside");break;case"budget":n("ramGB",0,Number.MAX_VALUE,false,true);n("diskGB",0,Number.MAX_VALUE,false,true);break;case"setup":if(s.platform==="android"){i("apk");i("phoneId",true);i("launchUrl",true);if(s.reverse!==void 0&&(!Array.isArray(s.reverse)||s.reverse.some(a=>typeof a!=="string"||!/^\d{1,5}(:\d{1,5})?$/.test(a))))t('reverse must be a list like ["8081:8802"].');if(s.image!==void 0&&(typeof s.image!=="object"||!["aosp_atd","google_atd"].includes(s.image?.tag)))t("image tag must be aosp_atd or google_atd.");break}for(const a of["appPath","deviceType","runtime"])i(a);if(s.seedDir!=null)i("seedDir");i("name",true);i("launchUrl",true);o("warmLaunch");if(!["slim","stock"].includes(s.profile))t("Use slim or stock.");n("metroPort",1,65535,true);break;case"removeTemplate":i("templateId");break;case"grow":case"shrink":case"acquire":i("templateId");n("count",1,64,true);if(e.method==="acquire"){i("purpose");n("ttlMs",1,Number.MAX_VALUE,false,true);n("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);o("launch");o("allowPartial");i("goal",true);i("runId",true);i("kind",true);if(s.labels!==void 0&&(!Array.isArray(s.labels)||s.labels.some(a=>typeof a!=="string")))t("labels must be a list of text.");if(!Number.isInteger(e.client.pid)||e.client.pid<=0)t("The client needs a valid pid.")}break;case"release":if(s.reset!==void 0&&!["fast","full"].includes(s.reset))t("Use fast or full reset.");break;case"action":if(!Array.isArray(s.udids)||s.udids.some(a=>typeof a!=="string"||!a))t("udids must be a list of sim ids.");if(!["reset","rebuild","open-url","screenshot","launch","install"].includes(s.action))t("Unknown sim action.");i("arg",s.action!=="open-url"&&s.action!=="install");break;case"lock":case"unlock":i("name");if(!/^[a-z0-9-]{1,40}$/.test(s.name))t("Lock names are short lowercase words.");n("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);break;case"fairness":if(!Array.isArray(s.windows)||s.windows.length>500||s.windows.some(a=>!a||typeof a.id!=="string"||!Number.isFinite(a.from)||!Number.isFinite(a.to)||a.to<a.from)){t("windows must be a list of { id, from, to } times in ms.")}break;case"endLease":i("leaseId");break;case"report":i("runId");i("udid",true);i("note",true);n("sequence",0,Number.MAX_SAFE_INTEGER,true,true);if(s.state!==void 0&&typeof s.state!=="string")t("state must be text.");break;case"extend":i("runId");n("minutes",1,24*60);break;case"dropCopies":if(!Array.isArray(s.udids)||!s.udids.length||s.udids.some(a=>typeof a!=="string"||!a))t("udids must be a list of copy ids.");break;case"bench":i("leaseId");i("udid");n("seconds",1,600,false,true);n("launches",0,20,true,true);break;case"finish":i("runId");i("summary",true);i("outcome",true);break;case"stopRun":i("runId");o("force");break;case"stopAll":o("force");break;case"shutdown":o("ifIdle");break}if(["renew","release","action","lock","unlock","fairness","dropCopies"].includes(e.method))i("leaseId")}import aa from"net";import{existsSync as ra}from"fs";import{spawn as ca}from"child_process";import{randomBytes as Ni}from"crypto";function es(e,t=["farmd"]){return()=>{const s=ca(process.execPath,[...process.execArgv,e,...t],{detached:true,stdio:"ignore",env:st()});s.unref()}}var Ci=class{constructor(e){this.opts=e;this.sock=null;this.nextId=1;this.pending=new Map;this.buf="";this.renewals=new Map;this.reconnecting=null;this.features=[];this.askedOlderToStop=false;this.closed=false;this.home=e.home??A();this.clientIdValue=e.clientId??`c-${Ni(5).toString("hex")}`}get clientId(){return this.owner?.clientId}require(e){if(!this.features.includes(e)){throw new p("FARMD_VERSION",`The farm running on this Mac is too old for ${e}.`,"Update Buoy Desktop, or quit it so a newer farm can start, then try again.")}}async connect(){this.token=Vs(this.home);this.owner={clientId:this.clientIdValue,pid:process.pid,pidStart:await Ie(process.pid)??void 0,label:this.opts.label,...this.opts.person?{kind:"person"}:{},...this.opts.agent?{agent:this.opts.agent}:{}};if(await this.tryOpen())return this.checkVersion();const e=await qe(this.home);if(!await Ye(e)){if(!this.opts.launcher)throw new p("HOST_ERROR","The farm is not running.","Start it from Buoy Desktop or `pnpm farm status`.");this.opts.launcher()}const t=Date.now()+(this.opts.startTimeoutMs??2e4);while(Date.now()<t){await T(250);if(await this.tryOpen())return this.checkVersion()}throw new p("HOST_ERROR","The farm did not start in time.",`Check ${X(this.home).log}.`)}async checkVersion(){const e=await this.send("hello",void 0,{});this.features=e.features??[];const t=e.protocol<te||!this.features.includes("android")&&!this.askedOlderToStop;if(t&&this.opts.launcher){this.askedOlderToStop=true;const s=await this.send("shutdown",{ifIdle:true},{});if(s.stopping){this.close();await T(500);return this.connect()}}return this}tryOpen(){const e=X(this.home).socket;if(!ra(e))return Promise.resolve(false);return new Promise(t=>{const s=aa.createConnection(e);s.once("connect",()=>{this.sock=s;s.on("data",i=>this.onData(i));s.on("close",()=>this.onClose());s.on("error",()=>{});t(true)});s.once("error",()=>t(false))})}onData(e){this.buf+=e.toString("utf8");let t;while((t=this.buf.indexOf("\n"))>=0){const s=this.buf.slice(0,t);this.buf=this.buf.slice(t+1);let i;try{i=JSON.parse(s)}catch{continue}const o=this.pending.get(i.id);if(!o)continue;this.pending.delete(i.id);clearTimeout(o.timer);if(i.ok)o.resolve(i.result);else{const n=i.error??{};o.reject(new p(n.code??"HOST_ERROR",n.message??"farm error",n.next,n.retryable))}}}onClose(){this.sock=null;for(const[,e]of this.pending){clearTimeout(e.timer);e.reject(new p("HOST_ERROR","Lost the connection to the farm.",void 0,true))}this.pending.clear()}reconnect(){if(!this.reconnecting){this.reconnecting=this.connect().then(()=>void 0).finally(()=>{this.reconnecting=null})}return this.reconnecting}async call(e,t,s={}){if(!this.sock&&!this.closed)await this.reconnect();return this.send(e,t,s)}send(e,t,s){if(!this.sock)return Promise.reject(new p("HOST_ERROR","Not connected to the farm.",void 0,true));const i=this.nextId++;const o={id:i,v:te,token:this.token,client:this.owner,method:e,params:t,requestId:s.requestId};return new Promise((n,a)=>{const r=setTimeout(()=>{this.pending.delete(i);a(new p("HOST_ERROR",`Farm call ${e} timed out.`,void 0,true))},s.timeoutMs??3e4);this.pending.set(i,{resolve:n,reject:a,timer:r});this.sock.write(JSON.stringify(o)+"\n")})}async acquire(e){const{requestId:t,...s}=e;const i=t??`acq-${Ni(4).toString("hex")}`;const o=await this.call("acquire",s,{timeoutMs:15*6e4+(e.waitMs??0),requestId:i});if(this.renewals.has(o.leaseId)||this.opts.person)return o;const n=setInterval(()=>{this.call("renew",{leaseId:o.leaseId,epoch:o.epoch}).catch(a=>{if(["LEASE_EXPIRED","STALE_EPOCH","RUN_STOPPED","RUN_ENDED"].includes(a.code))this.stopRenew(o.leaseId)})},15e3);n.unref?.();this.renewals.set(o.leaseId,n);return o}stopRenew(e){const t=this.renewals.get(e);if(t)clearInterval(t);this.renewals.delete(e)}async release(e,t,s="fast",i=false){this.stopRenew(e);return this.call("release",{leaseId:e,epoch:t,reset:s,keepRun:i},{timeoutMs:6e4,requestId:`rel-${e}-${t}`})}close(){this.closed=true;for(const e of[...this.renewals.keys()])this.stopRenew(e);this.sock?.end();this.sock=null}};async function ts(e){return new Ci(e).connect()}import Qe from"path";import la from"os";import{existsSync as ss,readdirSync as da,statSync as pa}from"fs";function Ve(e=process.cwd()){let t=e;for(;;){if(ss(Qe.join(t,"buoy.tools.json")))return t;const s=Qe.dirname(t);if(s===t)return null;t=s}}function ut(){const e=Qe.join(la.homedir(),"Library/Developer/Xcode/DerivedData");let t=null;try{for(const s of da(e)){if(!s.startsWith("exampledevbuild-"))continue;const i=Qe.join(e,s,"Build/Products/Debug-iphonesimulator/exampledevbuild.app");if(!ss(i))continue;const o=pa(i).mtimeMs;if(!t||o>t.t)t={p:i,t:o}}}catch{}return t?.p??null}function xi(e=Ve()){return e?Qe.join(e,"example-dev-build/artifacts/pool-seed"):null}async function ua(e=Ve()){const t=xi(e);let s=[];let i=[];try{const o=JSON.parse(await U("xcrun",["simctl","list","runtimes","--json"],{timeoutMs:3e4}));s=(o.runtimes??[]).filter(a=>a.isAvailable!==false&&/^iOS/.test(a.name??"")).map(a=>({id:a.identifier,name:a.name}));const n=JSON.parse(await U("xcrun",["simctl","list","devicetypes","--json"],{timeoutMs:3e4}));i=(n.devicetypes??[]).filter(a=>/iPhone|iPad/.test(a.name??"")).map(a=>({id:a.identifier,name:a.name}))}catch{}return{appPath:ut(),seedDir:t&&ss(t)?t:null,seedProblem:t?Le(t):"Could not find the repo's seed folder.",runtimes:s,deviceTypes:i,slimTurnsOff:Ce.map(o=>({name:o.name,downside:o.downside}))}}import ha from"path";var Li=[{app:"claude",name:"Claude",match:/(^|\/)claude$/i},{app:"codex",name:"Codex",match:/(^|\/)codex$/i},{app:"cursor",name:"Cursor",match:/cursor/i}];async function ma(e){const t=await _("ps",["-o","ppid=,comm=","-p",String(e)],{timeoutMs:3e3});const s=/^\s*(\d+)\s+(.+?)\s*$/.exec(t.stdout);return t.code===0&&s?{ppid:Number(s[1]),comm:s[2]}:null}async function fa(e){const t=await _("lsof",["-a","-p",String(e),"-d","cwd","-Fn"],{timeoutMs:3e3});const s=t.stdout.split("\n").find(i=>i.startsWith("n"));return s?s.slice(1):null}function wa(e){if(!e)return void 0;return Li.find(t=>e.toLowerCase().includes(t.app))}async function ga(e={}){const t=e.env??process.env;let s=e.pid??process.pid;let i=null;for(let d=0;d<5&&s>1;d++){const h=await ma(s).catch(()=>null);if(!h)break;const f=Li.find(w=>w.match.test(h.comm));if(d>0&&f){i={app:f,pid:s};break}s=h.ppid}const o=i?.app??wa(e.clientName);const n=(i?await fa(i.pid).catch(()=>null):null)??process.cwd();const a=(await _("git",["-C",n,"rev-parse","--abbrev-ref","HEAD"],{timeoutMs:3e3}).catch(()=>null))?.stdout.trim();const r=t.CLAUDE_CODE_SESSION_ID?.trim();let c;let l="none";if(r&&(o?.app??"claude")==="claude"){c=`claude:${r}`;l="claude-session"}else if(i){c=`${i.app.app}:${i.pid}:${await Ie(i.pid)??""}`;l="parent-process"}return{app:o?.app??"other",appName:o?.name??(e.clientName||"Agent"),chatKey:c,chatKeySource:l,project:ha.basename(n)||void 0,projectPath:n,branch:a&&a!=="HEAD"?a:void 0,clientName:e.clientName}}function ya(e){return e.project?`${e.appName} \xB7 ${e.project}`:e.appName}import Ui from"path";import{existsSync as va}from"fs";import Sa from"os";import{execFile as ba,spawn as ka}from"child_process";import{createHash as Ma}from"crypto";import{promises as ee}from"fs";import ht from"os";import Q from"path";import{promisify as _a}from"util";var be=_a(ba);var mt="gg.buoy.phoneprobe";var ft=1024*1024;function Ea(e){const t=e.match(/BUOY_PROBE physicalMemory=(\d+) cpus=(\d+) footprint=(\d+) available=(\d+)/);if(!t)return null;const[s,i,o,n]=t.slice(1).map(Number);const a=o+n;if(!s||!i||n<=0||a>s)return null;return{ramMB:Math.floor(s/ft),cpus:i,limitMB:Math.floor(a/ft)}}function ji(e,t){return e.match(new RegExp(`<key>${t}</key>\\s*<string>([^<]*)</string>`))?.[1]}function is(e,t,s){const i=e.match(new RegExp(`<key>${t}</key>\\s*<array>([\\s\\S]*?)</array>`))?.[1]??"";return[...i.matchAll(new RegExp(`<${s}>([\\s\\S]*?)</${s}>`,"g"))].map(o=>o[1].replace(/\s+/g,""))}function $a(e,t){const s=t.match(/<key>Entitlements<\/key>\s*<dict>([\s\S]*?)<\/dict>/)?.[1]??"";const i=ji(s,"application-identifier");const o=is(t,"TeamIdentifier","string")[0];const n=t.match(/<key>ExpirationDate<\/key>\s*<date>([^<]+)<\/date>/)?.[1];if(!i||!o||!n)return null;return{file:e,name:ji(t,"Name")??Q.basename(e),team:o,appId:i,expires:new Date(n),devices:is(t,"ProvisionedDevices","string"),allDevices:/<key>ProvisionsAllDevices<\/key>\s*<true\/>/.test(t),getTaskAllow:/<key>get-task-allow<\/key>\s*<true\/>/.test(s),certSha1s:is(t,"DeveloperCertificates","data").map(a=>Ma("sha1").update(Buffer.from(a,"base64")).digest("hex").toUpperCase())}}function Ia(e,t,s,i=new Date){const o=e.filter(a=>a.getTaskAllow&&a.expires>i&&(a.allDevices||a.devices.includes(t))&&(a.appId===`${a.team}.*`||a.appId===`${a.team}.${mt}`)&&a.certSha1s.some(r=>s.has(r)));o.sort((a,r)=>Number(r.appId.endsWith(".*"))-Number(a.appId.endsWith(".*"))||r.expires.getTime()-a.expires.getTime());const n=o[0];return n?{profile:n,identity:n.certSha1s.find(a=>s.has(a))}:null}async function Ba(){const e=[Q.join(ht.homedir(),"Library/Developer/Xcode/UserData/Provisioning Profiles"),Q.join(ht.homedir(),"Library/MobileDevice/Provisioning Profiles")];const t=[];for(const s of e){let i=[];try{i=(await ee.readdir(s)).filter(o=>o.endsWith(".mobileprovision"))}catch{continue}for(const o of i){const n=Q.join(s,o);try{const{stdout:a}=await be("security",["cms","-D","-i",n],{timeout:15e3,maxBuffer:16*ft});const r=$a(n,a);if(r)t.push(r)}catch{}}}return t}async function Oa(){const{stdout:e}=await be("security",["find-identity","-v","-p","codesigning"],{timeout:15e3});return new Set([...e.matchAll(/^\s*\d+\)\s+([0-9A-F]{40})\s/gm)].map(t=>t[1]))}async function Ta(){const e=await ee.mkdtemp(Q.join(ht.tmpdir(),"buoy-phones-"));try{const t=Q.join(e,"devices.json");await be("xcrun",["devicectl","list","devices","--json-output",t],{timeout:3e4});const s=JSON.parse(await ee.readFile(t,"utf8"))?.result?.devices??[];return s.filter(i=>i.hardwareProperties?.reality==="physical"&&i.hardwareProperties?.deviceType==="iPhone"&&i.connectionProperties?.pairingState==="paired").map(i=>({udid:i.hardwareProperties.udid,name:i.hardwareProperties.marketingName??"iPhone",productType:i.hardwareProperties.productType,ios:i.deviceProperties?.osVersionNumber??""}))}finally{await ee.rm(e,{recursive:true,force:true}).catch(()=>void 0)}}function Da(e,t,s){const i=Object.keys(e).find(n=>e[n].productType===t);if(i)return i;const o=n=>n.toLowerCase().replace(/[()]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");return`${o(s)}-${o(t)}`.slice(0,64).replace(/-$/,"")}async function Aa(e,t,s,i){const o=Q.join(e,"PhoneProbe.app");await ee.mkdir(o,{recursive:true});const n=Q.join(e,"PhoneProbe.m");await ee.writeFile(n,Cn);await be("xcrun",["--sdk","iphoneos","clang","-arch","arm64","-miphoneos-version-min=13.0","-fobjc-arc","-O2","-framework","Foundation","-framework","UIKit",n,"-o",Q.join(o,"PhoneProbe")],{timeout:18e4,maxBuffer:8*ft,env:{...process.env,TMPDIR:e}});const a=mt;await ee.writeFile(Q.join(o,"Info.plist"),`<?xml version="1.0" encoding="UTF-8"?>
|
|
368
|
+
`;var Cn="499c4e3de0f8";var xn='// PhoneProbe: a tiny app `farm phone measure` puts on a real iPhone for a few\n// seconds. It prints what the phone reports, then quits:\n// physicalMemory the RAM iOS reports\n// cpus the CPU count\n// footprint this app\'s own footprint right now\n// available os_proc_available_memory(): how much more this app may use\n// footprint + available is the per-app limit (jetsam) on this phone.\n\n#import <UIKit/UIKit.h>\n#import <mach/mach.h>\n#import <os/proc.h>\n\nstatic uint64_t footprintBytes(void) {\n task_vm_info_data_t info;\n mach_msg_type_number_t count = TASK_VM_INFO_COUNT;\n if (task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count) != KERN_SUCCESS) return 0;\n return info.phys_footprint;\n}\n\n// iOS 27 ends apps that don\'t use scenes, so the window comes from a scene delegate.\n@interface ProbeSceneDelegate : UIResponder <UIWindowSceneDelegate>\n@property (nonatomic, strong) UIWindow *window;\n@end\n\n@implementation ProbeSceneDelegate\n- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)options {\n self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];\n self.window.rootViewController = [UIViewController new];\n self.window.rootViewController.view.backgroundColor = UIColor.systemTealColor;\n [self.window makeKeyAndVisible];\n}\n@end\n\n@interface ProbeDelegate : UIResponder <UIApplicationDelegate>\n@end\n\n@implementation ProbeDelegate\n- (UISceneConfiguration *)application:(UIApplication *)app configurationForConnectingSceneSession:(UISceneSession *)session options:(UISceneConnectionOptions *)options {\n UISceneConfiguration *c = [[UISceneConfiguration alloc] initWithName:@"Default" sessionRole:session.role];\n c.delegateClass = ProbeSceneDelegate.class;\n return c;\n}\n- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts {\n NSProcessInfo *pi = NSProcessInfo.processInfo;\n fprintf(stdout, "BUOY_PROBE physicalMemory=%llu cpus=%lu footprint=%llu available=%zu\\n",\n pi.physicalMemory, (unsigned long)pi.processorCount, footprintBytes(), os_proc_available_memory());\n fflush(stdout);\n dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{ exit(0); });\n return YES;\n}\n@end\n\nint main(int argc, char *argv[]) {\n @autoreleasepool {\n return UIApplicationMain(argc, argv, nil, NSStringFromClass(ProbeDelegate.class));\n }\n}\n';var rt=Pn(An);var pi={"iphone-se-2nd-gen":{name:"iPhone SE (2nd generation)",productType:"iPhone12,8",ramMB:2957,cpus:6,limitMB:2098,warnMB:1707,warnSource:"measured (plain memory test); the phone warned at 1,134 MB in a photos test",ios:"26.5",measuredAt:"2026-10-14"}};var ui="iphone-se-2nd-gen";var zt="off";function Z(){return process.env.BUOY_FARM_HOME||K.join(Yt.homedir(),".buoy","farm")}function ct(e=Z()){return K.join(e,"phone")}function hi(e=Z()){return K.join(ct(e),"phones.json")}function mi(e){const t=e;const s=(i,o,n)=>typeof i==="number"&&Number.isInteger(i)&&i>=o&&i<=n;return!!t&&typeof t==="object"&&typeof t.name==="string"&&t.name.length>0&&t.name.length<=100&&typeof t.productType==="string"&&s(t.ramMB,256,1<<20)&&s(t.cpus,1,256)&&s(t.limitMB,64,t.ramMB)&&s(t.warnMB,32,t.limitMB-1)}function Oe(e=Z()){const t={...pi};try{const s=JSON.parse(Rn(hi(e),"utf8"));for(const[i,o]of Object.entries(s?.phones??{})){if(/^[a-z0-9][a-z0-9-]{0,63}$/.test(i)&&mi(o))t[i]=o}}catch{}return t}async function fi(e,t,s=Z()){if(!/^[a-z0-9][a-z0-9-]{0,63}$/.test(e))throw new Error(`Bad phone id ${e}.`);if(!mi(t))throw new Error(`Bad phone numbers for ${e}.`);const i=hi(s);let o={phones:{}};try{o=JSON.parse(await x.readFile(i,"utf8"));if(!o||typeof o.phones!=="object")o={phones:{}}}catch{}o.phones[e]=t;await x.mkdir(K.dirname(i),{recursive:true});const n=`${i}.${process.pid}.tmp`;await x.writeFile(n,JSON.stringify(o,null,2)+"\n");await x.rename(n,i)}function wi(e){return Math.max(3072,e.limitMB+1024)}function gi(e,t,s){const i=t.capMB>e.limitMB?t.capMB:wi(e);return{BUOY_PROFILE_NAME:e.name,BUOY_PROFILE_RAM_MB:String(e.ramMB),BUOY_PROFILE_CPUS:String(e.cpus),BUOY_PROFILE_LIMIT_MB:String(e.limitMB),BUOY_PROFILE_WARN_MB:String(e.warnMB),BUOY_PROFILE_ON_LIMIT:t.onLimit,BUOY_PROFILE_CAP_MB:String(i),BUOY_PROFILE_EVENTS:s}}function Kt(...e){const t=new Set;const s=[];for(const i of e){for(const o of(i??"").split(":")){const n=o.trim();if(n&&!t.has(n)){t.add(n);s.push(n)}}}return s.join(":")}function Ke(e,t=Z()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return K.join(ct(t),`${e}.launch.json`)}function lt(e,t=Z()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return K.join(ct(t),`${e}.events.jsonl`)}async function yi(e,t=Z()){const s=Ke(e.udid,t);await x.mkdir(K.dirname(s),{recursive:true});const i=`${s}.${process.pid}.tmp`;await x.writeFile(i,JSON.stringify(e,null,2)+"\n");await x.rename(i,s)}async function dt(e,t=Z()){await x.rm(Ke(e,t),{force:true})}async function vi(e,t=Z()){try{const s=JSON.parse(await x.readFile(Ke(e,t),"utf8"));if(s?.udid!==e||typeof s.dylib!=="string"||!s.vars||typeof s.vars!=="object")return null;await x.access(s.dylib);return s}catch{return null}}async function Ln(e){try{const{stdout:t}=await rt("xcrun",["simctl","spawn",e,"launchctl","getenv","DYLD_INSERT_LIBRARIES"],{timeout:1e4});return t.trim()}catch{return""}}async function Qt(e,t,s,i=Z(),o=Ln){const n=await vi(e,i);if(!n||n.bundleId!==t){return s?{SIMCTL_CHILD_DYLD_INSERT_LIBRARIES:Kt(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await o(e),s)}:{}}const a={};for(const[r,c]of Object.entries(n.vars))a[`SIMCTL_CHILD_${r}`]=c;a.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES=Kt(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await o(e),s,n.dylib);return a}function Si(e){const t=[];for(const s of e.split("\n")){if(!s.trim())continue;try{const i=JSON.parse(s);if(i&&typeof i.event==="string"&&typeof i.pid==="number")t.push(i)}catch{}}return t}function bi(e){let t=-1;for(let n=e.length-1;n>=0;n--)if(e[n].event==="profile_loaded"){t=n;break}if(t<0)return{loaded:false,ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};const s=e[t];const i=e.slice(t).filter(n=>n.pid===s.pid);const o={pid:s.pid,loaded:true,ignored:Array.isArray(s.ignored)?s.ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};for(const n of i){o.peakMB=Math.max(o.peakMB,n.footprintMB??0,typeof n.peakMB==="number"?n.peakMB:0);o.lastFootprintMB=n.footprintMB;if(n.event==="memory_warning")o.memoryWarnings++;if(n.event==="over_limit")o.overLimit=true;if(n.event==="jetsam_kill"){o.overLimit=true;o.ended="limit"}if(n.event==="safety_kill"){o.overLimit=true;o.ended="safety"}}return o}async function pt(e,t=Z()){const s=lt(e,t);try{const i=await x.open(s,"r");try{const{size:o}=await i.stat();const n=Math.min(o,256*1024);const a=Buffer.alloc(n);await i.read(a,0,n,o-n);let r=a.toString("utf8");if(n<o)r=r.slice(r.indexOf("\n")+1);return bi(Si(r))}finally{await i.close()}}catch{return null}}async function Un(){try{const{stdout:e}=await rt("xcodebuild",["-version"],{timeout:3e4});return e.trim().split("\n").pop().replace(/^Build version\s*/,"").replace(/[^\w.-]/g,"")||"unknown"}catch{return"unknown"}}async function ki(e){const t=K.join(e,"BuoyPhoneProfile.dylib");try{await x.access(t);return t}catch{}const s=await x.mkdtemp(K.join(Yt.tmpdir(),"buoy-phone-profile-"));try{const i=K.join(s,"BuoyPhoneProfile.m");await x.writeFile(i,Nn);const o=K.join(s,"BuoyPhoneProfile.dylib");await rt("xcrun",["--sdk","iphonesimulator","clang","-arch","arm64","-arch","x86_64","-mios-simulator-version-min=13.0","-fobjc-arc","-O2","-Wall","-Werror","-dynamiclib","-framework","Foundation","-install_name","@rpath/BuoyPhoneProfile.dylib",i,"-o",o],{timeout:18e4,maxBuffer:8*1024*1024,env:{...process.env,TMPDIR:s}});await rt("codesign",["-f","-s","-",o],{timeout:6e4});await x.mkdir(e,{recursive:true});const n=`${t}.${process.pid}`;await x.copyFile(o,n);await x.rename(n,t);return t}finally{await x.rm(s,{recursive:true,force:true}).catch(()=>void 0)}}var Mi=class{constructor(e=K.join(Yt.homedir(),".buoy","phone-profile")){this.root=e;this.built=null;this.key="";this.error="";this.failedAt=0}dylib(){if(process.platform!=="darwin"){this.error="Phone profiles need a Mac.";return Promise.resolve(null)}const e=process.env.DEVELOPER_DIR??"";if(this.built&&this.key===e)return this.built;if(this.key===e&&this.failedAt&&Date.now()-this.failedAt<5*6e4)return Promise.resolve(null);if(this.key!==e)this.failedAt=0;this.key=e;this.error="";this.built=Un().then(t=>ki(K.join(this.root,`${t}-${Cn}`))).catch(t=>{const s=t;this.error=(s.stderr||s.message||"build failed").trim().split("\n").slice(-3).join(" ").slice(0,300);this.built=null;this.failedAt=Date.now();return null});return this.built}lastError(){return this.error}};var Te=1024**3;var Vt="Buoy Farm ";var Jt=".buoy-farm.json";var jn=120;var Fn=180;var Xt=9e4;var _i=500;var Gn=10*6e4;var Wn=7*24*36e5;var Ei=200;var Hn=3;var ae=["done","failed","stopped","interrupted"];var $i=["setting-up","working","waiting","passed","failed","needs-help"];var qn={now:()=>performance.now()};var Yn=10*6e4;function re(e){return Math.round((e.phone.ramMB/1024+En)*10)/10}var Ii=class{constructor(){this.tails=new Map}run(e,t){const s=this.tails.get(e)??Promise.resolve();const i=s.catch(()=>{}).then(t);this.tails.set(e,i);i.finally(()=>{if(this.tails.get(e)===i)this.tails.delete(e)}).catch(()=>{});return i}};var zn=class{constructor(){this.counts=new Map}get size(){return this.counts.size}has(e){return this.counts.has(e)}add(e){this.counts.set(e,(this.counts.get(e)??0)+1)}delete(e){const t=(this.counts.get(e)??0)-1;if(t>0)this.counts.set(e,t);else this.counts.delete(e)}};var Kn=class{constructor(e){this.free=e;this.waiting=[]}async use(e){if(this.free>0)this.free--;else await new Promise(t=>this.waiting.push(t));try{return await e()}finally{const t=this.waiting.shift();if(t)t();else this.free++}}};var Bi=class{constructor(e){this.phoneFailed=new Map;this.simLock=new Ii;this.deadlines=new Map;this.reservations=new Map;this.busy=new zn;this.ticking=false;this.lastTrimAt=0;this.hardDeadlines=new Map;this.acquiring=new Set;this.budgetLock=new Ii;this.labels=new Map;this.copyMemAt=new Map;this.inflight=new Map;this.workContext=new Mo;this.activeWork=0;this.lockQueues=new Map;this.fileTasks=new Set;this.checkActive=()=>this.store.assertActive();this.shotsInFlight=0;this.pendingBg=0;this.host=new Proxy(e.host,{get:(s,i)=>{const o=Reflect.get(s,i);if(typeof o!=="function")return o;return async(...n)=>{this.store.assertActive();const a=await o.apply(s,n);this.store.assertActive();return a}}});this.store=e.store;this.clock=e.clock??qn;this.log=e.log??(()=>{});this.pollMs=e.pollMs??1e3;this.readyTimeoutMs=e.readyTimeoutMs??12e4;this.phoneBuilder=e.phoneBuilder??new Mi;this.phoneLoadTimeoutMs=e.phoneLoadTimeoutMs??1e4;this.bootGate=new Kn(this.state.settings.maxConcurrentBoots);this.android=e.android??new li({home:this.store.homeDir});const t={loadCounters:(s,i)=>this.host.loadCounters(s,i),simTree:async s=>{const i=this.copies[s];if(!i)return this.host.simTree(s);return(await this.android.treePids(i.pid)).map(o=>({pid:o,ppid:0,start:"",stat:"",comm:"",command:""}))}};this.meter=new Cs(t,()=>{const s=this.state.locks["perf-slot"];const i=s&&this.state.leases[s.leaseId];return i?{leaseId:i.id,udids:i.sims}:null})}async createTemplate(e){return this.work(void 0,()=>this.createTemplateWork(e))}async cloneSim(e,t){return this.work(void 0,()=>this.cloneSimWork(e,t))}async bootSim(e,t){return this.work(t,()=>this.bootSimWork(e,t))}async parkSim(e,t="idle"){return this.work(void 0,()=>this.parkSimWork(e,t))}async deleteSim(e,t){return this.work(t,()=>this.deleteSimWork(e,t))}async rebuildSim(e,t){return this.work(void 0,()=>this.simLock.run(`rebuild:${e}`,()=>this.rebuildSimWork(e,t)))}async fastReset(e,t){return this.work(void 0,()=>this.fastResetWork(e,t))}async claimOne(e,t){return this.work(e.id,()=>this.claimOneWork(e,t))}async action(e,t,s,i,o,n){this.touch(this.liveLease(e,t,n));const a=this.state.locks["perf-slot"];if(i==="screenshot"&&a&&a.leaseId!==e)throw new p("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);return this.work(i==="reset"||i==="rebuild"?void 0:e,()=>this.actionWork(e,t,s,i,o,n))}async work(e,t){this.store.assertActive();if(this.workContext.getStore()?.active)return t();while(this.state.locks["perf-slot"]&&this.state.locks["perf-slot"].leaseId!==e){await T(this.pollMs);this.store.assertActive()}const s={active:true,leaseId:e};this.activeWork++;try{return await this.workContext.run(s,t)}finally{s.active=false;this.activeWork--}}async files(e){this.checkActive();const t=e();this.fileTasks.add(t);try{const s=await t;this.checkActive();return s}finally{this.fileTasks.delete(t)}}fence(){this.store.fence()}async settleFiles(){await Promise.allSettled([...this.fileTasks])}get state(){return this.store.state}async recover(){for(const[o,n]of Object.entries(this.state.locks)){if(!this.state.leases[n.leaseId])delete this.state.locks[o]}const e=this.store.pending.filter(o=>o.op==="copy-start"||o.op==="copy-stop").map(({seq:o,op:n,data:a})=>({seq:o,op:n,data:a}));let t;try{t=await this.host.listDevices()}catch(o){if(Object.keys(this.state.sims).length||Object.keys(this.state.templates).length)this.log(`recover: iOS sims not checked: ${o.message}`);for(const n of this.store.pending)this.store.result(n.seq,n.op,{recovered:true});await this.recoverLeases();await this.recoverAndroid(e);this.store.save();return}const s=new Map(t.map(o=>[o.udid,o]));for(const o of Object.values(this.state.sims)){const n=s.get(o.udid);if(!n){this.store.event({kind:"lost",message:`${o.name} was deleted outside the farm.`,udid:o.udid});if(o.leaseId)this.dropFromLease(o.leaseId,o.udid);await this.files(()=>this.forgetPhone(o.udid)).catch(()=>void 0);delete this.state.sims[o.udid];continue}if(n.state==="Booted"){o.power=o.power==="frozen"?"frozen":"awake";if(o.power!=="frozen"){const a=this.state.templates[o.templateId];const r=await this.health(o.udid,a?Ee(a.profile):[]);o.profile=r.profile;o.lastCheckAt=new Date().toISOString();if(!r.ok||a?.profile==="slim"&&r.profile==="unknown"){o.health="suspect";o.lastError=r.faults.join("; ")||"Slim state is not known."}}}else{o.power=o.power==="parked"?"parked":"cold"}if(["creating","booting","thawing","deleting"].includes(o.power))o.power=n.state==="Booted"?"awake":"cold"}for(const o of Object.values(this.state.templates)){const n=s.get(o.udid);if(!n){o.usable=false;o.note="Its sim was deleted outside the farm."}else if(!$t(o.appPath)){o.note="Its app build is gone. Clones still work; rebuild the template for a new build."}}for(const o of Object.keys(this.state.orphans)){if(!s.has(o))delete this.state.orphans[o]}for(const o of this.store.pending){if(o.op==="clone"){const n=o.data?.name;const a=t.find(r=>r.name===n&&!this.state.sims[r.udid]);if(a){this.state.orphans[a.udid]={udid:a.udid,name:a.name,cloneIntentName:n};this.store.event({kind:"orphan",message:`Found ${a.name} with no record after a crash. Held for review. Use farm delete to remove it.`,udid:a.udid})}}if(o.op!=="rebuild")this.store.result(o.seq,o.op,{recovered:true})}const i=new Set;for(const o of[...this.store.pending]){if(o.op!=="rebuild")continue;const{udid:n,name:a,templateId:r}=o.data;if(Object.values(this.state.orphans).some(c=>c.name===a))continue;i.add(n);this.busy.add(n);this.background(`resume rebuild ${a}`,()=>this.work(void 0,async()=>{try{const c=Object.values(this.state.sims).find(l=>l.name===a&&l.udid!==n);if(!c){if(this.state.sims[n]){const l=this.state.sims[n];if(l.leaseId)this.dropFromLease(l.leaseId,n);l.leaseId=void 0;await this.deleteSim(n)}await this.cloneSim(r,a)}this.store.result(o.seq,"rebuild",{recovered:true})}finally{this.busy.delete(n)}}))}await this.recoverLeases();await this.recoverAndroid(e);for(const o of Object.values(this.state.sims)){if(o.health==="dirty"&&!o.leaseId&&!i.has(o.udid)){this.busy.add(o.udid);this.background(`resume reset ${o.name}`,async()=>{try{await this.rebuildSim(o.udid)}finally{this.busy.delete(o.udid)}})}}this.store.save()}async recoverLeases(){for(const e of Object.values(this.state.leases)){this.deadlines.set(e.id,this.clock.now()+Xt);const t=Date.parse(e.createdAt)+e.ttlMs-Date.now();this.hardDeadlines.set(e.id,this.clock.now()+Math.max(t,0));if(t<=0)this.background(`expire ${e.id}`,()=>this.endLease(e.id,"ran out of time while the farm was down","full","deadline"))}}async dataPath(e){const t=await this.host.dataPathOf?.(e);if(t)return t;const s=(await this.host.listDevices()).find(i=>i.udid===e);if(!s)throw new p("NOT_FOUND",`No sim ${e} on this Mac.`);return s.dataPath}async isOwned(e){const t=this.state.sims[e]??Object.values(this.state.templates).find(i=>i.udid===e);if(!t)return false;let s;try{s=await this.dataPath(e)}catch(i){if(i instanceof p&&i.code==="NOT_FOUND")return false;throw i}try{const i=JSON.parse(await q.readFile(C.join(s,Jt),"utf8"));return i.farmId===this.state.farmId}catch{return false}}async assertOwned(e){if(!await this.isOwned(e)){throw new p("NOT_OWNED",`Sim ${e} is not a farm sim. The farm never changes sims it did not make.`)}}async writeMarker(e,t){const s=await this.dataPath(e);await this.files(async()=>{await q.mkdir(s,{recursive:true});this.checkActive();await q.writeFile(C.join(s,Jt),JSON.stringify({farmId:this.state.farmId,...t})+"\n")})}farmAwakeGB(){const e=this.state.settings;let t=0;for(const s of Object.values(this.state.sims)){if(["awake","booting","frozen","thawing"].includes(s.power))t+=s.profile==="slim"?e.slimSimGB:e.stockSimGB}for(const s of Object.values(this.copies)){const i=this.androidTemplates[s.templateId];t+=i?re(i):4.3}return t}async budget(e={ramGB:0,diskGB:0}){const t=this.state.settings;const s=await this.host.hostStats();let i=0;let o=0;for(const w of this.reservations.values()){i+=w.ramGB;o+=w.diskGB}for(const w of Object.values(this.copies)){const v=this.androidTemplates[w.templateId];if(v&&(w.state==="starting"||Date.now()-Date.parse(w.startedAt)<6e4))i+=re(v)}const n=s.totalMemBytes/Te;const a=Vn(n)*(s.memPressure==="warn"?2:1);const r=s.availableMemBytes/Te-i;const c=n-Qn(n,t.reserveRamGB)-this.farmAwakeGB()-i;const l=s.freeDiskBytes/Te-o-t.reserveDiskGB;const d=Math.max(0,Math.floor(Math.min(r-a,c,l/t.cloneDiskGB*t.slimSimGB)/t.slimSimGB));const h=(w,v)=>({ok:false,code:w,reason:v,stats:s,roomFor:0});if(s.memPressure==="critical")return h("RAM_LOW","This Mac is low on memory. New sims will wait.");if(e.diskGB>0&&l-e.diskGB<0){return h("DISK_LOW",`Need ${t.reserveDiskGB} GB free after this. This Mac has ${Math.floor(s.freeDiskBytes/Te)} GB.`)}if(e.ramGB>0){if(r-e.ramGB<a||c-e.ramGB<0){return h("RAM_LOW",`Not enough free memory for another sim (${Math.max(0,Math.floor(r))} GB free).`)}}const f=this.state.locks["perf-slot"];if(f&&(!this.workContext.getStore()?.active||this.workContext.getStore()?.leaseId!==f.leaseId))return h("HOST_BUSY","A speed test is running. New sims will wait.");return{ok:true,stats:s,roomFor:d,ramRoomGB:Math.max(0,Math.min(r-a,c))}}async withReservation(e,t){const s=we(4).toString("hex");await this.budgetLock.run("budget",async()=>{const i=await this.budget(e);if(!i.ok)throw new p(i.code,i.reason,void 0,true);this.reservations.set(s,e)});try{return await t()}finally{this.reservations.delete(s)}}async health(e,t=[]){const s=[];let i;try{i=await this.host.simTree(e)}catch(r){this.checkActive();return{ok:false,profile:"unknown",faults:[`Could not read sim processes: ${r.message}`],procs:0}}if(i.length===0)return{ok:false,profile:"unknown",faults:["launchd_sim is not running"],procs:0};const o=new Set(i.map(r=>r.comm.split("/").pop()));for(const r of["SpringBoard","backboardd"])if(!o.has(r))s.push(`${r} is not running`);const n=Oi(await this.host.launchdLogTail(e));if(n)s.push(`${n} keeps crashing`);if(s.length===0){const r=await this.host.spawn(e,["launchctl","getenv","OS_ACTIVITY_MODE"],1e4);if(r.code===0&&r.stdout.trim())s.push(`OS_ACTIVITY_MODE=${r.stdout.trim()} is set for the whole sim (it was booted with SIMCTL_CHILD_OS_ACTIVITY_MODE)`)}if(s.length===0){const r=C.join(this.store.shotsPath,`${e}.png`);await this.files(()=>q.mkdir(C.dirname(r),{recursive:true}));if(!await this.host.screenshot(e,r,1e4))s.push("screenshot did not come back")}let a=i.length<jn?"slim":i.length>Fn?"stock":"unknown";if(s.length===0&&t.length){const r=await this.host.spawn(e,["launchctl","print-disabled","system"],2e4);if(r.code===0&&!r.timedOut){const c=new Set([...r.stdout.matchAll(/"([^"]+)" => (?:disabled|true)/g)].map(d=>d[1]));const l=t.filter(d=>!c.has(d)).length;a=l===0?"slim":l===t.length?"stock":"unknown"}else{a="unknown"}}return{ok:s.length===0,profile:a,faults:s,procs:i.length}}async waitHealthy(e,t=[]){const s=performance.now()+this.readyTimeoutMs;let i=await this.health(e,t);while(!i.ok&&performance.now()<s){await T(this.pollMs);i=await this.health(e,t)}return i}async createTemplateWork(e){if(!["slim","stock"].includes(e.profile)||!Number.isInteger(e.metroPort)||e.metroPort<1||e.metroPort>65535){throw new p("BAD_REQUEST","Use slim or stock and a port from 1 to 65535.")}const t=e.seedDir==null?null:Le(e.seedDir);if(t)throw new p("NO_SEED",t,"Run: pnpm e2e:pool seed");if(!$t(C.join(e.appPath,"Info.plist"))){throw new p("BAD_REQUEST",`No app build at ${e.appPath}.`,"Build the app first.")}const s=await No(e.appPath);const i=`t-${we(3).toString("hex")}`;const o=e.name??`${Vt}Template ${i.slice(2)}`;return this.withReservation({ramGB:this.state.settings.stockSimGB,diskGB:4},async()=>{const n=this.store.intent("create-template",{id:i,name:o});const a=await this.host.create(o,e.deviceType,e.runtime);const r={id:i,udid:a,name:o,bundleId:s,appPath:e.appPath,appHash:await Ro(e.appPath),seedDir:e.seedDir??null,seedHash:e.seedDir?await Ao(e.seedDir):null,launchUrl:e.launchUrl,deviceType:e.deviceType,runtime:e.runtime,xcodeBuild:await this.host.xcodeBuild(),profile:e.profile,profileHash:_s(e.profile),metroPort:e.metroPort,createdAt:new Date().toISOString(),usable:false,note:"Being built."};this.state.templates[i]=r;await this.writeMarker(a,{template:i});this.store.result(n,"create-template",{udid:a});try{await this.buildTemplate(r,e.warmLaunch!==false);r.usable=true;r.note=void 0;this.store.event({kind:"template",message:`Template ${o} is ready.`,udid:a})}catch(c){r.note=`Build failed: ${c.message}`;this.store.event({kind:"template-failed",message:r.note,udid:a});await this.host.shutdown(a).catch(()=>{});throw c}finally{this.store.save()}return r})}async buildTemplate(e,t){const s=Ee(e.profile);if(s.length)await this.files(()=>Bs(e.udid,s,this.host.disabledRoot));await this.host.boot(e.udid);const i=await this.waitHealthy(e.udid,s);if(!i.ok)throw new Error(`template did not come up: ${i.faults.join("; ")}`);if(e.profile==="slim"&&i.profile!=="slim")throw new Error(`slim did not apply (${i.procs} processes)`);await this.host.install(e.udid,e.appPath);const o=await this.host.appContainer(e.udid,e.bundleId);if(!o)throw new Error("app installed but has no data container");if(e.seedDir)await this.files(()=>Rs(o,e.bundleId,e.seedDir,this.checkActive));else await this.files(()=>ot(o,this.checkActive));await this.setPrefs(e.udid,e);if(t){await this.launchApp(e.udid,e,{phone:false});await T(Math.min(15e3,this.pollMs*15));await this.host.terminate(e.udid,e.bundleId)}await this.host.shutdown(e.udid);await this.files(()=>ot(o,this.checkActive))}async launchApp(e,t,s={}){if(s.phone===false){await this.host.launch(e,t.bundleId)}else{await this.launchAsPhone(e,t.bundleId)}if(t.launchUrl)await this.host.openUrl(e,t.launchUrl)}async launchAsPhone(e,t){const s=this.store.homeDir;const i=this.state.settings;const o=b=>{const E=this.state.sims[e];if(E)E.phone=b};const n=async b=>{const E=await this.host.spawn(b,["launchctl","getenv","DYLD_INSERT_LIBRARIES"],1e4).catch(()=>null);return E&&E.code===0?E.stdout.trim():""};const a=async b=>{await this.files(()=>dt(e,s));const E=await Qt(e,t,void 0,s,n);await this.host.launch(e,t,E);o(b)};if(i.phoneProfile===zt)return a({status:"off"});const r=Oe(s);const c=r[i.phoneProfile]?i.phoneProfile:ui;const l=r[c];const d=c!==i.phoneProfile?`No phone "${i.phoneProfile}" on this Mac, so it uses ${l.name}. `:"";const h=this.phoneFailed.get(t);if(h!==void 0&&performance.now()-h<Yn){return a({status:"fallback",phoneId:c,phoneName:l.name,note:`${d}This app did not start with the phone profile a moment ago, so it runs as a plain simulator app. The farm tries again in a few minutes.`})}const f=await this.phoneBuilder.dylib();if(!f){const b=this.phoneBuilder.lastError()||"unknown error";this.log(`phone profile: could not build the library: ${b}`);return a({status:"fallback",phoneId:c,phoneName:l.name,note:`${d}The phone profile could not be built (${b}), so the app runs as a plain simulator app.`})}const w=lt(e,s);await this.files(async()=>{await q.mkdir(C.dirname(w),{recursive:true});await q.writeFile(w,"");await yi({udid:e,bundleId:t,dylib:f,phoneId:c,vars:gi(l,{phone:c,onLimit:i.phoneOnLimit,capMB:i.phoneCapMB},w),writtenAt:new Date().toISOString()},s)});const v=await Qt(e,t,void 0,s,n);await this.host.launch(e,t,v);const k=performance.now()+this.phoneLoadTimeoutMs;let y=null;for(;;){y=await pt(e,s);if(y?.loaded||performance.now()>=k)break;await T(Math.min(250,Math.max(10,this.pollMs/4)))}if(y?.loaded){this.phoneFailed.delete(t);const b=y.ignored.length?` Ignored bad settings: ${y.ignored.join(", ")}.`:"";o({status:"on",phoneId:c,phoneName:l.name,pid:y.pid,note:(d+b).trim()||void 0});return}this.log(`phone profile: ${t} on ${e} did not report it; launching without it`);this.phoneFailed.set(t,performance.now());await this.host.terminate(e,t);await a({status:"fallback",phoneId:c,phoneName:l.name,note:`${d}The app did not start with the phone profile, so it runs as a plain simulator app.`})}async setPrefs(e,t){const s=["defaults","write","com.apple.keyboard.preferences"];await this.host.spawn(e,[...s,"AutomaticMinimizationEnabled","-bool","true"]);await this.host.spawn(e,[...s,"DidShowContinuousPathIntroduction","-bool","true"]);await this.host.spawn(e,["defaults","write",t.bundleId,"RCT_jsLocation",`localhost:${t.metroPort}`])}async nextName(){const e=await this.host.listDevices();const t=new Set([...e.map(i=>i.name),...Object.values(this.state.sims).map(i=>i.name)]);let s=this.state.nextIndex;while(t.has(`${Vt}${String(s).padStart(2,"0")}`))s++;this.state.nextIndex=s+1;return`${Vt}${String(s).padStart(2,"0")}`}template(e){const t=this.state.templates[e];if(!t)throw new p("NO_TEMPLATE",`No template ${e}.`,"Run sim_farm_setup to make one.");if(!t.usable)throw new p("NO_TEMPLATE",`Template ${t.name} can't be used: ${t.note??"not ready"}.`);return t}async cloneSimWork(e,t,s){t??=await this.nextName();const i=t;const o=this.template(e);return this.withReservation({ramGB:0,diskGB:this.state.settings.cloneDiskGB},()=>this.simLock.run(`template:${o.id}`,async()=>{await this.assertOwned(o.udid);const n=this.store.intent("clone",{templateId:e,name:i});const a=await this.host.clone(o.udid,i);const r={udid:a,name:i,templateId:e,power:"creating",profile:"unknown",health:"ok",bootEpoch:0,failures:0,createdAt:new Date().toISOString()};this.state.sims[a]=r;this.store.result(n,"clone",{udid:a});await this.writeMarker(a,{template:o.id});const c=await it(await this.dataPath(a),o.bundleId);if(c)await this.files(()=>ot(c,this.checkActive));const l=s?this.state.leases[s.leaseId]:void 0;if(l&&!this.leaseOverdue(l)){r.leaseId=l.id;l.sims=l.sims.map(d=>d===s.udid?a:d)}r.power="cold";this.store.event({kind:"clone",message:`Made ${t}.`,udid:a});this.store.save();return r}))}async bootSimWork(e,t){await this.assertOwned(e);const s=this.sim(e);if(s.power==="frozen"||s.power==="thawing"){throw new p("BAD_REQUEST",`${s.name} is frozen. Thaw it first.`)}const i=this.state.templates[s.templateId];const o=this.state.settings;const n={ramGB:i?.profile==="slim"?o.slimSimGB:o.stockSimGB,diskGB:0};return this.withReservation(n,()=>this.bootGate.use(()=>this.simLock.run(e,async()=>{this.assertLeaseState(s,t);if(s.power==="awake")return s;const a=i?Ee(i.profile):[];if(a.length&&!await To(e,a,this.host.disabledRoot)){await this.files(()=>Bs(e,a,this.host.disabledRoot))}s.power="booting";s.bootEpoch++;this.store.save();await this.host.boot(e);const r=await this.waitHealthy(e,i?.profile==="slim"?a:[]);s.lastCheckAt=new Date().toISOString();s.profile=r.profile;s.power="awake";if(!r.ok){s.failures++;s.health=s.failures>=2?"broken":"suspect";s.lastError=r.faults.join("; ");this.store.event({kind:"unhealthy",message:`${s.name}: ${s.lastError}`,udid:e});this.store.save();throw new p("HOST_ERROR",`${s.name} did not come up: ${s.lastError}`,"The farm will rebuild it.",true)}s.failures=0;if(s.health==="suspect")s.health="ok";s.idleSince=new Date().toISOString();if(i?.profile==="slim"&&r.profile==="unknown"){s.health="suspect";s.lastError="can't tell if slim applied";this.store.event({kind:"unknown-profile",message:`${s.name}: can't tell if slim worked. Held for now.`,udid:e});this.store.save();throw new p("HOST_ERROR",`${s.name}: can't tell if slim applied.`,"The farm will rebuild it.",true)}if(i?.profile==="slim"&&r.profile==="stock"){this.store.event({kind:"stock",message:`${s.name}: slim didn't work here (${r.profile}). It uses full RAM.`,udid:e});const c=await this.budget({ramGB:o.stockSimGB-o.slimSimGB,diskGB:0});if(!c.ok){s.health="suspect";s.lastError="slim did not apply and there is no RAM for a full sim";await this.host.shutdown(e).catch(()=>{});s.power="parked";this.store.save();throw new p("RAM_LOW",`${s.name}: slim didn't apply and there's no room for a full sim.`,void 0,true)}}this.store.save();return s})))}assertLeaseState(e,t){if(!this.state.sims[e.udid])throw new p("NOT_FOUND",`${e.name} is gone.`);if(t){const s=this.state.leases[t];if(e.leaseId!==t||!s||this.leaseOverdue(s)){throw new p("LEASE_EXPIRED",`${e.name} is no longer in lease ${t}.`)}}else if(e.leaseId){throw new p("BAD_REQUEST",`${e.name} is in use. End its lease first.`)}}async parkSimWork(e,t="idle"){await this.assertOwned(e);const s=this.sim(e);await this.simLock.run(e,async()=>{this.assertLeaseState(s);if(this.busy.has(e))throw new p("BAD_REQUEST",`${s.name} is being reset.`);if(s.power==="frozen"||s.power==="thawing")throw new p("BAD_REQUEST",`${s.name} is frozen. Wake it before parking.`);if(s.power!=="awake")return;this.busy.add(e);try{await this.host.shutdown(e);s.power="parked"}finally{this.busy.delete(e)}this.store.event({kind:"park",message:`Parked ${s.name} (${t}).`,udid:e});this.store.save()})}sim(e){const t=this.state.sims[e];if(!t)throw new p("NOT_OWNED",`Sim ${e} is not a farm sim.`);return t}async deleteSimWork(e,t){const s=this.state.orphans[e];if(s){await this.simLock.run(e,async()=>{const o=(await this.host.listDevices()).find(r=>r.udid===e);if(!o)throw new p("NOT_FOUND","This sim is gone.");let n=false;try{const r=JSON.parse(await q.readFile(C.join(o.dataPath,Jt),"utf8"));n=r.farmId===this.state.farmId}catch(r){n=r.code==="ENOENT"&&o.name===s.cloneIntentName}if(!n)throw new p("NOT_OWNED","This sim has no valid farm mark.");const a=this.store.intent("delete",{udid:e});await this.host.shutdown(e);await this.host.delete(e);await this.files(()=>q.rm(C.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`),{recursive:true,force:true}));await this.files(()=>this.forgetPhone(e));delete this.state.orphans[e];this.store.result(a,"delete",{})});return}await this.assertOwned(e);const i=this.sim(e);await this.simLock.run(e,async()=>{this.assertLeaseState(i,t);if(i.power==="frozen"||i.power==="thawing")throw new p("BAD_REQUEST",`${i.name} is frozen. Wake it first.`);i.power="deleting";const o=this.store.intent("delete",{udid:e});await this.host.shutdown(e).catch(()=>{});await this.host.delete(e);await this.files(()=>q.rm(C.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`),{recursive:true,force:true}));await this.files(()=>this.forgetPhone(e));delete this.state.sims[e];this.store.result(o,"delete",{});this.store.event({kind:"delete",message:`Deleted ${i.name}.`,udid:e})})}async rebuildSimWork(e,t){const s=this.sim(e);const{name:i,templateId:o}=s;await this.assertOwned(e);await this.simLock.run(e,async()=>{this.assertLeaseState(s,t);this.busy.add(e)});const n=this.store.intent("rebuild",{udid:e,name:i,templateId:o});try{await this.deleteSim(e,t);const a=await this.cloneSimWork(o,i,t?{udid:e,leaseId:t}:void 0);this.store.result(n,"rebuild",{udid:a.udid});return a}finally{this.busy.delete(e)}}async fastResetWork(e,t){await this.assertOwned(e);const s=this.sim(e);const i=this.template(s.templateId);await this.simLock.run(e,async()=>{this.assertLeaseState(s,t);if(s.power==="frozen"||s.power==="thawing")throw new p("BAD_REQUEST",`${s.name} is frozen. Wake it first.`);this.busy.add(e);try{if(s.power==="awake")await this.host.terminate(e,i.bundleId);if(!t&&s.power==="awake"&&this.host.installedApp){const n=await this.host.installedApp(e,i.bundleId);if(n&&!await Po(n,i.appPath))await this.host.install(e,i.appPath)}const o=await it(await this.dataPath(e),i.bundleId);if(!o)throw new Error("app container not found");if(i.seedDir)await this.files(()=>Rs(o,i.bundleId,i.seedDir,this.checkActive));else await this.files(()=>ot(o,this.checkActive));s.health="ok";this.store.save()}finally{this.busy.delete(e)}})}async grow(e,t){if(!Number.isInteger(t)||t<1||t>64)throw new p("BAD_REQUEST","count must be 1 to 64.");const s=[];for(let i=0;i<t;i++)s.push(await this.cloneSim(e));return s}async shrink(e,t){if(!Number.isInteger(t)||t<1||t>64)throw new p("BAD_REQUEST","count must be 1 to 64.");const s=["cold","parked","awake"];const i=Object.values(this.state.sims).filter(n=>n.templateId===e&&!n.leaseId&&s.includes(n.power)&&!this.busy.has(n.udid)).sort((n,a)=>s.indexOf(n.power)-s.indexOf(a.power));const o=[];for(const n of i.slice(0,t)){try{await this.deleteSim(n.udid);o.push(n.name)}catch(a){if(!(a instanceof p))throw a}}return o}async removeTemplate(e){if(this.androidTemplates[e])return this.work(void 0,()=>this.removeAndroidTemplate(e));return this.work(void 0,async()=>{const t=this.state.templates[e];if(!t)throw new p("NO_TEMPLATE",`No template ${e}.`);const s=Object.values(this.state.sims).filter(a=>a.templateId===e);const i=s.filter(a=>a.leaseId||this.busy.has(a.udid));if(i.length){throw new p("HOST_BUSY",`${i.map(a=>a.name).join(", ")} ${i.length===1?"is":"are"} in use.`,"End those uses first.",true)}const o=[];for(const a of s){await this.deleteSim(a.udid);o.push(a.name)}const n=(await this.host.listDevices()).some(a=>a.udid===t.udid);if(n){await this.assertOwned(t.udid);await this.simLock.run(t.udid,async()=>{const a=this.store.intent("delete",{udid:t.udid});await this.host.shutdown(t.udid).catch(()=>{});await this.host.delete(t.udid);await this.files(()=>q.rm(C.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${t.udid}`),{recursive:true,force:true}));this.store.result(a,"delete",{})})}delete this.state.templates[e];o.push(t.name);this.store.event({kind:"delete",message:`Removed template ${t.name} and its ${s.length} sim(s).`});return{removed:o}})}leaseOverdue(e){const t=this.clock.now();if(t>(this.hardDeadlines.get(e.id)??Infinity))return true;if(e.owner.kind==="person")return false;return!this.acquiring.has(e.id)&&t>(this.deadlines.get(e.id)??0)}leaseDeadline(e){return Math.min(this.deadlines.get(e.id)??0,this.hardDeadlines.get(e.id)??Infinity)}async acquire(e){if(e.ttlMs!==void 0&&(typeof e.ttlMs!=="number"||!Number.isFinite(e.ttlMs)||e.ttlMs<=0)){throw new p("BAD_REQUEST","ttlMs must be a finite number above zero.")}const t=this.androidTemplates[e.templateId];if(t&&this.android.stale(t)){throw new p("NO_TEMPLATE",`Android template ${t.id} can't start copies: ${this.android.stale(t)}`,`Make a new one: farm android template --apk ${t.apk}`)}const s=t?void 0:this.template(e.templateId);const i=t??s;if(!Number.isInteger(e.count)||e.count<1||e.count>64)throw new p("BAD_REQUEST","count must be 1 to 64.");const o=e.owner.kind==="person"?this.state.settings.personLeaseHours*36e5:24*36e5;const n=Math.min(Math.max(e.ttlMs??15*6e4,3e4),o);const a=e.runId?this.ownRun(e.runId,e.owner.clientId):this.newRun(e,i.id,n);const r=Date.parse(a.deadlineAt)-Date.now();if(r<3e4)throw new p("RUN_ENDED","This run is almost out of time.","Ask for more with sim_farm_extend, or finish it and start a new run.");const c={id:`l-${we(4).toString("hex")}`,epoch:1,owner:e.owner,purpose:e.purpose,runId:a.id,templateId:i.id,sims:[],createdAt:new Date().toISOString(),ttlMs:Math.min(n,r)};a.leases.push(c.id);a.lastActivityAt=c.createdAt;if(e.labels?.length)this.labels.set(c.id,e.labels);this.state.leases[c.id]=c;this.hardDeadlines.set(c.id,this.clock.now()+c.ttlMs);this.acquiring.add(c.id);const l=()=>this.deadlines.set(c.id,this.clock.now()+Math.min(c.ttlMs,Xt));l();const d=performance.now()+(e.waitMs??0);const h=()=>{if(!this.state.leases[c.id])throw new p("LEASE_EXPIRED","The lease ended while sims were being prepared.");if(this.leaseOverdue(c))throw new p("LEASE_EXPIRED","The lease ran out of time while sims were being prepared.")};try{while(c.sims.length<e.count){h();l();const f=t?await this.claimCopy(c,t):await this.claimOne(c,s);h();if(f)continue;if(performance.now()>=d)break;if(!c.sims.length&&a.state==="active"&&!Object.keys(a.lanes).length)a.state="waiting";await T(Math.max(this.pollMs,2e3))}if(c.sims.length<e.count&&!e.allowPartial){const f=await this.budget({ramGB:t?re(t):this.state.settings.slimSimGB,diskGB:0});throw new p(f.code??"HOST_BUSY",`Got ${c.sims.length} of ${e.count} ${t?"copies":"sims"}. ${f.reason??"Others are in use."}`,"Try again later or ask for fewer.",true)}if(c.sims.length===0)throw new p("HOST_BUSY","No sim is free right now.","Try again later.",true);if(e.launch)await this.work(c.id,async()=>{h();if(t)await Promise.all(c.sims.map(f=>this.launchCopy(f)));else await Promise.all(c.sims.map(f=>this.launchApp(f,s)));l();if(t)await this.checkCopyIdentities(c,6e4);else await this.checkIdentities(c,6e4);h()});l();this.store.event({kind:"lease",message:`${e.owner.label} took ${c.sims.length} sim(s) for ${a.goal}.`,leaseId:c.id,runId:a.id});this.note(a,"farm",`Got ${c.sims.length} sim${c.sims.length===1?"":"s"}.`);this.store.save();return{lease:c,run:a,sims:c.sims.map(f=>this.state.sims[f]??this.copyView(this.copies[f]))}}catch(f){if(this.state.leases[c.id])this.note(a,"farm",`Could not get sims: ${f.message}`);await this.endLease(c.id,"acquire failed","fast","acquire-failed").catch(()=>{});throw f}finally{this.acquiring.delete(c.id);this.labels.delete(c.id)}}async claimOneWork(e,t){const s=Object.values(this.state.sims).filter(a=>a.templateId===t.id&&!a.leaseId&&a.health==="ok"&&!this.busy.has(a.udid));const i=s.find(a=>a.power==="awake")??s.find(a=>a.power==="parked"||a.power==="cold");const o=async a=>{const r=await this.isOwned(a.udid);return this.simLock.run(a.udid,async()=>{if(a.leaseId||this.busy.has(a.udid)||a.health!=="ok"||!this.state.sims[a.udid])return null;if(!r){a.health="broken";a.lastError="farm marker missing";this.store.save();return null}if(!this.state.leases[e.id]||this.leaseOverdue(e))return null;a.leaseId=e.id;e.sims.push(a.udid);a.idleSince=void 0;this.addLane(e,a);this.store.save();return a})};if(i?.power==="awake")return o(i);let n=null;try{if(i){n=await o(i)}else{const a=await this.budget({ramGB:t.profile==="slim"?this.state.settings.slimSimGB:this.state.settings.stockSimGB,diskGB:this.state.settings.cloneDiskGB});if(!a.ok)return null;n=await o(await this.cloneSim(t.id))}if(!n)return null;await this.bootSim(n.udid,e.id);return n}catch(a){if(n)this.unclaim(e,n.udid);if(a instanceof p&&(a.code==="RAM_LOW"||a.code==="DISK_LOW"||a.code==="HOST_BUSY"||a.code==="LEASE_EXPIRED")){return null}if(n&&this.state.sims[n.udid]?.health!=="ok"){const r=n.udid;this.background(`rebuild ${r}`,()=>this.rebuildSim(r))}throw a}}unclaim(e,t){e.sims=e.sims.filter(o=>o!==t);const s=e.runId?this.state.runs[e.runId]:void 0;if(s?.lanes[t])delete s.lanes[t];const i=this.state.sims[t];if(i&&i.leaseId===e.id)i.leaseId=void 0;this.store.save()}async checkIdentities(e,t=6e4){const s=this.template(e.templateId);const i=performance.now()+t;const o=new Map;for(;;){for(const a of e.sims){if(o.has(a))continue;const r=await it(await this.dataPath(a),s.bundleId);const c=r?await Ps(r):null;if(!c)continue;const l=[...o.entries()].find(([,h])=>h===c);if(l)throw new p("ID_CLASH",`${this.state.sims[a].name} and ${this.state.sims[l[0]].name} share one Buoy ID.`);const d=Object.values(this.state.sims).find(h=>h.udid!==a&&!e.sims.includes(h.udid)&&h.buoyDeviceId===c);if(d)throw new p("ID_CLASH",`${this.state.sims[a].name} and ${d.name} share one Buoy ID.`);o.set(a,c);this.state.sims[a].buoyDeviceId=c}if(o.size===e.sims.length||performance.now()>=i)break;await T(Math.max(this.pollMs,1e3))}const n=e.sims.filter(a=>!o.has(a));if(n.length){this.store.event({kind:"no-id",message:`No Buoy ID yet on ${n.map(a=>this.state.sims[a].name).join(", ")}. Is the app loading? Check Metro.`,leaseId:e.id})}}liveLease(e,t,s){const i=this.state.leases[e];if(!i){const o=this.runOfLease(e);if(o&&(o.endedBy==="user"||o.endedBy==="deadline"))this.assertRunOpen(o);throw new p("LEASE_EXPIRED",`Lease ${e} has ended.`,"Acquire new sims.")}if(i.epoch!==t)throw new p("STALE_EPOCH",`Lease ${e} is at epoch ${i.epoch}, not ${t}.`);if(s&&i.owner.clientId!==s)throw new p("NOT_OWNED",`Lease ${e} belongs to ${i.owner.label}.`);if(this.leaseOverdue(i)){const o=this.overdueEnd(i);this.background(`expire ${i.id}`,()=>this.endLease(i.id,"expired","full",o));if(o==="deadline")throw new p("RUN_ENDED","This run's time expired.","Ask for more time with sim_farm_extend before the end next time. Start a new run to go on.");throw new p("LEASE_EXPIRED",`Lease ${e} has expired.`,"Acquire new sims.")}return i}renew(e,t,s){const i=this.liveLease(e,t,s);const o=Math.min(this.clock.now()+Math.min(i.ttlMs,Xt),this.hardDeadlines.get(i.id)??Infinity);this.deadlines.set(i.id,o);return{expiresInMs:Math.round(o-this.clock.now())}}async release(e,t,s="fast",i,o=false){if(s!=="fast"&&s!=="full")throw new p("BAD_REQUEST","Use fast or full reset.");this.liveLease(e,t,i);return this.endLease(e,"released",s,o?null:"released")}async endLease(e,t,s,i="released"){const o=this.state.leases[e];if(!o)return{};o.epoch++;delete this.state.leases[e];this.deadlines.delete(e);this.hardDeadlines.delete(e);for(const[c,l]of Object.entries(this.state.locks))if(l.leaseId===e)delete this.state.locks[c];const n=new Set(o.sims.filter(c=>{const l=this.state.sims[c];return l?.bootEpoch===0&&l.power==="cold"&&l.health==="ok"}));const a={};const r=o.sims.filter(c=>this.copies[c]?.leaseId===e);for(const c of r){const l=this.copies[c];l.leaseId=void 0;l.state="stopping";a[l.name]="closing"}if(r.length)this.background(`close copies of ${e}`,()=>this.work(void 0,async()=>{for(const c of r)await this.stopCopy(c,`${o.owner.label}'s lease ended`)}));for(const c of o.sims){const l=this.state.sims[c];if(!l||l.leaseId!==e)continue;l.leaseId=void 0;l.health="dirty";l.idleSince=new Date().toISOString();this.busy.add(c);a[l.name]="resetting";void this.files(()=>dt(c,this.store.homeDir)).catch(()=>void 0)}this.store.event({kind:"lease-end",message:`${o.owner.label}'s ${o.purpose} ended (${t}).`,leaseId:e,runId:o.runId});this.settleRun(o,i);this.store.save();this.background(`reset after ${e}`,()=>this.work(void 0,async()=>{for(const c of o.sims){if(!this.state.sims[c])continue;try{const l=Date.parse(o.createdAt)-2e3;for(const d of await this.host.clientsOf(c)){if(Date.parse(d.start)>=l)await this.host.kill(d.pid,d.start)}if(s==="full"&&!n.has(c)||this.state.sims[c].failures>0)await this.rebuildSim(c);else await this.fastReset(c)}catch(l){this.store.event({kind:"reset-failed",message:`${this.state.sims[c]?.name??c}: ${l.message}. Rebuilding.`,udid:c});await this.rebuildSim(c).catch(()=>{})}finally{this.busy.delete(c)}}}));return a}dropFromLease(e,t){const s=this.state.leases[e];if(s)s.sims=s.sims.filter(i=>i!==t)}async actionWork(e,t,s,i,o,n){const a=this.liveLease(e,t,n);const r={};for(const c of s){if(!a.sims.includes(c))throw new p("NOT_OWNED",`${c} is not in lease ${e}.`);if(this.copies[c]){r[c]=await this.copyAction(a,c,i,o,()=>this.liveLease(e,t,n));continue}if(i==="install")throw new p("BAD_REQUEST","install is for Android copies. Rebuild the iOS template for a new app build.");await this.assertOwned(c);this.liveLease(e,t,n);const l=this.template(a.templateId);switch(i){case"screenshot":{const d=this.state.locks["perf-slot"];if(d&&d.leaseId!==a.id)throw new p("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);const h=C.join(this.store.shotsPath,`${c}.png`);r[c]=await this.host.screenshot(c,h)?h:null;break}case"open-url":if(!o)throw new p("BAD_REQUEST","open-url needs a url.");await this.host.openUrl(c,o);r[c]="opened";break;case"launch":await this.launchApp(c,l);r[c]="launched";break;case"reset":await this.fastReset(c,a.id);r[c]="reset";break;case"rebuild":{const d=await this.rebuildSim(c,a.id);if(!this.state.leases[a.id])throw new p("LEASE_EXPIRED",`Lease ${e} ended during the rebuild.`);d.leaseId=a.id;a.sims=a.sims.map(w=>w===c?d.udid:w);const h=a.runId?this.state.runs[a.runId]:void 0;const f=h?.lanes[c];if(h&&f){delete h.lanes[c];h.lanes[d.udid]={...f,udid:d.udid,name:d.name,state:"setting-up",note:"Rebuilt"}}await this.bootSim(d.udid,a.id);r[c]=d.udid;break}default:throw new p("BAD_REQUEST",`Unknown action ${i}.`)}}this.store.save();return r}async lock(e,t,s,i=0,o){if(typeof e!=="string"||!/^[a-z0-9-]{1,40}$/.test(e))throw new p("BAD_REQUEST","Lock names are short lowercase words.");this.touch(this.liveLease(t,s,o));if(this.state.locks[e]?.leaseId===t)return{held:true};const n=this.lockQueues.get(e)??[];this.lockQueues.set(e,n);const a={};n.push(a);const r=performance.now()+i;try{for(;;){this.store.assertActive();this.liveLease(t,s,o);const c=this.state.locks[e];const l=e==="perf-slot"&&(this.activeWork>0||this.busy.size>0||this.pendingBg>0);if(n[0]===a&&!c&&!l){this.state.locks[e]={name:e,leaseId:t,since:new Date().toISOString()};this.store.save();return{held:true}}if(performance.now()>=r){return{held:false,holder:c?this.state.leases[c.leaseId]?.owner.label??"someone":"the farm (work is still running)",since:c?.since}}await T(Math.max(1,Math.min(this.pollMs,r-performance.now())))}}finally{n.splice(n.indexOf(a),1);if(!n.length)this.lockQueues.delete(e)}}fairness(e,t,s,i){this.liveLease(e,t,i);const o=this.state.settings;const n=Jn(o,this.meter.cpuCount);const a=this.meter.verdicts(e,s,n);return{rules:n,fair:a.filter(r=>r.fair).length,unfair:a.filter(r=>!r.fair).length,runs:a}}unlock(e,t,s,i){this.liveLease(t,s,i);if(this.state.locks[e]?.leaseId===t)delete this.state.locks[e];this.store.save();return{held:false}}async shot(e){if(this.state.locks["perf-slot"])throw new p("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(this.shotsInFlight>=Hn)throw new p("HOST_BUSY","Taking other pictures. Try again in a moment.",void 0,true);this.shotsInFlight++;try{return await this.work(void 0,()=>this.shotWork(e))}finally{this.shotsInFlight--}}async shotWork(e){if(this.copies[e])return this.copyShot(e);await this.assertOwned(e);const t=this.sim(e);if(this.state.locks["perf-slot"])throw new p("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(t.power!=="awake")throw new p("BAD_REQUEST",`${t.name} is ${t.power}.`);const s=C.join(this.store.shotsPath,`${e}.png`);await this.files(()=>q.mkdir(C.dirname(s),{recursive:true}));if(!await this.host.screenshot(e,s,8e3))throw new p("HOST_ERROR",`No still from ${t.name} in time.`,void 0,true);return{file:s,at:new Date().toISOString()}}async show(e){if(this.copies[e])throw new p("BAD_REQUEST","Android copies run with no window, and can't get one later. Use its pictures in Details.");return this.work(this.state.sims[e]?.leaseId,()=>this.showWork(e))}async showWork(e){await this.assertOwned(e);const t=this.sim(e);if(t.power==="frozen"||t.power==="thawing")throw new p("BAD_REQUEST",`${t.name} is paused. Wake it first.`);if(t.power!=="awake")await this.bootSim(e,t.leaseId);await this.host.showWindow(e);return"shown"}async tick(){if(this.ticking)return;this.ticking=true;try{for(const s of Object.values(this.state.leases)){const i=s.owner.kind!=="person"&&!await J(s.owner.pid,s.owner.pidStart,true);if(i)await this.endLease(s.id,"its owner exited","full","owner-exited");else if(this.leaseOverdue(s))await this.endLease(s.id,"no renewal","full",this.overdueEnd(s))}await this.tickRuns();await this.tickCopies().catch(s=>this.log(`copies: ${s.message}`));await this.tickPhones().catch(s=>this.log(`phones: ${s.message}`));const e=this.state.settings.parkAfterMinutes;if(e>0){for(const s of Object.values(this.state.sims)){if(s.power!=="awake"||s.leaseId||this.busy.has(s.udid)||!s.idleSince)continue;if(Date.now()-Date.parse(s.idleSince)>e*6e4){await this.parkSim(s.udid).catch(i=>this.log(`park ${s.name}: ${i.message}`))}}}await this.trimIdle().catch(s=>this.log(`trim: ${s.message}`));const t=Object.keys(this.state.requests);if(t.length>_i)for(const s of t.slice(0,t.length-_i))delete this.state.requests[s]}finally{this.ticking=false}}async trimIdle(e=Date.now()){const t=this.state.settings.trimIdleHours;if(!(t>0)||e-this.lastTrimAt<Gn)return[];this.lastTrimAt=e;const s=o=>Date.parse(o.idleSince??o.createdAt);const i=[];for(const o of Object.keys(this.state.templates)){const n=Object.values(this.state.sims).filter(d=>d.templateId===o);if(n.some(d=>d.leaseId||this.busy.has(d.udid)))continue;const a=n.every(d=>e-s(d)>Wn);const r=a?0:this.state.settings.keepWarmPerTemplate;const c=n.filter(d=>(d.power==="parked"||d.power==="cold")&&e-s(d)>t*36e5).sort((d,h)=>s(h)-s(d));const l=Math.max(0,n.length-r);for(const d of c.slice(c.length-Math.min(l,c.length))){try{await this.deleteSim(d.udid);i.push(d.name)}catch(h){if(!(h instanceof p))throw h}}}if(i.length)this.store.event({kind:"trim",message:`Deleted ${i.length} idle cop${i.length===1?"y":"ies"}: ${i.join(", ")}.`});return i}async drain(e=10*6e4){const t=performance.now()+e;while((this.busy.size>0||this.pendingBg>0||this.activeWork>0)&&performance.now()<t)await T(200);return this.busy.size===0&&this.pendingBg===0&&this.activeWork===0}idle(){return Object.keys(this.state.leases).length===0&&Object.keys(this.copies).length===0&&this.busy.size===0&&this.activeWork===0&&this.pendingBg===0&&this.acquiring.size===0}async parkAllIdle(){for(const e of Object.values(this.state.sims)){if(e.power==="awake"&&!e.leaseId&&!this.busy.has(e.udid))await this.parkSim(e.udid,"farm stopping").catch(()=>{})}}background(e,t){this.pendingBg++;this.workContext.exit(t).catch(s=>{this.log(`${e}: ${s.message}`);try{this.store.event({kind:"error",message:`${e}: ${s.message}`})}catch{}}).finally(()=>this.pendingBg--)}async once(e,t,s,i){if(!t)return i();const o=`${e}:${t}`;const n=_o("sha256").update(JSON.stringify(s)).digest("hex").slice(0,16);const a=this.state.requests[o];if(a){if(a.hash!==n)throw new p("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return a.result}const r=this.inflight.get(o);if(r){if(r.hash!==n)throw new p("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return r.promise}const c=i();this.inflight.set(o,{hash:n,promise:c});try{const l=await c;this.state.requests[o]={hash:n,result:l,at:new Date().toISOString()};this.store.save();return l}finally{this.inflight.delete(o)}}get copies(){return this.state.androidCopies??={}}get androidTemplates(){return this.state.androidTemplates??={}}copyView(e){const t=e.leaseId?this.state.leases[e.leaseId]:void 0;const s=t?.runId?this.state.runs[t.runId]:void 0;return{udid:e.id,name:e.name,platform:"android",templateId:e.templateId,serial:e.serial,state:e.state,startedAt:e.startedAt,readyMs:e.readyMs,bootedFrom:e.bootedFrom,leaseId:e.leaseId,owner:t?.owner.label,purpose:s?.goal??t?.purpose,runId:s?.id,lane:s?.lanes[e.id]?.label,buoyDeviceId:e.buoyDeviceId,phone:e.phone,lastError:e.lastError}}copyTemplate(e){const t=this.androidTemplates[e.templateId];if(!t)throw new p("NO_TEMPLATE",`${e.name}'s template is gone.`);return t}async claimCopy(e,t){return this.work(e.id,async()=>{try{return await this.withReservation({ramGB:re(t),diskGB:1},()=>this.bootGate.use(()=>this.startCopyWork(t,e)))}catch(s){if(s instanceof p&&(s.code==="RAM_LOW"||s.code==="DISK_LOW"||s.code==="HOST_BUSY"||s.code==="LEASE_EXPIRED"))return null;throw s}})}async startCopyWork(e,t,s={}){if(t&&(!this.state.leases[t.id]||this.leaseOverdue(t)))throw new p("LEASE_EXPIRED","The lease ended while copies were being prepared.");const i=await this.android.freePort(new Set(Object.values(this.copies).map(c=>c.port)));const o=`a-${we(3).toString("hex")}`;this.state.nextCopyIndex=(this.state.nextCopyIndex??0)+1;const n={id:o,name:`Buoy Android ${this.state.nextCopyIndex}`,templateId:e.id,serial:`emulator-${i}`,port:i,pid:0,pidStart:"",log:"",state:"starting",startedAt:new Date().toISOString(),leaseId:t?.id,window:s.window||void 0};this.copies[o]=n;const a=this.store.intent("copy-start",{id:o,port:i,avd:e.avd});const r=performance.now();try{Object.assign(n,await this.android.startCopy(e,i,s))}catch(c){delete this.copies[o];this.store.result(a,"copy-start",{failed:true});throw new p("HOST_ERROR",`Could not start a copy: ${c.message}`)}this.store.result(a,"copy-start",{id:o,pid:n.pid});if(t){t.sims.push(o);this.addLane(t,{udid:o,name:n.name})}this.store.save();try{const c=await this.android.readyCopy(e,n);n.bootedFrom=c.bootedFrom;n.readyMs=Math.round(performance.now()-r);n.state="ready";if(c.bootedFrom==="cold"){this.store.event({kind:"copy-cold",message:`${n.name} booted from scratch: the template's snapshot did not load. Make a new template.`})}}catch(c){n.lastError=c.message;if(t)this.unclaim(t,o);await this.stopCopy(o,"it did not start").catch(()=>{});throw new p("HOST_ERROR",`${n.name} did not start: ${c.message}`)}this.store.save();return n}async stopCopy(e,t){const s=this.copies[e];if(!s)return;s.state="stopping";const i=this.store.intent("copy-stop",{id:e,pid:s.pid,pidStart:s.pidStart,serial:s.serial});try{if(s.pid)await this.android.stopCopy(this.androidTemplates[s.templateId],s)}finally{delete this.copies[e];this.copyMemAt.delete(e);this.store.result(i,"copy-stop",{id:e});this.store.event({kind:"copy-closed",message:`${s.name} closed (${t}).`})}}async launchCopy(e,t){const s=this.copies[e];const i=await this.android.launchApp(this.copyTemplate(s),s.serial,t);s.phone=void 0;this.copyMemAt.delete(e);return i.totalMs}async checkCopyIdentities(e,t=6e4){const s=performance.now()+t;const i=new Map;for(;;){for(const n of e.sims){const a=this.copies[n];if(!a||i.has(n))continue;const r=await this.android.installId(this.copyTemplate(a),a.serial).catch(()=>null);if(!r)continue;const c=[...i.entries()].find(([,l])=>l===r)??Object.values(this.copies).filter(l=>l.id!==n&&!e.sims.includes(l.id)&&l.buoyDeviceId===r).map(l=>[l.id,r])[0];if(c)throw new p("ID_CLASH",`${a.name} and ${this.copies[c[0]]?.name??c[0]} share one Buoy ID.`,"Make a new template: its saved id was not cleared.");i.set(n,r);a.buoyDeviceId=r}if(i.size===e.sims.length||performance.now()>=s)break;await T(Math.max(this.pollMs,1e3))}const o=e.sims.filter(n=>!i.has(n));if(o.length){this.store.event({kind:"no-id",message:`No Buoy ID yet on ${o.map(n=>this.copies[n]?.name??n).join(", ")}. Is the app loading? Check Metro. A release build's id can't be read.`,leaseId:e.id})}}async copyAction(e,t,s,i,o){const n=this.copies[t];if(n.state!=="ready"||!await this.android.alive(n.pid,n.pidStart))throw new p("NOT_FOUND",`${n.name} is not running.`,"Release it and take a new copy.");o();const a=this.copyTemplate(n);switch(s){case"screenshot":{const r=this.state.locks["perf-slot"];if(r&&r.leaseId!==e.id)throw new p("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);const c=C.join(this.store.shotsPath,`${t}.png`);return await this.android.screenshot(a,n.serial,c)?c:null}case"open-url":if(!i)throw new p("BAD_REQUEST","open-url needs a url.");await this.android.openUrl(a,n.serial,i);return"opened";case"launch":{const r=await this.launchCopy(t,i);return r!=null?{launched:true,startMs:r}:"launched"}case"install":if(!i||!i.endsWith(".apk")||!$t(i))throw new p("BAD_REQUEST","install needs the path of an .apk on this Mac.");await this.android.install(a,n.serial,i);return"installed";case"reset":case"rebuild":return this.replaceCopy(e,t);default:throw new p("BAD_REQUEST",`Unknown action ${s}.`)}}async replaceCopy(e,t){const s=this.copies[t];const i=this.copyTemplate(s);await this.stopCopy(t,"reset");e.sims=e.sims.filter(r=>r!==t);const o=e.runId?this.state.runs[e.runId]:void 0;const n=o?.lanes[t];if(o&&n)delete o.lanes[t];const a=await this.withReservation({ramGB:re(i),diskGB:1},()=>this.startCopyWork(i,e));if(o&&n)o.lanes[a.id]={...n,udid:a.id,name:a.name,state:"setting-up",note:"Reset"};this.store.save();return a.id}async copyShot(e){const t=this.copies[e];if(this.state.locks["perf-slot"])throw new p("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(t.state!=="ready")throw new p("BAD_REQUEST",`${t.name} is ${t.state}.`);const s=C.join(this.store.shotsPath,`${e}.png`);await this.files(()=>q.mkdir(C.dirname(s),{recursive:true}));if(!await this.android.screenshot(this.copyTemplate(t),t.serial,s,8e3))throw new p("HOST_ERROR",`No still from ${t.name} in time.`,void 0,true);return{file:s,at:new Date().toISOString()}}async copyBench(e,t,s,i,o){const n=this.liveLease(e,t,o);if(!n.sims.includes(s)||!this.copies[s])throw new p("NOT_OWNED",`${s} is not a copy in lease ${e}.`);this.touch(n);const a=this.copies[s];return this.work(n.id,()=>this.android.bench(this.copyTemplate(a),a,i))}async dropCopies(e,t,s,i){const o=this.liveLease(e,t,i);for(const a of s)if(!o.sims.includes(a)||!this.copies[a])throw new p("NOT_OWNED",`${a} is not a copy in lease ${e}.`);const n=[];await this.work(void 0,async()=>{for(const a of s){n.push(this.copies[a]?.name??a);const r=o.runId?this.state.runs[o.runId]:void 0;if(r?.lanes[a])r.lanes[a].gone=true;this.dropFromLease(o.id,a);await this.stopCopy(a,"closed by its owner")}});if(!this.state.leases[o.id]?.sims.length)await this.endLease(o.id,"its copies were closed","fast","released");this.store.save();return n}async createAndroidTemplate(e){return this.work(void 0,async()=>{const t={phone:{ramMB:4096}};return this.withReservation({ramGB:re(t),diskGB:4},async()=>{this.store.event({kind:"template",message:`Making an Android template from ${C.basename(e.apk)}\u2026`});let s;try{s=await this.android.buildTemplate(e,o=>{this.log(o);this.store.event({kind:"template-progress",message:o})})}catch(o){this.store.event({kind:"template-failed",message:`Android template failed: ${o.message}`});throw new p("HOST_ERROR",`Android template failed: ${o.message}`)}s.usable=false;s.note="Checking that a copy starts from the snapshot.";this.androidTemplates[s.id]=s;this.store.save();let i=null;try{i=await this.startCopyWork(s);s.copyReadyMs=i.readyMs;if(i.bootedFrom==="cold")throw new Error("Copies cold boot instead of loading the snapshot.");s.usable=true;s.note=void 0;this.store.event({kind:"template",message:`Android template ${s.id} is ready: ${s.packageName}, copies start in ${((i.readyMs??0)/1e3).toFixed(1)} s.`})}catch(o){s.note=`Build failed: ${o.message}`;this.store.event({kind:"template-failed",message:s.note});throw new p("HOST_ERROR",s.note)}finally{if(i)await this.stopCopy(i.id,"template check").catch(()=>{});this.store.save()}return s})})}async removeAndroidTemplate(e){const t=this.androidTemplates[e];const s=Object.values(this.copies).filter(i=>i.templateId===e);if(s.length)throw new p("HOST_BUSY",`Template ${e} still has copies running: ${s.map(i=>i.name).join(", ")}.`,"Close them first.");await this.android.removeTemplateFiles(t);delete this.androidTemplates[e];this.store.event({kind:"template-removed",message:`Removed Android template ${e}.`});this.store.save();return{removed:[]}}async recoverAndroid(e){await this.importLegacyAndroid();for(const s of e){if(s.op==="copy-stop"){const i=s.data;await this.android.stopCopy(this.androidTemplates[this.copies[i.id]?.templateId??""],i).catch(()=>{});delete this.copies[i.id]}if(s.op==="copy-start"){const i=s.data;const o=this.copies[i.id];if(o&&!o.pid)delete this.copies[i.id]}}for(const s of Object.values(this.copies)){const i=s.pid&&await this.android.alive(s.pid,s.pidStart);const o=s.leaseId?this.state.leases[s.leaseId]:void 0;if(!i){this.lostCopy(s,"its emulator stopped while the farm was down");continue}if(!o)await this.stopCopy(s.id,"no one held it after a restart").catch(()=>{})}const t=new Set(Object.values(this.copies).map(s=>s.port));for(const s of await this.android.findEmulators().catch(()=>[])){if(t.has(s.port))continue;this.store.event({kind:"orphan-copy",message:`Closed a farm emulator on port ${s.port} that had no record.`});await this.android.stopCopy(Object.values(this.androidTemplates).find(i=>i.avd===s.avd),{serial:`emulator-${s.port}`,pid:s.pid,pidStart:s.pidStart}).catch(()=>{})}}lostCopy(e,t){delete this.copies[e.id];this.copyMemAt.delete(e.id);this.store.event({kind:"lost",message:`${e.name} is gone: ${t}.`,udid:e.id});const s=e.leaseId?this.state.leases[e.leaseId]:void 0;if(!s)return;this.dropFromLease(s.id,e.id);const i=s.runId?this.state.runs[s.runId]:void 0;if(i?.lanes[e.id]){i.lanes[e.id].gone=true;this.note(i,"farm",`${i.lanes[e.id].label??e.name} stopped by itself (${t}).`,e.id)}if(!s.sims.length)this.background(`end ${s.id}`,()=>this.endLease(s.id,"its copies are gone","fast","lost"))}async importLegacyAndroid(){if(this.state.androidImported)return;const e=C.join(ze(this.store.homeDir),"state.json");let t=null;try{t=JSON.parse(ko(e,"utf8"))}catch{}for(const i of Object.values(t?.templates??{}))if(!this.androidTemplates[i.id])this.androidTemplates[i.id]=i;const s=[];for(const i of Object.values(t?.copies??{})){if(!await this.android.alive(i.pid,i.pidStart))continue;this.state.nextCopyIndex=(this.state.nextCopyIndex??0)+1;s.push({id:`a-${we(3).toString("hex")}`,name:`Buoy Android ${this.state.nextCopyIndex}`,templateId:i.templateId,serial:i.serial,port:i.port,pid:i.pid,pidStart:i.pidStart,log:i.log,state:"ready",startedAt:i.startedAt,readyMs:i.readyMs,bootedFrom:i.bootedFrom,window:i.window||void 0})}if(s.length){const i={clientId:"c-moved",kind:"person",pid:0,label:"You (farm android)"};const o=this.state.settings.personLeaseHours*36e5;const n={templateId:s[0].templateId,count:s.length,purpose:"Copies started before this update",goal:"Copies started before this update",owner:i,ttlMs:o};const a=this.newRun(n,s[0].templateId,o);const r={id:`l-${we(4).toString("hex")}`,epoch:1,owner:i,purpose:n.purpose,runId:a.id,templateId:s[0].templateId,sims:[],createdAt:new Date().toISOString(),ttlMs:o};a.leases.push(r.id);this.state.leases[r.id]=r;this.hardDeadlines.set(r.id,this.clock.now()+o);for(const c of s){c.leaseId=r.id;this.copies[c.id]=c;r.sims.push(c.id);this.addLane(r,{udid:c.id,name:c.name})}}this.state.androidImported=true;if(t)this.store.event({kind:"android-moved",message:`Moved ${Object.keys(t.templates??{}).length} Android template(s) and ${s.length} running cop${s.length===1?"y":"ies"} into the farm.`});this.store.save()}async tickCopies(){for(const e of Object.values(this.copies)){if(e.state!=="ready")continue;if(!await this.android.alive(e.pid,e.pidStart)){this.lostCopy(e,"its emulator stopped");this.store.save();continue}const t=e.leaseId?this.state.leases[e.leaseId]:void 0;if(!t||this.state.locks["perf-slot"]||Date.now()-(this.copyMemAt.get(e.id)??0)<5e3)continue;this.copyMemAt.set(e.id,Date.now());const s=this.androidTemplates[e.templateId];if(!s)continue;const i=await this.android.appMemory(s,e.serial).catch(()=>null);if(i?.pid){if(e.phone?.pid!==i.pid)e.phone={...e.phone,pid:i.pid,peakMB:i.pssMB??void 0,overLimit:void 0,killedFor:void 0};else if(i.pssMB&&i.pssMB>(e.phone.peakMB??0))e.phone.peakMB=i.pssMB}else if(e.phone?.pid&&!e.phone.killedFor){const o=await this.android.memoryKill(s,e.serial,e.phone.pid).catch(()=>null);if(o){e.phone.killedFor=o;e.phone.overLimit=true;const n=t.runId?this.state.runs[t.runId]:void 0;if(n&&e.phone.notedPid!==e.phone.pid){e.phone.notedPid=e.phone.pid;this.note(n,"farm",`${n.lanes[e.id]?.label??e.name}: Android closed the app for memory (${o}), like ${s.phone.name} would. Peak ${e.phone.peakMB??"?"} MB.`,e.id)}}}}}newRun(e,t,s){const i=Date.now();const o=e.owner.agent?this.state.settings.runCapMinutes*6e4:24*36e5;const n=(e.goal??"").trim()||e.purpose;const a={id:`r-${we(4).toString("hex")}`,goal:n.slice(0,200),kind:e.kind?.slice(0,40),owner:e.owner,templateId:t,state:"active",createdAt:new Date(i).toISOString(),deadlineAt:new Date(i+Math.min(s,o)).toISOString(),capAt:new Date(i+o).toISOString(),leases:[],lanes:{},notes:[],lastActivityAt:new Date(i).toISOString(),auto:!e.goal};this.state.runs[a.id]=a;return a}touch(e){const t=e.runId?this.state.runs[e.runId]:void 0;if(t)t.lastActivityAt=new Date().toISOString()}runOfLease(e){return Object.values(this.state.runs).find(t=>t.leases.includes(e))}assertRunOpen(e){if(e.state==="stopping"||e.state==="stopped"){if(e.endedBy==="user")throw new p("RUN_STOPPED","The person stopped this run in Buoy Desktop (reason=user).","Tell them: You stopped this run in Sims. Start a new run only if they ask.");if(e.endedBy==="deadline")throw new p("RUN_ENDED","This run ran out of time.","Start a new run to go on. Ask for more time with sim_farm_extend before the end next time.")}if(ae.includes(e.state)||e.state==="stopping")throw new p("RUN_ENDED",`This run has ended (${e.state}).`,"Start a new run.")}ownRun(e,t){const s=this.state.runs[e];if(!s)throw new p("NOT_FOUND",`No run ${e}.`,"Use the runId that sim_farm_acquire returned.");if(t&&s.owner.clientId!==t)throw new p("NOT_OWNED",`Run ${e} belongs to ${s.owner.label}.`);this.assertRunOpen(s);return s}async forgetPhone(e){await q.rm(Ke(e,this.store.homeDir),{force:true});await q.rm(lt(e,this.store.homeDir),{force:true})}async tickPhones(){for(const e of Object.values(this.state.sims)){if(e.phone?.status!=="on"||e.power!=="awake")continue;const t=e.leaseId?this.state.leases[e.leaseId]:void 0;const s=t?.runId?this.state.runs[t.runId]:void 0;if(!s||ae.includes(s.state))continue;const i=await pt(e.udid,this.store.homeDir);if(!i?.overLimit||!i.pid||e.phone.notedPid===i.pid)continue;e.phone.notedPid=i.pid;const o=s.lanes[e.udid]?.label??e.name;const n=e.phone.phoneName??"the phone";this.note(s,"farm",i.ended==="safety"?`${o}: the app used ${i.peakMB} MB and hit the farm's safety cap, so it was closed. ${n} would have closed it sooner.`:i.ended==="limit"?`${o}: the app went past ${n}'s memory limit (${i.peakMB} MB) and was closed, like on a real phone.`:`${o}: the app went past ${n}'s memory limit (${i.peakMB} MB). A real phone would close it.`,e.udid);this.store.save()}}note(e,t,s,i){e.notes.push({at:new Date().toISOString(),by:t,text:s.slice(0,500),udid:i});if(e.notes.length>Ei)e.notes.splice(0,e.notes.length-Ei)}addLane(e,t){const s=e.runId?this.state.runs[e.runId]:void 0;if(!s)return;const i=this.labels.get(e.id)?.[e.sims.length-1];s.lanes[t.udid]={udid:t.udid,name:t.name,label:i?.slice(0,80),state:"setting-up",at:new Date().toISOString()};if(s.state==="waiting")s.state="active"}overdueEnd(e){return this.clock.now()>=(this.hardDeadlines.get(e.id)??Infinity)?"deadline":"lost"}settleRun(e,t){const s=e.runId?this.state.runs[e.runId]:void 0;if(!s)return;for(const o of e.sims)if(s.lanes[o])s.lanes[o].gone=true;if(ae.includes(s.state)||s.state==="stopping"||t===null)return;const i=s.leases.some(o=>this.state.leases[o]);switch(t){case"released":if(!i)this.endRun(s,this.outcomeOf(s)==="failed"?"failed":"done","released");return;case"acquire-failed":if(!i&&!Object.keys(s.lanes).length)this.endRun(s,"failed","acquire-failed");return;case"user":return this.endRun(s,"stopped","user","You stopped this run.");case"deadline":return this.endRun(s,"stopped","deadline","Ran out of time. The sims were taken back.");case"owner-exited":return this.endRun(s,"interrupted","owner-exited","The chat closed. Cleaning up.");case"lost":return this.endRun(s,"interrupted","lost","Lost touch with the chat. Cleaning up.");case"finished":return this.endRun(s,"done","finished")}}endRun(e,t,s,i,o="full"){if(ae.includes(e.state))return;e.state="stopping";e.endedBy=s;if(i)this.note(e,s==="user"?"person":"farm",i);for(const n of e.leases){if(this.state.leases[n])void this.endLease(n,i??s,s==="finished"||s==="released"?o:"full",null)}for(const n of Object.values(e.lanes))n.gone=true;e.state=t;e.endedAt=new Date().toISOString();e.outcome=e.outcome??this.outcomeOf(e);this.store.event({kind:"run-end",message:`${e.owner.label}: "${e.goal}" ${t==="done"?"finished":t}.`,runId:e.id})}outcomeOf(e){const t=Object.values(e.lanes);const s=t.filter(o=>o.state==="passed").length;const i=t.filter(o=>o.state==="failed").length;if(!s&&!i)return"unknown";if(i&&s)return"mixed";return i?"failed":"passed"}report(e,t){const s=this.ownRun(e.runId,t);const i=new Date().toISOString();if(e.sequence!==void 0){if(!Number.isInteger(e.sequence)||e.sequence<0)throw new p("BAD_REQUEST","sequence must be a whole number.");if(s.lastSeq!==void 0&&e.sequence<=s.lastSeq)return{applied:false,reason:"already have this report",...this.timeLeft(s)};s.lastSeq=e.sequence}if(e.state!==void 0&&!$i.includes(e.state))throw new p("BAD_REQUEST",`state must be one of ${$i.join(", ")}.`);if(e.udid!==void 0){const o=s.lanes[e.udid];if(!o)throw new p("NOT_FOUND",`That sim is not in run ${s.id}.`,"Use a UDID from this run's sims.");if(e.state)o.state=e.state;if(e.state==="passed"||e.state==="failed")o.resultSource=e.resultSource==="test"?"test":"agent";if(e.note!==void 0)o.note=String(e.note).slice(0,200);o.at=i}if(e.counts!==void 0){const o=e.counts;const n=a=>a===void 0||Number.isInteger(a)&&a>=0;if(!o||typeof o!=="object"||!n(o.done)||!n(o.total)||!n(o.passed)||!n(o.failed)||o.done>o.total){throw new p("BAD_REQUEST","counts needs whole numbers, with done no more than total.")}s.counts={done:o.done,total:o.total,passed:o.passed,failed:o.failed}}if(e.note)this.note(s,"agent",String(e.note),e.udid);s.lastReportAt=s.lastActivityAt=i;this.store.save();return{applied:true,...this.timeLeft(s)}}extend(e,t,s){const i=this.ownRun(e,s);if(!Number.isFinite(t)||t<=0)throw new p("BAD_REQUEST","minutes must be above zero.");const o=Date.parse(i.deadlineAt);const n=Date.now()+t*6e4;const a=Math.min(n,Date.parse(i.capAt));if(a<=o){return{granted:false,partly:false,reason:"This run is at its time limit.",...this.timeLeft(i)}}i.deadlineAt=new Date(a).toISOString();i.lastActivityAt=new Date().toISOString();for(const r of i.leases){const c=this.state.leases[r];if(!c)continue;c.ttlMs=a-Date.parse(c.createdAt);this.hardDeadlines.set(r,this.clock.now()+(a-Date.now()))}this.note(i,"farm",`More time: until ${new Date(a).toLocaleTimeString()}.`);this.store.save();return{granted:true,partly:a<n,...this.timeLeft(i)}}finish(e,t,s,i){const o=this.state.runs[e];if(o?.endedBy==="finished"&&o.owner.clientId===i)return{state:o.state,outcome:o.outcome};const n=this.ownRun(e,i);if(t&&!["passed","failed","mixed","unknown"].includes(t))throw new p("BAD_REQUEST","outcome must be passed, failed, mixed or unknown.");n.outcome=t??this.outcomeOf(n);if(s)n.summary=s.slice(0,600);this.endRun(n,n.outcome==="failed"?"failed":"done","finished",void 0,"fast");this.store.save();return{state:n.state,outcome:n.outcome}}stopRun(e,t="you"){const s=this.state.runs[e];if(!s)throw new p("NOT_FOUND",`No run ${e}.`);if(ae.includes(s.state))return{state:s.state};this.endRun(s,"stopped","user",t==="you"?"You stopped this run.":`Stopped by ${t}.`);this.store.save();return{state:s.state}}stopAll(e="you"){const t=Object.values(this.state.runs).filter(s=>!ae.includes(s.state));for(const s of t)this.stopRun(s.id,e);return{stopped:t.length}}timeLeft(e){return{deadlineAt:e.deadlineAt,capAt:e.capAt,secondsLeft:Math.max(0,Math.round((Date.parse(e.deadlineAt)-Date.now())/1e3))}}async tickRuns(){const e=Date.now();for(const o of Object.values(this.state.runs)){if(ae.includes(o.state))continue;if(e>=Date.parse(o.deadlineAt)){this.endRun(o,"stopped","deadline","Ran out of time. The sims were taken back.");this.store.save()}else if(!o.leases.some(n=>this.state.leases[n])&&!await J(o.owner.pid,o.owner.pidStart,true)){this.endRun(o,"interrupted","owner-exited","The chat closed.");this.store.save()}}const t=this.state.settings.keepRunsHours*36e5;const s=Object.values(this.state.runs).filter(o=>ae.includes(o.state)).sort((o,n)=>Date.parse(n.endedAt??n.createdAt)-Date.parse(o.endedAt??o.createdAt));let i=false;s.forEach((o,n)=>{if(n>=200||e-Date.parse(o.endedAt??o.createdAt)>t){delete this.state.runs[o.id];i=true}});if(i)this.store.save()}async status(e={}){const t=await this.budget();const s=await Promise.all(Object.values(this.state.sims).map(async o=>{const n=o.leaseId?this.state.leases[o.leaseId]:void 0;const a=n?.runId?this.state.runs[n.runId]:void 0;const r=o.phone?.status==="on"?await pt(o.udid,this.store.homeDir):null;const c=o.phone&&{...o.phone,notedPid:void 0,...r?.loaded?{pid:r.pid,peakMB:r.peakMB,memoryWarnings:r.memoryWarnings,overLimit:r.overLimit,ended:r.ended}:{}};return{...o,phone:c,owner:n?.owner.label,purpose:a?.goal??n?.purpose,runId:a?.id,lane:a?.lanes[o.udid]?.label,busy:this.busy.has(o.udid)}}));const i={farmId:this.state.farmId,counts:{pool:s.length,awake:s.filter(o=>o.power==="awake").length,leased:s.filter(o=>o.leaseId).length},budget:{roomForMoreSims:t.roomFor,freeDiskGB:Math.floor(t.stats.freeDiskBytes/Te),availableMemGB:Math.floor(t.stats.availableMemBytes/Te),memPressure:t.stats.memPressure,loadAvg1:Number(t.stats.loadAvg1.toFixed(1)),cpuCount:t.stats.cpuCount},templates:Object.values(this.state.templates),sims:s,android:{templates:Object.values(this.androidTemplates).map(o=>{const n=this.android.stale(o);return{...o,platform:"android",costGB:re(o),roomFor:Math.floor((t.ramRoomGB??0)/re(o)),...n&&o.usable?{usable:false,note:n}:{}}}),copies:Object.values(this.copies).map(o=>this.copyView(o))},orphans:Object.values(this.state.orphans),leases:Object.values(this.state.leases).map(o=>({...o,expiresInMs:Math.max(0,Math.round(this.leaseDeadline(o)-this.clock.now()))})),runs:Object.values(this.state.runs).sort((o,n)=>Date.parse(n.createdAt)-Date.parse(o.createdAt)).map(o=>({...o,notes:o.notes.slice(-50),secondsLeft:ae.includes(o.state)?0:Math.max(0,Math.round((Date.parse(o.deadlineAt)-Date.now())/1e3))})),locks:Object.values(this.state.locks),recentEvents:this.state.events.slice(-25),settings:this.state.settings};if(e.outside){const o=new Set([...Object.keys(this.state.sims),...Object.keys(this.state.orphans),...Object.values(this.state.templates).map(n=>n.udid)]);i.outsideSims=(await this.host.listDevices()).filter(n=>!o.has(n.udid)&&n.available).map(n=>({udid:n.udid,name:n.name,state:n.state,runtime:n.runtime}))}return i}setSettings(e){const t=this.state.settings;const s={parkAfterMinutes:[0,24*60],maxConcurrentBoots:[1,8],reserveRamGB:[4,512],reserveDiskGB:[5,2048],slimSimGB:[.5,16],stockSimGB:[1,32],cloneDiskGB:[.1,32],runCapMinutes:[5,24*60],keepRunsHours:[1,24*14],fairMinFreeShare:[0,1],fairMaxSwingShare:[.02,1],fairMaxThermalLevel:[0,4],fairMaxSwapGrowthMB:[0,65536],personLeaseHours:[1,24],phoneCapMB:[0,1048576],trimIdleHours:[0,24*30],keepWarmPerTemplate:[0,64]};if(!e||typeof e!=="object")throw new p("BAD_REQUEST","settings must be an object.");if("phoneProfile"in e){const o=Oe(this.store.homeDir);if(e.phoneProfile!==zt&&!(typeof e.phoneProfile==="string"&&o[e.phoneProfile])){throw new p("BAD_REQUEST",`phoneProfile must be "off" or one of: ${Object.keys(o).join(", ")}.`)}}if("phoneOnLimit"in e&&e.phoneOnLimit!=="warn"&&e.phoneOnLimit!=="kill"){throw new p("BAD_REQUEST",`phoneOnLimit must be "warn" or "kill".`)}if("phoneCapMB"in e&&(!Number.isInteger(e.phoneCapMB)||e.phoneCapMB!==0&&e.phoneCapMB<3072)){throw new p("BAD_REQUEST","phoneCapMB must be 0 (automatic) or a whole number of MB, at least 3072.")}for(const[o,n]of Object.entries(e)){if(!(o in t)||typeof n!==typeof t[o])throw new p("BAD_REQUEST",`Bad setting ${o}.`);const a=s[o];if(a&&(typeof n!=="number"||!Number.isFinite(n)||n<a[0]||n>a[1])){throw new p("BAD_REQUEST",`${o} must be ${a[0]} to ${a[1]}.`)}}const i=["phoneProfile","phoneOnLimit","phoneCapMB"].some(o=>o in e&&e[o]!==t[o]);for(const[o,n]of Object.entries(e))t[o]=n;this.store.save();if(i){for(const o of Object.keys(this.state.sims))void dt(o,this.store.homeDir).catch(()=>void 0)}return t}};function Qn(e,t){return Math.min(t,Math.max(4,Math.round(e*.375)))}function Vn(e){return Math.min(4,Math.max(1.5,e*.1))}function Jn(e,t){const s=i=>Math.round(i*10)/10;return{minFreeCores:s(Math.max(2,e.fairMinFreeShare*t)),maxSwingCores:s(Math.max(1,e.fairMaxSwingShare*t)),maxThermalLevel:e.fairMaxThermalLevel,maxSwapGrowthMB:e.fairMaxSwapGrowthMB}}function Oi(e,t=Date.now()-3*6e4){const s=new Map;for(const i of e.split("\n")){const o=/^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d)\.\d+ \((?:user|system)\/\d*\/?([\w.-]+) \[\d+\]\) <\w+>: exited due to (SIG\w+)/.exec(i);if(!o||o[3]==="SIGKILL"||o[3]==="SIGTERM")continue;const n=Date.parse(o[1].replace(" ","T"));if(!(n>=t))continue;const a=s.get(o[2])??[];a.push(n);s.set(o[2],a)}for(const[i,o]of s){o.sort((n,a)=>n-a);for(let n=2;n<o.length;n++)if(o[n]-o[n-2]<=6e4)return i.replace(/^com\.apple\./,"")}return null}import{promises as Ti}from"fs";import De from"os";import Ae from"path";var Di=6e4;var Zt=class{constructor(){this.disabledRoot=Be;this.devicesDir=Ae.join(De.homedir(),"Library/Developer/CoreSimulator/Devices");this.graphicsPids=[];this.loadCalls=0}simctl(e,t=Di){return L("xcrun",["simctl",...e],{timeoutMs:t})}async listDevices(){const e=JSON.parse(await this.simctl(["list","devices","--json"],3e4));const t=[];for(const[s,i]of Object.entries(e.devices??{})){for(const o of i){t.push({udid:o.udid,name:o.name,state:o.state,runtime:s,deviceType:o.deviceTypeIdentifier,dataPath:o.dataPath??Ae.join(this.devicesDir,o.udid,"data"),available:o.isAvailable!==false})}}return t}async dataPathOf(e){if(!/^[0-9A-F-]{36}$/i.test(e))return null;const t=Ae.join(this.devicesDir,e);try{await Ti.access(Ae.join(t,"device.plist"));return Ae.join(t,"data")}catch{return null}}async create(e,t,s){return(await this.simctl(["create",e,t,s])).trim()}async clone(e,t){return(await this.simctl(["clone",e,t])).trim()}async boot(e){const t=await _("xcrun",["simctl","boot",e],{timeoutMs:Di,env:st()});if(t.code!==0&&!/current state: Booted/i.test(t.stderr)){throw new Error(`simctl boot ${e} failed: ${t.stderr.trim().slice(0,300)}`)}}async shutdown(e){const t=await _("xcrun",["simctl","shutdown",e],{timeoutMs:9e4});if(t.code!==0&&!/current state: Shutdown/i.test(t.stderr)){throw new Error(`simctl shutdown ${e} failed: ${t.stderr.trim().slice(0,300)}`)}}async delete(e){await this.simctl(["delete",e],9e4)}async install(e,t){await this.simctl(["install",e,t],18e4)}async launch(e,t,s){await L("xcrun",["simctl","launch","--terminate-running-process",e,t],{timeoutMs:6e4,env:s&&Object.keys(s).length?{...process.env,...s}:void 0})}async terminate(e,t){await _("xcrun",["simctl","terminate",e,t],{timeoutMs:2e4})}async openUrl(e,t){await this.simctl(["openurl",e,t],2e4)}async screenshot(e,t,s=1e4){const i=await _("xcrun",["simctl","io",e,"screenshot",t],{timeoutMs:s});return i.code===0&&!i.timedOut}spawn(e,t,s=2e4){return _("xcrun",["simctl","spawn",e,...t],{timeoutMs:s})}async appContainer(e,t){const s=await _("xcrun",["simctl","get_app_container",e,t,"data"],{timeoutMs:2e4});return s.code===0?s.stdout.trim()||null:null}async installedApp(e,t){const s=await _("xcrun",["simctl","get_app_container",e,t,"app"],{timeoutMs:2e4});return s.code===0?s.stdout.trim()||null:null}async simTree(e){const t=await tt();const s=t.find(i=>/launchd_sim/.test(i.command)&&i.command.includes(e));return s?Ot(t,s.pid):[]}async signal(e,t){for(const s of e){try{process.kill(s,t)}catch{}}}async launchdLogTail(e,t=256*1024){const s=Ae.join(this.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`,"launchd.log");try{const i=await Ti.open(s,"r");try{const{size:o}=await i.stat();const n=Math.max(0,o-t);const a=Buffer.alloc(o-n);await i.read(a,0,a.length,n);return a.toString("utf8")}finally{await i.close()}}catch{return""}}async hostStats(){const[e,t,s,i]=await Promise.all([L("vm_stat",[],{timeoutMs:1e4}),_("sysctl",["-n","kern.memorystatus_vm_pressure_level"],{timeoutMs:5e3}),_("sysctl",["-n","vm.swapusage"],{timeoutMs:5e3}),L("df",["-k",De.homedir()],{timeoutMs:1e4})]);const o=Number(/page size of (\d+)/.exec(e)?.[1]??16384);const n=h=>Number(new RegExp(`${h}:\\s+(\\d+)`).exec(e)?.[1]??0);const a=n("Pages free")+n("Pages inactive")+n("Pages speculative")+n("Pages purgeable");const r=Number(t.stdout.trim());const c=/used = ([\d.]+)M/.exec(s.stdout)?.[1];const l=i.trim().split("\n").pop()??"";const d=Number(l.split(/\s+/)[3]??0);return{totalMemBytes:De.totalmem(),availableMemBytes:a*o,memPressure:r>=4?"critical":r>=2?"warn":"normal",swapUsedBytes:c?Number(c)*1024*1024:0,freeDiskBytes:d*1024,cpuCount:De.cpus().length,loadAvg1:De.loadavg()[0]}}async showWindow(e){const t=await _("open",["-b","com.apple.iphonesimulator","--args","-CurrentDeviceUDID",e],{timeoutMs:2e4});if(t.code===0)return;await L("open",["-b","com.apple.dt.Devices"],{timeoutMs:2e4})}async loadCounters(e,t=true){if(t&&this.loadCalls++%10===0){const k=await _("pgrep",["-x","SimMetalHost"],{timeoutMs:5e3});this.graphicsPids=k.stdout.split("\n").map(Number).filter(y=>y>0)}const s=De.cpus();let i=0;let o=0;for(const k of s){const y=k.times;const b=y.user+y.nice+y.sys+y.idle+y.irq;o+=b;i+=b-y.idle}const n=[...e,...this.graphicsPids];const[a,r,c,l]=await Promise.all([t&&n.length?_("ps",["-o","pid=,time=","-p",n.join(",")],{timeoutMs:5e3}):null,t?_("ioreg",["-r","-c","IOAccelerator","-d","1"],{timeoutMs:5e3}):null,_("sysctl",["-n","kern.memorystatus_vm_pressure_level","vm.swapusage"],{timeoutMs:5e3}),_("notifyutil",["-g","com.apple.system.thermalpressurelevel"],{timeoutMs:5e3})]);const d=new Set(this.graphicsPids);let h=0;let f=0;for(const k of a?.stdout.split("\n")??[]){const y=/^\s*(\d+)\s+(\S+)/.exec(k);if(!y)continue;const b=xs(y[2]);if(d.has(Number(y[1])))f+=b;else h+=b}const w=r&&/"Device Utilization %"=(\d+)/.exec(r.stdout);const v=/thermalpressurelevel\s+(\d+)/.exec(l.stdout);return{hostBusyMs:i,hostTotalMs:o,cpuCount:s.length,pidsCpuMs:t?h:null,graphicsCpuMs:t?f:null,gpuPct:w?Number(w[1]):null,memPressureLevel:Number(c.stdout.trim().split("\n")[0])||1,swapUsedMB:Number(/used = ([\d.]+)M/.exec(c.stdout)?.[1]??0),thermalLevel:v?Number(v[1]):null}}async xcodeBuild(){const e=await _("xcodebuild",["-version"],{timeoutMs:2e4});return e.stdout.trim().replace(/\s+/g," ")||"unknown"}async clientsOf(e){const t=await tt();const s=new Set((await this.simTree(e)).map(o=>o.pid));const i=new Set(Ot(t,process.pid).map(o=>o.pid));return t.filter(o=>!i.has(o.pid)&&!s.has(o.pid)&&o.command.includes(e)&&!/launchd_sim|CoreSimulator/.test(o.command))}async kill(e,t){if(!await J(e,t))return;try{process.kill(e,"SIGTERM")}catch{return}for(let s=0;s<20;s++){await new Promise(i=>setTimeout(i,100));if(!await J(e,t))return}if(await J(e,t)){try{process.kill(e,"SIGKILL")}catch{}}}};import Xn from"net";import{chmodSync as Zn,rmSync as ea,appendFileSync as Ai,mkdirSync as ta}from"fs";import{timingSafeEqual as sa}from"crypto";var ia=["android"];var Ri=new Set(["setup","grow","shrink","acquire","release","action","lock","unlock","park","wake","reset","rebuild","delete","endLease","settings","deleteFarm","show","removeTemplate","report","extend","finish","stopRun","stopAll","bench","dropCopies"]);async function Pi(e={}){const t=e.home??A();const s=X(t);if(Buffer.byteLength(s.socket)>103){const n=new p("BAD_REQUEST",`The farm folder's path is too long for macOS (${Buffer.byteLength(s.socket)} of 103 bytes).`,"Set BUOY_FARM_HOME to a shorter folder.");try{ta(t,{recursive:true});Ai(s.log,`${new Date().toISOString()} ${n.message} ${n.next}
|
|
369
|
+
`)}catch{}throw n}const i=await kn(te,e.hostLabel??"node",t);if(!i)return null;const o=n=>{try{Ai(s.log,`${new Date().toISOString()} ${n}
|
|
370
|
+
`)}catch{}e.log?.(n)};try{return await oa(e,t,i,o)}catch(n){i.release();throw n}}async function oa(e,t,s,i){const o=X(t);const n=Vs(t);const a=new ei(t);const r=new Bi({host:e.host??new Zt,store:a,log:i,pollMs:e.pollMs,readyTimeoutMs:e.readyTimeoutMs,phoneBuilder:e.phoneBuilder});await r.recover();ea(o.socket,{force:true});const c=new Set;let l=Date.now();let d=false;let h=0;const f=Xn.createServer(m=>{c.add(m);m.on("close",()=>c.delete(m));m.on("error",()=>{});let u="";m.on("data",g=>{u+=g.toString("utf8");if(u.length>1024*1024)return m.destroy();let S;while((S=u.indexOf("\n"))>=0){const M=u.slice(0,S);u=u.slice(S+1);if(M.trim())void v(m,M)}})});const w=(m,u,g)=>{if(!m.destroyed)m.write(JSON.stringify({id:u,...g})+"\n")};async function v(m,u){l=Date.now();let g;try{g=JSON.parse(u)}catch{return w(m,-1,{ok:false,error:{code:"BAD_REQUEST",message:"Not JSON."}})}h++;try{if(!g||typeof g!=="object")throw new p("BAD_REQUEST","Use a request object.");if(!na(g.token,n))throw new p("UNAUTHORIZED","Bad farm token.");if(!g.client||typeof g.client.clientId!=="string"||!g.client.clientId.trim())throw new p("BAD_REQUEST","Each call needs a clientId.");if(d&&!["hello","status"].includes(g.method)){throw new p("HOST_BUSY","The farm is stopping.","Try again in a few seconds.",true)}if(g.v>te&&Ri.has(g.method)){throw new p("FARMD_VERSION",`This farm runs protocol ${te}; the client speaks ${g.v}.`,"Ask it to stop when idle (method shutdown), then start a newer farm.")}if(g.v<te&&Ri.has(g.method)){throw new p("READ_ONLY",`This client speaks farm protocol ${g.v}; the farm runs ${te}.`,"Update Buoy to change the farm.")}aa(g);const S=await k(g);w(m,g.id,{ok:true,result:S})}catch(S){const M=S instanceof p?S.toJSON():{code:"HOST_ERROR",message:S.message,retryable:false};w(m,g?.id??-1,{ok:false,error:M})}finally{h--}}async function k(m){const u=m.params??{};const g=m.client;const S=M=>r.once(g.clientId,m.requestId,{m:m.method,q:u},M);switch(m.method){case"hello":return{protocol:te,features:ia,pid:process.pid,host:s.info.host,farmId:r.state.farmId};case"status":return r.status({outside:u.outside});case"budget":return r.budget({ramGB:u.ramGB??0,diskGB:u.diskGB??0});case"setup":return S(()=>u.platform==="android"?r.createAndroidTemplate(u):r.createTemplate(u));case"grow":return S(async()=>(await r.grow(u.templateId,u.count)).map(M=>M.name));case"shrink":return S(()=>r.shrink(u.templateId,u.count));case"removeTemplate":return S(()=>r.removeTemplate(u.templateId));case"acquire":return S(async()=>{const{lease:M,run:I,sims:R}=await r.acquire({...u,owner:g});return{runId:I.id,leaseId:M.id,epoch:M.epoch,deadlineAt:I.deadlineAt,capAt:I.capAt,sims:R.map($=>"platform"in $?{udid:$.udid,name:$.name,platform:$.platform,serial:$.serial,buoyDeviceId:$.buoyDeviceId}:{udid:$.udid,name:$.name,buoyDeviceId:$.buoyDeviceId,profile:$.profile})}});case"renew":return r.renew(u.leaseId,u.epoch,g.clientId);case"release":return S(()=>r.release(u.leaseId,u.epoch,u.reset,g.clientId,u.keepRun===true));case"action":return S(()=>r.action(u.leaseId,u.epoch,u.udids,u.action,u.arg,g.clientId));case"lock":return r.lock(u.name,u.leaseId,u.epoch,u.waitMs??0,g.clientId);case"unlock":return r.unlock(u.name,u.leaseId,u.epoch,g.clientId);case"fairness":return r.fairness(u.leaseId,u.epoch,u.windows,g.clientId);case"dropCopies":return S(()=>r.dropCopies(u.leaseId,u.epoch,u.udids,g.clientId));case"bench":return r.copyBench(u.leaseId,u.epoch,u.udid,{seconds:u.seconds,launches:u.launches},g.clientId);case"park":return S(async()=>(await r.parkSim(u.udid,"asked"),"parked"));case"wake":return S(async()=>(await r.bootSim(u.udid)).power);case"reset":return S(async()=>(await r.fastReset(u.udid),"reset"));case"rebuild":return S(async()=>(await r.rebuildSim(u.udid)).udid);case"delete":return S(async()=>(await r.deleteSim(u.udid),"deleted"));case"endLease":{const M=r.state.leases[u.leaseId];if(!M)return{};if(M.owner.clientId!==g.clientId&&u.force!==true){throw new p("NOT_OWNED",`Lease ${u.leaseId} belongs to ${M.owner.label}.`,"Pass force: true only after a person confirms.")}return S(()=>r.endLease(u.leaseId,`ended by ${g.label}`,"full",M.owner.clientId===g.clientId?"released":"user"))}case"report":return S(async()=>r.report(u,g.clientId));case"extend":return S(async()=>r.extend(u.runId,u.minutes,g.clientId));case"finish":return S(async()=>r.finish(u.runId,u.outcome,u.summary,g.clientId));case"stopRun":{const M=r.state.runs[u.runId];if(M&&M.owner.clientId!==g.clientId&&u.force!==true){throw new p("NOT_OWNED",`Run ${u.runId} belongs to ${M.owner.label}.`,"Pass force: true only after a person confirms.")}return S(async()=>r.stopRun(u.runId))}case"stopAll":if(u.force!==true)throw new p("BAD_REQUEST","stopAll needs force: true, after a person confirms.");return S(async()=>r.stopAll());case"settings":return r.setSettings(u);case"shot":return r.shot(u.udid);case"show":return r.show(u.udid);case"shutdown":if(u.ifIdle!==false&&(!r.idle()||h>1))return{stopping:false,reason:"busy"};void N("asked to stop");return{stopping:true};default:throw new p("BAD_REQUEST",`Unknown method ${m.method}.`)}}await new Promise((m,u)=>{f.once("error",u);f.listen(o.socket,()=>m())});Zn(o.socket,384);i(`farmd ${te} up, pid ${process.pid}, host ${e.hostLabel??"node"}`);const y=setInterval(()=>{void r.tick().catch(u=>i(`tick: ${u.message}`));const m=Date.now()-l;if(e.idleExitMs&&c.size===0&&m>e.idleExitMs){void(async()=>{await r.parkAllIdle();if(r.idle()&&c.size===0&&h===0&&Date.now()-l>e.idleExitMs)await N("idle")})()}},e.tickMs??5e3);y.unref?.();const b=setInterval(()=>void r.meter.tick().catch(m=>i(`meter: ${m.message}`)),1e3);b.unref?.();let E=null;function N(m,u=10*6e4){if(!E)E=D(m,u);return E}async function D(m,u){if(d)return;d=true;clearInterval(y);clearInterval(b);i(`farmd stopping: ${m}`);const g=Date.now()+u;while(h>0&&Date.now()<g)await new Promise(I=>setTimeout(I,200));const S=await r.drain(Math.max(0,g-Date.now()));let M=false;if(h>0||!S||Bt()>0){a.save();r.fence();M=true;i(`farmd stopping with ${h} call(s) and ${Bt()} child process(es) still running; killing them`);Io();const I=Date.now()+5e3;while(Bt()>0&&Date.now()<I)await new Promise(R=>setTimeout(R,100))}await r.settleFiles();if(!M&&h===0){try{await r.parkAllIdle()}catch{}}for(const I of c)I.destroy();await new Promise(I=>f.close(()=>I()));if(!M){a.save();r.fence()}s.release();e.onExit?.(m)}return{engine:r,owner:s,stop:N}}function na(e,t){if(typeof e!=="string"||e.length!==t.length)return false;return sa(Buffer.from(e),Buffer.from(t))}function aa(e){const t=a=>{throw new p("BAD_REQUEST",a)};const s=e.params??{};if(typeof s!=="object"||Array.isArray(s))t("Params must be an object.");const i=(a,r=false)=>{if(r&&s[a]===void 0)return;if(typeof s[a]!=="string"||!s[a].trim())t(`${a} must be text.`)};const o=a=>{if(s[a]!==void 0&&typeof s[a]!=="boolean")t(`${a} must be true or false.`)};const n=(a,r,c,l=false,d=false)=>{const h=s[a];if(d&&h===void 0)return;if(typeof h!=="number"||!Number.isFinite(h)||h<r||h>c||l&&!Number.isInteger(h))t(`${a} must be a number from ${r} to ${c}.`)};switch(e.method){case"status":o("outside");break;case"budget":n("ramGB",0,Number.MAX_VALUE,false,true);n("diskGB",0,Number.MAX_VALUE,false,true);break;case"setup":if(s.platform==="android"){i("apk");i("phoneId",true);i("launchUrl",true);if(s.reverse!==void 0&&(!Array.isArray(s.reverse)||s.reverse.some(a=>typeof a!=="string"||!/^\d{1,5}(:\d{1,5})?$/.test(a))))t('reverse must be a list like ["8081:8802"].');if(s.image!==void 0&&(typeof s.image!=="object"||!["aosp_atd","google_atd"].includes(s.image?.tag)))t("image tag must be aosp_atd or google_atd.");break}for(const a of["appPath","deviceType","runtime"])i(a);if(s.seedDir!=null)i("seedDir");i("name",true);i("launchUrl",true);o("warmLaunch");if(!["slim","stock"].includes(s.profile))t("Use slim or stock.");n("metroPort",1,65535,true);break;case"removeTemplate":i("templateId");break;case"grow":case"shrink":case"acquire":i("templateId");n("count",1,64,true);if(e.method==="acquire"){i("purpose");n("ttlMs",1,Number.MAX_VALUE,false,true);n("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);o("launch");o("allowPartial");i("goal",true);i("runId",true);i("kind",true);if(s.labels!==void 0&&(!Array.isArray(s.labels)||s.labels.some(a=>typeof a!=="string")))t("labels must be a list of text.");if(!Number.isInteger(e.client.pid)||e.client.pid<=0)t("The client needs a valid pid.")}break;case"release":if(s.reset!==void 0&&!["fast","full"].includes(s.reset))t("Use fast or full reset.");break;case"action":if(!Array.isArray(s.udids)||s.udids.some(a=>typeof a!=="string"||!a))t("udids must be a list of sim ids.");if(!["reset","rebuild","open-url","screenshot","launch","install"].includes(s.action))t("Unknown sim action.");i("arg",s.action!=="open-url"&&s.action!=="install");break;case"lock":case"unlock":i("name");if(!/^[a-z0-9-]{1,40}$/.test(s.name))t("Lock names are short lowercase words.");n("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);break;case"fairness":if(!Array.isArray(s.windows)||s.windows.length>500||s.windows.some(a=>!a||typeof a.id!=="string"||!Number.isFinite(a.from)||!Number.isFinite(a.to)||a.to<a.from)){t("windows must be a list of { id, from, to } times in ms.")}break;case"endLease":i("leaseId");break;case"report":i("runId");i("udid",true);i("note",true);n("sequence",0,Number.MAX_SAFE_INTEGER,true,true);if(s.state!==void 0&&typeof s.state!=="string")t("state must be text.");break;case"extend":i("runId");n("minutes",1,24*60);break;case"dropCopies":if(!Array.isArray(s.udids)||!s.udids.length||s.udids.some(a=>typeof a!=="string"||!a))t("udids must be a list of copy ids.");break;case"bench":i("leaseId");i("udid");n("seconds",1,600,false,true);n("launches",0,20,true,true);break;case"finish":i("runId");i("summary",true);i("outcome",true);break;case"stopRun":i("runId");o("force");break;case"stopAll":o("force");break;case"shutdown":o("ifIdle");break}if(["renew","release","action","lock","unlock","fairness","dropCopies"].includes(e.method))i("leaseId")}import ra from"net";import{existsSync as ca}from"fs";import{spawn as la}from"child_process";import{randomBytes as Ni}from"crypto";function es(e,t=["farmd"]){return()=>{const s=la(process.execPath,[...process.execArgv,e,...t],{detached:true,stdio:"ignore",env:st()});s.unref()}}var Ci=class{constructor(e){this.opts=e;this.sock=null;this.nextId=1;this.pending=new Map;this.buf="";this.renewals=new Map;this.reconnecting=null;this.features=[];this.askedOlderToStop=false;this.closed=false;this.home=e.home??A();this.clientIdValue=e.clientId??`c-${Ni(5).toString("hex")}`}get clientId(){return this.owner?.clientId}require(e){if(!this.features.includes(e)){throw new p("FARMD_VERSION",`The farm running on this Mac is too old for ${e}.`,"Update Buoy Desktop, or quit it so a newer farm can start, then try again.")}}async connect(){this.token=Vs(this.home);this.owner={clientId:this.clientIdValue,pid:process.pid,pidStart:await Ie(process.pid)??void 0,label:this.opts.label,...this.opts.person?{kind:"person"}:{},...this.opts.agent?{agent:this.opts.agent}:{}};if(await this.tryOpen())return this.checkVersion();const e=await qe(this.home);if(!await Ye(e)){if(!this.opts.launcher)throw new p("HOST_ERROR","The farm is not running.","Start it from Buoy Desktop or `pnpm farm status`.");this.opts.launcher()}const t=Date.now()+(this.opts.startTimeoutMs??2e4);while(Date.now()<t){await T(250);if(await this.tryOpen())return this.checkVersion()}throw new p("HOST_ERROR","The farm did not start in time.",`Check ${X(this.home).log}.`)}async checkVersion(){const e=await this.send("hello",void 0,{});this.features=e.features??[];const t=e.protocol<te||!this.features.includes("android")&&!this.askedOlderToStop;if(t&&this.opts.launcher){this.askedOlderToStop=true;const s=await this.send("shutdown",{ifIdle:true},{});if(s.stopping){this.close();await T(500);return this.connect()}}return this}tryOpen(){const e=X(this.home).socket;if(!ca(e))return Promise.resolve(false);return new Promise(t=>{const s=ra.createConnection(e);s.once("connect",()=>{this.sock=s;s.on("data",i=>this.onData(i));s.on("close",()=>this.onClose());s.on("error",()=>{});t(true)});s.once("error",()=>t(false))})}onData(e){this.buf+=e.toString("utf8");let t;while((t=this.buf.indexOf("\n"))>=0){const s=this.buf.slice(0,t);this.buf=this.buf.slice(t+1);let i;try{i=JSON.parse(s)}catch{continue}const o=this.pending.get(i.id);if(!o)continue;this.pending.delete(i.id);clearTimeout(o.timer);if(i.ok)o.resolve(i.result);else{const n=i.error??{};o.reject(new p(n.code??"HOST_ERROR",n.message??"farm error",n.next,n.retryable))}}}onClose(){this.sock=null;for(const[,e]of this.pending){clearTimeout(e.timer);e.reject(new p("HOST_ERROR","Lost the connection to the farm.",void 0,true))}this.pending.clear()}reconnect(){if(!this.reconnecting){this.reconnecting=this.connect().then(()=>void 0).finally(()=>{this.reconnecting=null})}return this.reconnecting}async call(e,t,s={}){if(!this.sock&&!this.closed)await this.reconnect();return this.send(e,t,s)}send(e,t,s){if(!this.sock)return Promise.reject(new p("HOST_ERROR","Not connected to the farm.",void 0,true));const i=this.nextId++;const o={id:i,v:te,token:this.token,client:this.owner,method:e,params:t,requestId:s.requestId};return new Promise((n,a)=>{const r=setTimeout(()=>{this.pending.delete(i);a(new p("HOST_ERROR",`Farm call ${e} timed out.`,void 0,true))},s.timeoutMs??3e4);this.pending.set(i,{resolve:n,reject:a,timer:r});this.sock.write(JSON.stringify(o)+"\n")})}async acquire(e){const{requestId:t,...s}=e;const i=t??`acq-${Ni(4).toString("hex")}`;const o=await this.call("acquire",s,{timeoutMs:15*6e4+(e.waitMs??0),requestId:i});if(this.renewals.has(o.leaseId)||this.opts.person)return o;const n=setInterval(()=>{this.call("renew",{leaseId:o.leaseId,epoch:o.epoch}).catch(a=>{if(["LEASE_EXPIRED","STALE_EPOCH","RUN_STOPPED","RUN_ENDED"].includes(a.code))this.stopRenew(o.leaseId)})},15e3);n.unref?.();this.renewals.set(o.leaseId,n);return o}stopRenew(e){const t=this.renewals.get(e);if(t)clearInterval(t);this.renewals.delete(e)}async release(e,t,s="fast",i=false){this.stopRenew(e);return this.call("release",{leaseId:e,epoch:t,reset:s,keepRun:i},{timeoutMs:6e4,requestId:`rel-${e}-${t}`})}close(){this.closed=true;for(const e of[...this.renewals.keys()])this.stopRenew(e);this.sock?.end();this.sock=null}};async function ts(e){return new Ci(e).connect()}import Qe from"path";import da from"os";import{existsSync as ss,readdirSync as pa,statSync as ua}from"fs";function Ve(e=process.cwd()){let t=e;for(;;){if(ss(Qe.join(t,"buoy.tools.json")))return t;const s=Qe.dirname(t);if(s===t)return null;t=s}}function ut(){const e=Qe.join(da.homedir(),"Library/Developer/Xcode/DerivedData");let t=null;try{for(const s of pa(e)){if(!s.startsWith("exampledevbuild-"))continue;const i=Qe.join(e,s,"Build/Products/Debug-iphonesimulator/exampledevbuild.app");if(!ss(i))continue;const o=ua(i).mtimeMs;if(!t||o>t.t)t={p:i,t:o}}}catch{}return t?.p??null}function xi(e=Ve()){return e?Qe.join(e,"example-dev-build/artifacts/pool-seed"):null}async function ha(e=Ve()){const t=xi(e);let s=[];let i=[];try{const o=JSON.parse(await L("xcrun",["simctl","list","runtimes","--json"],{timeoutMs:3e4}));s=(o.runtimes??[]).filter(a=>a.isAvailable!==false&&/^iOS/.test(a.name??"")).map(a=>({id:a.identifier,name:a.name}));const n=JSON.parse(await L("xcrun",["simctl","list","devicetypes","--json"],{timeoutMs:3e4}));i=(n.devicetypes??[]).filter(a=>/iPhone|iPad/.test(a.name??"")).map(a=>({id:a.identifier,name:a.name}))}catch{}return{appPath:ut(),seedDir:t&&ss(t)?t:null,seedProblem:t?Le(t):"Could not find the repo's seed folder.",runtimes:s,deviceTypes:i,slimTurnsOff:Ce.map(o=>({name:o.name,downside:o.downside}))}}import ma from"path";var Li=[{app:"claude",name:"Claude",match:/(^|\/)claude$/i},{app:"codex",name:"Codex",match:/(^|\/)codex$/i},{app:"cursor",name:"Cursor",match:/cursor/i}];async function fa(e){const t=await _("ps",["-o","ppid=,comm=","-p",String(e)],{timeoutMs:3e3});const s=/^\s*(\d+)\s+(.+?)\s*$/.exec(t.stdout);return t.code===0&&s?{ppid:Number(s[1]),comm:s[2]}:null}async function wa(e){const t=await _("lsof",["-a","-p",String(e),"-d","cwd","-Fn"],{timeoutMs:3e3});const s=t.stdout.split("\n").find(i=>i.startsWith("n"));return s?s.slice(1):null}function ga(e){if(!e)return void 0;return Li.find(t=>e.toLowerCase().includes(t.app))}async function ya(e={}){const t=e.env??process.env;let s=e.pid??process.pid;let i=null;for(let d=0;d<5&&s>1;d++){const h=await fa(s).catch(()=>null);if(!h)break;const f=Li.find(w=>w.match.test(h.comm));if(d>0&&f){i={app:f,pid:s};break}s=h.ppid}const o=i?.app??ga(e.clientName);const n=(i?await wa(i.pid).catch(()=>null):null)??process.cwd();const a=(await _("git",["-C",n,"rev-parse","--abbrev-ref","HEAD"],{timeoutMs:3e3}).catch(()=>null))?.stdout.trim();const r=t.CLAUDE_CODE_SESSION_ID?.trim();let c;let l="none";if(r&&(o?.app??"claude")==="claude"){c=`claude:${r}`;l="claude-session"}else if(i){c=`${i.app.app}:${i.pid}:${await Ie(i.pid)??""}`;l="parent-process"}return{app:o?.app??"other",appName:o?.name??(e.clientName||"Agent"),chatKey:c,chatKeySource:l,project:ma.basename(n)||void 0,projectPath:n,branch:a&&a!=="HEAD"?a:void 0,clientName:e.clientName}}function va(e){return e.project?`${e.appName} \xB7 ${e.project}`:e.appName}import Ui from"path";import{existsSync as Sa}from"fs";import ba from"os";import{execFile as ka,spawn as Ma}from"child_process";import{createHash as _a}from"crypto";import{promises as ee}from"fs";import ht from"os";import Q from"path";import{promisify as Ea}from"util";var be=Ea(ka);var mt="gg.buoy.phoneprobe";var ft=1024*1024;function $a(e){const t=e.match(/BUOY_PROBE physicalMemory=(\d+) cpus=(\d+) footprint=(\d+) available=(\d+)/);if(!t)return null;const[s,i,o,n]=t.slice(1).map(Number);const a=o+n;if(!s||!i||n<=0||a>s)return null;return{ramMB:Math.floor(s/ft),cpus:i,limitMB:Math.floor(a/ft)}}function ji(e,t){return e.match(new RegExp(`<key>${t}</key>\\s*<string>([^<]*)</string>`))?.[1]}function is(e,t,s){const i=e.match(new RegExp(`<key>${t}</key>\\s*<array>([\\s\\S]*?)</array>`))?.[1]??"";return[...i.matchAll(new RegExp(`<${s}>([\\s\\S]*?)</${s}>`,"g"))].map(o=>o[1].replace(/\s+/g,""))}function Ia(e,t){const s=t.match(/<key>Entitlements<\/key>\s*<dict>([\s\S]*?)<\/dict>/)?.[1]??"";const i=ji(s,"application-identifier");const o=is(t,"TeamIdentifier","string")[0];const n=t.match(/<key>ExpirationDate<\/key>\s*<date>([^<]+)<\/date>/)?.[1];if(!i||!o||!n)return null;return{file:e,name:ji(t,"Name")??Q.basename(e),team:o,appId:i,expires:new Date(n),devices:is(t,"ProvisionedDevices","string"),allDevices:/<key>ProvisionsAllDevices<\/key>\s*<true\/>/.test(t),getTaskAllow:/<key>get-task-allow<\/key>\s*<true\/>/.test(s),certSha1s:is(t,"DeveloperCertificates","data").map(a=>_a("sha1").update(Buffer.from(a,"base64")).digest("hex").toUpperCase())}}function Ba(e,t,s,i=new Date){const o=e.filter(a=>a.getTaskAllow&&a.expires>i&&(a.allDevices||a.devices.includes(t))&&(a.appId===`${a.team}.*`||a.appId===`${a.team}.${mt}`)&&a.certSha1s.some(r=>s.has(r)));o.sort((a,r)=>Number(r.appId.endsWith(".*"))-Number(a.appId.endsWith(".*"))||r.expires.getTime()-a.expires.getTime());const n=o[0];return n?{profile:n,identity:n.certSha1s.find(a=>s.has(a))}:null}async function Oa(){const e=[Q.join(ht.homedir(),"Library/Developer/Xcode/UserData/Provisioning Profiles"),Q.join(ht.homedir(),"Library/MobileDevice/Provisioning Profiles")];const t=[];for(const s of e){let i=[];try{i=(await ee.readdir(s)).filter(o=>o.endsWith(".mobileprovision"))}catch{continue}for(const o of i){const n=Q.join(s,o);try{const{stdout:a}=await be("security",["cms","-D","-i",n],{timeout:15e3,maxBuffer:16*ft});const r=Ia(n,a);if(r)t.push(r)}catch{}}}return t}async function Ta(){const{stdout:e}=await be("security",["find-identity","-v","-p","codesigning"],{timeout:15e3});return new Set([...e.matchAll(/^\s*\d+\)\s+([0-9A-F]{40})\s/gm)].map(t=>t[1]))}async function Da(){const e=await ee.mkdtemp(Q.join(ht.tmpdir(),"buoy-phones-"));try{const t=Q.join(e,"devices.json");await be("xcrun",["devicectl","list","devices","--json-output",t],{timeout:3e4});const s=JSON.parse(await ee.readFile(t,"utf8"))?.result?.devices??[];return s.filter(i=>i.hardwareProperties?.reality==="physical"&&i.hardwareProperties?.deviceType==="iPhone"&&i.connectionProperties?.pairingState==="paired").map(i=>({udid:i.hardwareProperties.udid,name:i.hardwareProperties.marketingName??"iPhone",productType:i.hardwareProperties.productType,ios:i.deviceProperties?.osVersionNumber??""}))}finally{await ee.rm(e,{recursive:true,force:true}).catch(()=>void 0)}}function Aa(e,t,s){const i=Object.keys(e).find(n=>e[n].productType===t);if(i)return i;const o=n=>n.toLowerCase().replace(/[()]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");return`${o(s)}-${o(t)}`.slice(0,64).replace(/-$/,"")}async function Ra(e,t,s,i){const o=Q.join(e,"PhoneProbe.app");await ee.mkdir(o,{recursive:true});const n=Q.join(e,"PhoneProbe.m");await ee.writeFile(n,xn);await be("xcrun",["--sdk","iphoneos","clang","-arch","arm64","-miphoneos-version-min=13.0","-fobjc-arc","-O2","-framework","Foundation","-framework","UIKit",n,"-o",Q.join(o,"PhoneProbe")],{timeout:18e4,maxBuffer:8*ft,env:{...process.env,TMPDIR:e}});const a=mt;await ee.writeFile(Q.join(o,"Info.plist"),`<?xml version="1.0" encoding="UTF-8"?>
|
|
371
371
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
372
372
|
<plist version="1.0"><dict>
|
|
373
373
|
<key>CFBundleIdentifier</key><string>${a}</string>
|
|
@@ -390,8 +390,8 @@ static void buoyPhoneProfileInit(void) {
|
|
|
390
390
|
<key>com.apple.developer.team-identifier</key><string>${t}</string>
|
|
391
391
|
<key>get-task-allow</key><true/>
|
|
392
392
|
</dict></plist>
|
|
393
|
-
`);await be("codesign",["-f","-s",i,"--entitlements",r,o],{timeout:6e4});return o}function
|
|
394
|
-
${s.map(a=>` ${a.udid} ${a.name}`).join("\n")}`);const i=s[0];t(`Measuring ${i.name} (${i.productType}, iOS ${i.ios})...`);const o=
|
|
393
|
+
`);await be("codesign",["-f","-s",i,"--entitlements",r,o],{timeout:6e4});return o}function Pa(e,t=45e3){return new Promise(s=>{const i=Ma("xcrun",["devicectl","device","process","launch","--device",e,"--terminate-existing","--console",mt]);let o="";let n=false;const a=()=>{if(n)return;n=true;clearTimeout(r);i.kill("SIGTERM");s(o)};const r=setTimeout(a,t);const c=l=>{o+=l.toString();if(/BUOY_PROBE .*available=\d+/.test(o))setTimeout(a,300)};i.stdout.on("data",c);i.stderr.on("data",c);i.on("close",a);i.on("error",a)})}async function Fi(e={}){const t=e.log??(()=>{});const s=(await Da()).filter(a=>!e.device||a.udid===e.device);if(s.length===0)throw new Error(e.device?`No paired iPhone ${e.device}.`:"No paired iPhone. Plug one in, unlock it and trust this Mac.");if(s.length>1)throw new Error(`More than one iPhone. Pick one with --device:
|
|
394
|
+
${s.map(a=>` ${a.udid} ${a.name}`).join("\n")}`);const i=s[0];t(`Measuring ${i.name} (${i.productType}, iOS ${i.ios})...`);const o=Ba(await Oa(),i.udid,await Ta());if(!o){throw new Error(`No provisioning profile on this Mac can install the measuring app on this iPhone. It needs a development profile for any app ("TEAM.*"), which comes with a paid Apple Developer team: in Xcode, sign in with that team and run any app with automatic signing on this phone once. Free Apple accounts only get profiles for one app, and they can't be used here.`)}const n=await ee.mkdtemp(Q.join(ht.tmpdir(),"buoy-probe-"));try{const a=await Ra(n,o.profile.team,o.profile.file,o.identity);await be("xcrun",["devicectl","device","install","app","--device",i.udid,a],{timeout:12e4});let r=null;try{const w=await Pa(i.udid);r=$a(w);if(!r){const v=/locked/i.test(w)?" The phone looks locked: unlock it and try again.":"";throw new Error(`Could not read the phone.${v} Output: ${w.trim().slice(-300)}`)}}finally{await be("xcrun",["devicectl","device","uninstall","app","--device",i.udid,mt],{timeout:6e4}).catch(()=>void 0)}const c=Oe();const l=Aa(c,i.productType,i.name);const d=c[l];const h=d?.warnSource?.startsWith("measured")&&d.warnMB<r.limitMB;const f={name:d?.name??i.name,productType:i.productType,ramMB:r.ramMB,cpus:r.cpus,limitMB:r.limitMB,warnMB:h?d.warnMB:Math.round(r.limitMB*.8),warnSource:h?d.warnSource:"80% of the limit (not measured)",ios:i.ios,measuredAt:new Date().toISOString().slice(0,10)};await fi(l,f);return{id:l,phone:f,previous:d}}finally{await ee.rm(n,{recursive:true,force:true}).catch(()=>void 0)}}import{copyFile as Na}from"fs/promises";import os from"os";import Re from"path";var ns=e=>`Usage: ${e} android <command>
|
|
395
395
|
|
|
396
396
|
Many slim Android phones on one computer. Each copy starts from a saved
|
|
397
397
|
snapshot in seconds and draws with your Mac's GPU.
|
|
@@ -431,13 +431,13 @@ Copies run in the Sim Farm service, so Buoy Desktop's Sims panel and the
|
|
|
431
431
|
Buoy MCP see them too. The farm runs its own adb server on port
|
|
432
432
|
${de}, so other tools can't drop its copies. To use plain adb with them:
|
|
433
433
|
ANDROID_ADB_SERVER_PORT=${de} adb devices
|
|
434
|
-
`;function ie(e,t){const s=e.indexOf(`--${t}`);if(s<0)return void 0;const i=e[s+1];if(!i||i.startsWith("--"))throw new p("BAD_REQUEST",`--${t} needs a value.`);return i}function Gi(e,t){const s=[];e.forEach((i,o)=>{if(i===`--${t}`){const n=e[o+1];if(!n||n.startsWith("--"))throw new p("BAD_REQUEST",`--${t} needs a value.`);s.push(n)}});return s}function Pe(e,t){const s=[];for(let i=0;i<e.length;i++){if(e[i].startsWith("--")){if(t.includes(e[i].slice(2)))i++;continue}s.push(e[i])}return s}function as(e){const t=ie(e,"image")??ne.tag;if(t!=="aosp_atd"&&t!=="google_atd")throw new p("BAD_REQUEST","Use --image aosp_atd or google_atd.");return{...ne,tag:t}}function Wi(e){const t=ie(e,"sdk")??"auto";if(t!=="auto"&&t!=="buoy"&&t!=="system")throw new p("BAD_REQUEST","Use --sdk auto, buoy or system.");return t}function rs(e){const t=e.usable?"ready":e.note??"not ready";const s=e.copyReadyMs?`, copies ready in ${(e.copyReadyMs/1e3).toFixed(1)} s`:"";const i=e.usable&&e.roomFor!=null?`, room for ${e.roomFor} more (${e.costGB} GB each)`:"";return`Template ${e.id}: ${e.packageName||Re.basename(e.apk)} as ${e.phone.name} (${e.image.tag}, Android ${e.image.api}): ${t}${s}${i}`}var wt=`p-${os.userInfo().username}`;async function ke(e){const t=await ts({label:`You (${os.userInfo().username})`,clientId:wt,person:true,launcher:e?es(e,["farmd"]):void 0});t.require("android");return t}function
|
|
434
|
+
`;function ie(e,t){const s=e.indexOf(`--${t}`);if(s<0)return void 0;const i=e[s+1];if(!i||i.startsWith("--"))throw new p("BAD_REQUEST",`--${t} needs a value.`);return i}function Gi(e,t){const s=[];e.forEach((i,o)=>{if(i===`--${t}`){const n=e[o+1];if(!n||n.startsWith("--"))throw new p("BAD_REQUEST",`--${t} needs a value.`);s.push(n)}});return s}function Pe(e,t){const s=[];for(let i=0;i<e.length;i++){if(e[i].startsWith("--")){if(t.includes(e[i].slice(2)))i++;continue}s.push(e[i])}return s}function as(e){const t=ie(e,"image")??ne.tag;if(t!=="aosp_atd"&&t!=="google_atd")throw new p("BAD_REQUEST","Use --image aosp_atd or google_atd.");return{...ne,tag:t}}function Wi(e){const t=ie(e,"sdk")??"auto";if(t!=="auto"&&t!=="buoy"&&t!=="system")throw new p("BAD_REQUEST","Use --sdk auto, buoy or system.");return t}function rs(e){const t=e.usable?"ready":e.note??"not ready";const s=e.copyReadyMs?`, copies ready in ${(e.copyReadyMs/1e3).toFixed(1)} s`:"";const i=e.usable&&e.roomFor!=null?`, room for ${e.roomFor} more (${e.costGB} GB each)`:"";return`Template ${e.id}: ${e.packageName||Re.basename(e.apk)} as ${e.phone.name} (${e.image.tag}, Android ${e.image.api}): ${t}${s}${i}`}var wt=`p-${os.userInfo().username}`;async function ke(e){const t=await ts({label:`You (${os.userInfo().username})`,clientId:wt,person:true,launcher:e?es(e,["farmd"]):void 0});t.require("android");return t}function Ca(e,t){const s=e.android?.copies.find(i=>i.serial===t||i.udid===t);if(!s)throw new p("NOT_FOUND",`${t} is not a running farm copy.`,"See them with: farm android list");return{copy:s,lease:e.leases.find(i=>i.id===s.leaseId)}}function cs(e,t){const{copy:s,lease:i}=Ca(e,t);if(!i||i.owner.clientId!==wt){throw new p("NOT_OWNED",`${s.serial} belongs to ${i?.owner.label??"no one"}.`,"Stop that run in Buoy Desktop's Sims panel.")}return{copy:s,lease:i}}async function Hi(e,t,s){const[i,...o]=e;const n=c=>console.log(c);const a=ze();const r=async c=>c.call("status");switch(i){case void 0:case"help":case"--help":return n(ns(t));case"setup":{const c=as(o);const l=Wi(o);const d=Ge(a,c,l);if(d?.hasImage&&l!=="buoy"){n(`Ready. Using the ${d.source==="buoy"?"farm's own":"existing"} Android SDK at ${d.root} (emulator ${d.emulatorVersion}).`);return n(`Next: ${t} android template --apk <your app.apk>`)}if(d?.hasImage){return n(`Ready. The farm's own SDK is at ${d.root} (emulator ${d.emulatorVersion}).`)}const h=await on(c);if(!o.includes("--accept-license")){for(const[v,k]of Object.entries(h.licenses))n(`---- ${v} ----
|
|
435
435
|
${k}
|
|
436
436
|
`);n(`The farm will download:
|
|
437
437
|
${h.archives.map(v=>` ${v.name} (${Math.round(v.size/1024**2)} MB)`).join("\n")}`);return n(`
|
|
438
438
|
These come from Google under the licenses above. To accept them and download, run:
|
|
439
|
-
${t} android setup --sdk buoy --accept-license`)}const f=Date.now();const w=await
|
|
440
|
-
Use it with: ${t} android template --apk <app.apk> --phone ${v}`)}throw new p("BAD_REQUEST",`Unknown phone command ${c}.`,"Try: phone list, phone measure")}case"doctor":{const c=as(o);const l=Ge(a,c,Wi(o));const d=await
|
|
439
|
+
${t} android setup --sdk buoy --accept-license`)}const f=Date.now();const w=await nn(a,h,v=>console.error(v));n(`Ready in ${((Date.now()-f)/1e3).toFixed(1)} s. Emulator ${w.emulatorVersion} at ${w.root}.`);return n(`Next: ${t} android template --apk <your app.apk>`)}case"template":{const c=ie(o,"apk");if(!c)throw new p("BAD_REQUEST","template needs --apk <app.apk>.");const l=await ke(s);const d=Date.now();const h=new Set((await r(l)).recentEvents.map(w=>w.at+w.message));const f=setInterval(()=>void r(l).then(w=>{for(const v of w.recentEvents){if(h.has(v.at+v.message))continue;h.add(v.at+v.message);if(v.kind==="template-progress")console.error(v.message)}}).catch(()=>{}),2e3);try{const w=await l.call("setup",{platform:"android",apk:Re.resolve(c),phoneId:ie(o,"phone"),image:as(o),reverse:Gi(o,"reverse"),launchUrl:ie(o,"launch-url")},{timeoutMs:30*6e4,requestId:`tpl-${Date.now()}`});n(rs(w));n(`Built in ${((Date.now()-d)/1e3).toFixed(0)} s. Start copies with: ${t} android up 2`)}finally{clearInterval(f);l.close()}return}case"up":{const[c]=Pe(o,["template","reverse"]);const l=Number(c??1);if(!Number.isInteger(l)||l<1)throw new p("BAD_REQUEST","up needs a count of 1 or more.");if(Gi(o,"reverse").length)throw new p("BAD_REQUEST","Ports are set on the template now.",`Make it with: ${t} android template --apk <app.apk> --reverse 8081:8802`);const d=await ke(s);try{const h=await r(d);const f=ie(o,"template")?h.android?.templates.find(y=>y.id===ie(o,"template")):h.android?.templates.filter(y=>y.usable).sort((y,b)=>y.createdAt.localeCompare(b.createdAt)).pop();if(!f)throw new p("NO_TEMPLATE","No Android template yet.",`Make one with: ${t} android template --apk <app.apk>`);const w=Date.now();const v=await d.acquire({templateId:f.id,count:l,purpose:"farm android up",goal:`${l} Android cop${l===1?"y":"ies"} from the CLI`,ttlMs:24*36e5,launch:o.includes("--launch")});const k=await r(d);for(const y of v.sims){const b=k.android?.copies.find(E=>E.udid===y.udid);n(`${y.serial} ${y.name} ready in ${((b?.readyMs??0)/1e3).toFixed(1)} s (${b?.bootedFrom??"?"})`)}return n(`${v.sims.length} cop${v.sims.length===1?"y":"ies"} up in ${((Date.now()-w)/1e3).toFixed(1)} s. They stay up until: ${t} android down --all`)}finally{d.close()}}case"list":{const c=await ke(s);try{const l=await r(c);const d=l.android?.templates??[];const h=l.android?.copies??[];if(o.includes("--json"))return n(JSON.stringify({adbPort:de,templates:d,copies:h},null,2));if(!d.length)n(`No templates. Make one with: ${t} android template --apk <app.apk>`);for(const f of d)n(rs(f));for(const f of h)n(` ${f.serial} ${f.name} ${f.state} ${f.owner?`used by ${f.owner}`:"free"} template ${f.templateId}`);return n(`adb server: ANDROID_ADB_SERVER_PORT=${de}`)}finally{c.close()}}case"down":{const c=await ke(s);try{const l=await r(c);const d=o.includes("--all");const h=d?(l.android?.copies??[]).filter(k=>l.leases.find(y=>y.id===k.leaseId)?.owner.clientId===wt).map(k=>k.serial):Pe(o,[]);if(!d&&!h.length)throw new p("BAD_REQUEST","down needs serials or --all.");const f=new Map;for(const k of h){const{copy:y,lease:b}=cs(l,k);const E=f.get(b.id)??{lease:b,copies:[]};E.copies.push(y);f.set(b.id,E)}const w=[];for(const{lease:k,copies:y}of f.values()){await c.call("dropCopies",{leaseId:k.id,epoch:k.epoch,udids:y.map(b=>b.udid)},{timeoutMs:12e4,requestId:`drop-${k.id}-${y.map(b=>b.udid).join(",")}`});w.push(...y.map(b=>b.serial))}await Promise.all(w.map(k=>_(ls(),["disconnect",`127.0.0.1:${Number(k.split("-")[1])+1}`],{timeoutMs:1e4})));const v=(l.android?.copies.length??0)-h.length;n(w.length?`Closed ${w.join(", ")}.`:"You had no copies running.");if(d&&v>0)n(`${v} other cop${v===1?"y is":"ies are"} in use by agents. Stop those runs in Buoy Desktop.`);return}finally{c.close()}}case"launch":case"shot":case"install":case"bench":{const c=await ke(s);try{const l=await r(c);const d=Pe(o,["url","seconds"]);if(i==="install"){const[b,...E]=d;if(!b)throw new p("BAD_REQUEST","install needs an APK.");const N=o.includes("--all")?(l.android?.copies??[]).filter(m=>l.leases.find(u=>u.id===m.leaseId)?.owner.clientId===wt).map(m=>m.serial):E;if(!N.length)throw new p("BAD_REQUEST","install needs serials or --all.");const D=Date.now();for(const m of N){const{copy:u,lease:g}=cs(l,m);await c.call("action",{leaseId:g.id,epoch:g.epoch,udids:[u.udid],action:"install",arg:Re.resolve(b)},{timeoutMs:20*6e4})}return n(`Installed on ${N.length} cop${N.length===1?"y":"ies"} in ${((Date.now()-D)/1e3).toFixed(1)} s.`)}const[h,f]=d;if(!h)throw new p("BAD_REQUEST",`${i} needs a serial.`);const{copy:w,lease:v}=cs(l,h);const k=(b,E)=>c.call("action",{leaseId:v.id,epoch:v.epoch,udids:[w.udid],action:b,arg:E},{timeoutMs:5*6e4});if(i==="launch"){const b=(await k("launch",ie(o,"url")))[w.udid];return n(typeof b==="object"&&b.startMs!=null?`Started in ${b.startMs} ms.`:"Launched.")}if(i==="shot"){if(!f)throw new p("BAD_REQUEST","shot needs a serial and a file.");const b=(await k("screenshot"))[w.udid];if(!b)throw new p("HOST_ERROR",`No screenshot from ${w.serial}.`);await Na(b,Re.resolve(f));return n(`Saved ${Re.resolve(f)}.`)}const y=await c.call("bench",{leaseId:v.id,epoch:v.epoch,udid:w.udid,seconds:Number(ie(o,"seconds")??15)},{timeoutMs:10*6e4});if(o.includes("--json"))return n(JSON.stringify(y,null,2));n(`App start: ${y.appStartMs??"?"} ms (runs: ${y.appStarts.join(", ")})`);n(`Frames: ${y.frames.frames}, median ${y.frames.p50??"?"} ms, 90% ${y.frames.p90??"?"} ms, 99% ${y.frames.p99??"?"} ms, janky ${y.frames.jankyPct??"?"}%`);return n(`App memory: ${y.appMemoryMB} MB. Mac memory for this copy: ${y.macMemoryMB??"?"} MB. Mac CPU: ${y.macCpuPct??"?"}%`)}finally{c.close()}}case"adb":{const[c,...l]=o;if(!c)throw new p("BAD_REQUEST","adb needs a serial.");const d=qt(c);if(!d)throw new p("NOT_FOUND",`${c} is not a running farm copy.`);const h=await _(d.adb,["-P",String(d.port),"-s",c,...l],{timeoutMs:10*6e4});process.stdout.write(h.stdout);process.stderr.write(h.stderr);if(h.code)process.exitCode=h.code;return}case"share":{const[c]=Pe(o,[]);if(!c||!qt(c))throw new p("BAD_REQUEST",`${c??"That"} is not a running farm copy.`);const l=Number(c.split("-")[1])+1;const d=await _(ls(),["connect",`127.0.0.1:${l}`],{timeoutMs:2e4});if(!/connected to/.test(d.stdout))throw new p("HOST_ERROR",`adb connect failed: ${(d.stdout+d.stderr).trim()}`);return n(`${c} is now also 127.0.0.1:${l} on your normal adb server.`)}case"remove-template":{const[c]=Pe(o,[]);if(!c)throw new p("BAD_REQUEST","remove-template needs a template id.");const l=await ke(s);try{await l.call("removeTemplate",{templateId:c},{timeoutMs:12e4,requestId:`rmt-${c}`})}finally{l.close()}return n(`Removed template ${c}.`)}case"phone":{const[c]=Pe(o,["serial"]);if(!c||c==="list"){for(const[l,d]of Object.entries(Lt(a))){n(`${l}${l===xt?" (default)":""}: ${d.name}, ${d.ramMB} MB RAM, ${d.cores} cores, ${d.width}x${d.height} at ${d.density} dpi, heap ${d.heapGrowthMB} MB (large ${d.heapMB} MB), ${d.source}`)}return}if(c==="measure"){const l=ls();const d=ie(o,"serial");const h=d?["-s",d]:["-d"];const f=async y=>{const b=await _(l,[...h,"shell",y],{timeoutMs:2e4});if(b.code!==0)throw new p("HOST_ERROR",`Could not read the phone: ${(b.stderr||b.stdout).trim().slice(0,200)}`,"Plug in one Android phone with USB debugging on, or pass --serial.");return b.stdout};const w={props:await f("getprop"),meminfo:await f("cat /proc/meminfo"),nproc:await f("nproc"),wmSize:await f("wm size"),wmDensity:await f("wm density")};const{id:v,phone:k}=pn(w,`measured ${new Date().toISOString().slice(0,10)}`);await ln(a,v,k);return n(`Saved "${v}": ${k.ramMB} MB RAM, ${k.cores} cores, ${k.width}x${k.height} at ${k.density} dpi, heap ${k.heapGrowthMB} MB (large ${k.heapMB} MB).
|
|
440
|
+
Use it with: ${t} android template --apk <app.apk> --phone ${v}`)}throw new p("BAD_REQUEST",`Unknown phone command ${c}.`,"Try: phone list, phone measure")}case"doctor":{const c=as(o);const l=Ge(a,c,Wi(o));const d=await In();const h=Rt().map(w=>Fe(w,"system",c)).filter(Boolean);let f;try{const w=await ke(s);f=(await r(w)).android;w.close()}catch(w){f=`farm not reachable: ${w.message}`}n(JSON.stringify({sdk:l?{root:l.root,source:l.source,emulator:l.emulatorVersion,slimImage:l.hasImage}:"none yet: run android setup",otherSdks:h.map(w=>({root:w.root,emulator:w.emulatorVersion,slimImage:w.hasImage})),adbServerPort:de,avdFolder:Re.join(a,"avd"),gpu:"host (Metal on a Mac); copies never fall back to software drawing",freeMemoryGB:Number(d.availableGB.toFixed(1)),freeDiskGB:Math.floor(d.freeDiskGB),cpuCores:os.cpus().length,templates:typeof f==="string"?f:f?.templates.map(rs),copies:typeof f==="string"?[]:f?.copies.map(w=>w.serial)},null,2));return}default:n(ns(t));process.exitCode=2}}function ls(){for(const e of Rt()){const t=Fe(e,"system");if(t)return t.adb}return"adb"}import{spawn as xa}from"child_process";import{readFileSync as La,rmSync as Ua,writeFileSync as ja}from"fs";import qi from"os";import Fa from"path";import{createWriteStream as Ga,existsSync as ds,promises as Je}from"fs";import Wa from"os";import ce from"path";import{Readable as Ha}from"stream";import{pipeline as qa}from"stream/promises";var F="156.0.8078.47";var Ya="https://storage.googleapis.com/chrome-for-testing-public";function ps(e=process.platform,t=Wa.arch()){if(e==="darwin")return t==="arm64"?"mac-arm64":"mac-x64";if(e==="linux")return"linux64";if(e==="win32")return"win64";throw new Error(`Chrome Farm has no Chrome build for ${e}.`)}function Yi(e){return ce.join(e,"chrome")}function gt(e,t=F,s=ps()){const i=s==="win64"?"chrome-headless-shell.exe":"chrome-headless-shell";return ce.join(Yi(e),"bin",t,`chrome-headless-shell-${s}`,i)}function zi(e=F,t=ps()){return`${Ya}/${e}/${t}/chrome-headless-shell-${t}.zip`}function Ne(e){const t=process.env.BUOY_CHROME_PATH;if(t){if(!ds(t))throw new Error(`BUOY_CHROME_PATH points to ${t}, which doesn't exist.`);return{path:t,version:"custom",source:"env"}}const s=gt(e);return ds(s)?{path:s,version:F,source:"farm"}:null}async function za(e){const t=await _(e,["--version"],{timeoutMs:15e3});return/(\d+\.\d+\.\d+\.\d+)/.exec(t.stdout)?.[1]??null}async function yt(e,t=()=>{}){const s=Ne(e);if(s)return s;const i=ps();const o=ce.join(Yi(e),"bin");const n=ce.join(o,F);await Je.mkdir(o,{recursive:true});const a=await Je.mkdtemp(ce.join(o,".download-"));try{const r=ce.join(a,"shell.zip");t(`Downloading chrome-headless-shell ${F} for ${i}\u2026`);const c=await fetch(zi(F,i),{signal:AbortSignal.timeout(15*6e4)});if(!c.ok||!c.body)throw new Error(`Download failed: ${c.status} ${zi(F,i)}`);await qa(Ha.fromWeb(c.body),Ga(r));const l=ce.join(a,"out");await Je.mkdir(l);if(process.platform==="win32")await L("tar",["-xf",r,"-C",l],{timeoutMs:10*6e4});else await L("unzip",["-q",r,"-d",l],{timeoutMs:10*6e4});const d=ce.join(l,ce.relative(n,gt(e,F,i)));const h=await za(d);if(h!==F)throw new Error(`The downloaded Chrome reports ${h??"no version"}, not ${F}.`);try{await Je.rename(l,n)}catch(f){if(!ds(gt(e,F,i)))throw f}}finally{await Je.rm(a,{recursive:true,force:true})}t(`chrome-headless-shell ${F}: ready.`);return{path:gt(e,F,i),version:F,source:"farm"}}import{spawn as Ka}from"child_process";import{existsSync as Qa,mkdtempSync as Va,promises as Ja,readFileSync as Xa,rmSync as Za}from"fs";import er from"os";import vt from"path";import{closeSync as Ki,openSync as tr,constants as Qi,writeSync as sr}from"fs";var Vi=class extends Error{constructor(e,t){super(`${e}: ${t}`);this.method=e}};var Ji=class yo{constructor(t,s){this.out=t;this.nextId=1;this.pending=new Map;this.listeners=new Set;this.buf="";this.closedError=null;s.setEncoding("utf8");s.on("data",o=>this.onData(o));const i=()=>this.fail(new Error("Chrome closed the DevTools pipe."));s.on("close",i);s.on("error",i);t.on("error",i)}static fromProcess(t){const s=t.stdio[3];const i=t.stdio[4];if(!s||!i)throw new Error("Chrome was not started with a DevTools pipe.");return new yo(s,i)}send(t,s={},i,o=3e4){if(this.closedError)return Promise.reject(this.closedError);const n=this.nextId++;return new Promise((a,r)=>{const c=setTimeout(()=>{this.pending.delete(n);r(new Vi(t,`no reply in ${o} ms`))},o);this.pending.set(n,{method:t,resolve:a,reject:r,timer:c});this.out.write(JSON.stringify({id:n,method:t,params:s,...i?{sessionId:i}:{}})+"\0")})}on(t){this.listeners.add(t);return()=>this.listeners.delete(t)}waitFor(t,s=3e4){return new Promise((i,o)=>{const n=setTimeout(()=>{a();o(new Error(`no matching DevTools event in ${s} ms`))},s);const a=this.on(r=>{if(!t(r))return;clearTimeout(n);a();i(r)})})}get closed(){return this.closedError!==null}onData(t){this.buf+=t;let s;while((s=this.buf.indexOf("\0"))>=0){const i=this.buf.slice(0,s);this.buf=this.buf.slice(s+1);let o;try{o=JSON.parse(i)}catch{continue}if(typeof o.id==="number"){const n=this.pending.get(o.id);if(!n)continue;this.pending.delete(o.id);clearTimeout(n.timer);if(o.error)n.reject(new Vi(n.method,o.error.message??"error"));else n.resolve(o.result)}else if(o.method){for(const n of this.listeners)n({method:o.method,params:o.params,sessionId:o.sessionId})}}}fail(t){if(this.closedError)return;this.closedError=t;for(const s of this.pending.values()){clearTimeout(s.timer);s.reject(t)}this.pending.clear()}};var ir=["--disable-background-networking","--disable-component-update","--disable-sync","--disable-default-apps","--disable-extensions","--no-first-run","--no-default-browser-check","--disable-client-side-phishing-detection","--disable-domain-reliability","--metrics-recording-only","--disable-breakpad","--disable-hang-monitor","--no-service-autorun","--use-mock-keychain","--password-store=basic","--mute-audio","--disable-field-trial-config"];var or=["--disable-background-timer-throttling","--disable-renderer-backgrounding","--disable-backgrounding-occluded-windows"];var nr=["Translate","MediaRouter","DialMediaRouteProvider","OptimizationHints","OptimizationGuideModelDownloading","AutofillServerCommunication","CertificateTransparencyComponentUpdater","GlobalMediaControls","LensOverlay","IntensiveWakeUpThrottling"];function ar(e=process.platform){return e==="darwin"?["--use-angle=metal"]:[]}function rr(e){const t=[...nr,...e.disableFeatures??[]];return[`--user-data-dir=${e.userDataDir}`,"--remote-debugging-pipe",`--remote-debugging-port=${e.port??0}`,...ir,...or,...e.gpu===false?[]:ar(),`--disable-features=${t.join(",")}`,...e.extra??[],"about:blank"]}import{createHash as cr}from"crypto";import{existsSync as Xi,mkdirSync as lr,renameSync as dr,rmSync as pr,writeFileSync as ur}from"fs";import Zi from"net";import hr from"path";var mr=String.raw`#include <mach/mach.h>
|
|
441
441
|
#include <mach/thread_policy.h>
|
|
442
442
|
#include <pthread.h>
|
|
443
443
|
#include <stdlib.h>
|
|
@@ -494,7 +494,7 @@ __attribute__((constructor)) static void start(void) {
|
|
|
494
494
|
pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);
|
|
495
495
|
pthread_create(&t, &a, watch, NULL);
|
|
496
496
|
}
|
|
497
|
-
`;var us=null;function
|
|
497
|
+
`;var us=null;function fr(e){if(process.platform!=="darwin")return Promise.resolve(null);us??=eo(e,"buoy-boost",mr,["-dynamiclib","-O2"],".dylib").then(t=>{if(!t)us=null;return t});return us}async function eo(e,t,s,i,o=""){const n=cr("sha256").update(s).digest("hex").slice(0,10);const a=hr.join(e,`${t}-${n}${o}`);if(Xi(a))return a;lr(e,{recursive:true});const r=`${a}.${process.pid}.tmp`;const c=`${r}.c`;ur(c,s);const l=await _("clang",[...i,"-o",r,c],{timeoutMs:6e4});pr(c,{force:true});if(l.code!==0||!Xi(r))return null;dr(r,a);return a}async function wr(e){const t=await _("mkfifo",[e],{timeoutMs:5e3});return t.code===0}var gr=class{constructor(e,t=400){this.slots=e;this.holdMs=t;this.on=new Map}touch(e,t){const s=this.on.get(e);if(s){clearTimeout(s)}else{if(this.on.size>=this.slots)return;t("1")}this.on.set(e,setTimeout(()=>{this.on.delete(e);t("0")},this.holdMs))}release(e){const t=this.on.get(e);if(t)clearTimeout(t);this.on.delete(e)}get active(){return this.on.size}};function yr(e,t){const s=new Set;let i=false;const o=Zi.createServer(n=>{const a=Zi.connect(e,"127.0.0.1");s.add(n);s.add(a);n.on("data",()=>{if(!i)t()});n.pipe(a);a.pipe(n);const r=()=>{n.destroy();a.destroy();s.delete(n);s.delete(a)};n.on("error",r).on("close",r);a.on("error",r).on("close",r)});return new Promise((n,a)=>{o.once("error",a);o.listen(0,"127.0.0.1",()=>{const r=o.address().port;n({port:r,close:()=>{i=true;o.close();for(const c of s)c.destroy()}})})})}var St={width:402,height:874,dpr:3,mobile:true,touch:true};var hs={width:1280,height:800,dpr:2,mobile:false,touch:false};var vr=0;var Sr=()=>`c${Date.now().toString(36).slice(-4)}${(vr++).toString(36)}`;var bt=e=>new Promise(t=>setTimeout(t,e));var ms=class{constructor(e){this.opts=e;this.live=new Map;this.gate=null;this.workDir=e.workDir??Va(vt.join(er.tmpdir(),"buoy-chrome-farm-.noindex-"));this.log=e.log??(()=>{})}list(){return[...this.live.values()].map(e=>e.info)}get(e){return this.live.get(e)?.info}async start(e){const t=Date.now();const s=Sr();const i=vt.join(this.workDir,s);await Ja.mkdir(i,{recursive:true});let o=null;const n=vt.join(this.workDir,`${s}.boost`);if(e.boost&&this.opts.boostDir){const f=await fr(this.opts.boostDir).catch(()=>null);if(f&&await wr(n))o={DYLD_INSERT_LIBRARIES:f,BUOY_BOOST_FIFO:n}}const a=Ka(this.opts.exe,rr({userDataDir:i,extra:e.extraSwitches,gpu:!e.noGpu}),{stdio:["ignore","ignore","pipe","pipe","pipe"],env:o?{...process.env,...o}:process.env,detached:true});const r=new Promise((f,w)=>a.once("error",v=>w(new Error(`Could not start Chrome: ${v.message}`))));r.catch(()=>{});let c="";a.stderr?.on("data",f=>{if(c.length<8e3)c+=f});let l;a.once("exit",()=>{if(l&&this.live.get(s)===l){this.live.delete(s);Me(i);Me(n)}if(l)this.dropBoost(l)});let d;let h;try{const f=Ji.fromProcess(a);const{port:w,wsPath:v}=await Promise.race([br(i,a,()=>c),r]);const k=e.viewport??St;const{targetInfos:y}=await f.send("Target.getTargets");const b=y.find(u=>u.type==="page");const E=b?b.targetId:(await f.send("Target.createTarget",{url:"about:blank"})).targetId;const{sessionId:N}=await f.send("Target.attachToTarget",{targetId:E,flatten:true});await this.setupPage(f,N,k);let D=w;if(o){h=tr(n,Qi.O_RDWR|Qi.O_NONBLOCK);const u=this.gate??=new gr(this.opts.boostSlots??4);const g=h;const S=M=>{try{sr(g,M)}catch{}};d=await yr(w,()=>{if(this.live.get(s)?.boost)u.touch(s,S)});D=d.port}const m={id:s,url:e.url,pid:a.pid,port:D,browserWs:`ws://127.0.0.1:${D}${v}`,pageWs:`ws://127.0.0.1:${D}/devtools/page/${E}`,targetId:E,userDataDir:i,startedAt:t,readyMs:0,viewport:k,boosted:!!d};l={info:m,proc:a,cdp:f,sessionId:N,spec:e,boost:d&&h!==void 0?{fd:h,fifo:n,closeProxy:d.close}:void 0};if(a.exitCode!==null)throw new Error(`Chrome exited (${a.exitCode}): ${c.slice(-400)}`);this.live.set(s,l);await this.load(l);m.readyMs=Date.now()-t;return m}catch(f){if(l&&this.live.get(s)===l)this.live.delete(s);if(l)this.dropBoost(l);else{d?.close();if(h!==void 0)try{Ki(h)}catch{}}await to(a);Me(i);Me(n);throw f}}async startMany(e,t,s=8,i){const o=new Array(t);let n=0;await Promise.all(Array.from({length:Math.min(s,t)},async()=>{while(n<t){const a=n++;try{const r=await this.start(e);i?.(r);o[a]={status:"fulfilled",value:r}}catch(r){o[a]={status:"rejected",reason:r}}}}));return o}async reset(e){const t=this.need(e);const s=Date.now();const i=new URL(t.spec.url).origin;const{targetInfos:o}=await t.cdp.send("Target.getTargets");for(const a of o){if(a.type==="page"&&a.targetId!==t.info.targetId)await t.cdp.send("Target.closeTarget",{targetId:a.targetId}).catch(()=>{})}const n=t.cdp.waitFor(a=>a.method==="Page.loadEventFired"&&a.sessionId===t.sessionId,1e4);n.catch(()=>{});await t.cdp.send("Page.navigate",{url:"about:blank"},t.sessionId);await n.catch(()=>{});await Promise.all([t.cdp.send("Storage.clearDataForOrigin",{origin:i,storageTypes:"all"},t.sessionId),t.cdp.send("Network.clearBrowserCookies",{},t.sessionId),t.cdp.send("Network.clearBrowserCache",{},t.sessionId)]);await this.load(t);return Date.now()-s}signal(e,t){const s=this.live.get(e)?.proc.pid;if(!s)return;try{process.kill(-s,t)}catch{}}async evaluate(e,t,s=3e4){const i=this.need(e);const o=await i.cdp.send("Runtime.evaluate",{expression:t,returnByValue:true,awaitPromise:true},i.sessionId,s);if(o.exceptionDetails)throw new Error(o.exceptionDetails.exception?.description??o.exceptionDetails.text);return o.result.value}async alive(e,t=5e3){try{await this.evaluate(e,"1",t);return true}catch{return false}}async screenshot(e){const t=this.need(e);const{data:s}=await t.cdp.send("Page.captureScreenshot",{format:"png"},t.sessionId);return Buffer.from(s,"base64")}async stop(e){const t=this.live.get(e);if(!t)return;this.live.delete(e);this.signal(e,"SIGCONT");try{await t.cdp.send("Browser.close",{},void 0,3e3)}catch{}await to(t.proc,3e3);Me(t.info.userDataDir);this.dropBoost(t)}dropBoost(e){if(!e.boost)return;this.gate?.release(e.info.id);e.boost.closeProxy();try{Ki(e.boost.fd)}catch{}Me(e.boost.fifo);e.boost=void 0}async stopAll(){await Promise.all([...this.live.keys()].map(e=>this.stop(e)));if(!this.opts.workDir)Me(this.workDir)}pidOf(e){return this.live.get(e)?.proc.pid}need(e){const t=this.live.get(e);if(!t)throw new Error(`No Chrome Farm instance ${e}.`);return t}async setupPage(e,t,s){await Promise.all([e.send("Page.enable",{},t),e.send("Runtime.enable",{},t),e.send("Emulation.setDeviceMetricsOverride",{width:s.width,height:s.height,deviceScaleFactor:s.dpr,mobile:s.mobile},t),e.send("Emulation.setTouchEmulationEnabled",s.touch?{enabled:true,maxTouchPoints:5}:{enabled:false},t),e.send("Emulation.setFocusEmulationEnabled",{enabled:true},t)])}async load(e){const{cdp:t,sessionId:s,spec:i}=e;const o=i.readyTimeoutMs??6e4;const n=t.waitFor(l=>l.method==="Page.loadEventFired"&&l.sessionId===s,o);n.catch(()=>{});const a=await t.send("Page.navigate",{url:i.url},s);if(a.errorText)throw new Error(`Could not open ${i.url}: ${a.errorText}`);await n;if(!i.readyText)return;const r=Date.now()+o;const c=`(() => { const t = ${JSON.stringify(i.readyText)}; try { if (document.querySelector(t)) return true; } catch {} return !!document.body && document.body.innerText.includes(t); })()`;while(Date.now()<r){const l=await t.send("Runtime.evaluate",{expression:c,returnByValue:true},s);if(l.result?.value===true)return;await bt(50)}throw new Error(`${i.url} did not show "${i.readyText}" within ${o} ms.`)}};async function to(e,t=0){const s=e.pid;if(t&&e.exitCode===null)await Promise.race([new Promise(i=>e.once("exit",i)),bt(t)]);if(s){try{process.kill(-s,"SIGCONT")}catch{}try{process.kill(-s,"SIGKILL")}catch{}}if(e.exitCode===null&&e.signalCode===null)await Promise.race([new Promise(i=>e.once("exit",i)),bt(3e3)])}function Me(e){try{Za(e,{recursive:true,force:true,maxRetries:3,retryDelay:100})}catch{}}async function br(e,t,s,i=2e4){const o=vt.join(e,"DevToolsActivePort");const n=Date.now()+i;while(Date.now()<n){if(t.exitCode!==null)throw new Error(`Chrome exited (${t.exitCode}): ${s().slice(-400)}`);if(Qa(o)){const a=/^(\d+)\n(\/devtools\/browser\/[\w-]+)\s*$/.exec(Xa(o,"utf8"));if(a)return{port:Number(a[1]),wsPath:a[2]}}await bt(20)}throw new Error(`Chrome did not open its debugging port in ${i} ms.`)}import so from"http";import{spawn as kr}from"child_process";import{chmodSync as io,closeSync as Mr,existsSync as oo,mkdirSync as no,openSync as _r,readdirSync as Er,rmSync as Xe,writeFileSync as $r,readFileSync as Ir}from"fs";import Br from"os";import le from"path";var Or=String.raw`#include <libproc.h>
|
|
498
498
|
#include <mach/mach_time.h>
|
|
499
499
|
#include <stdio.h>
|
|
500
500
|
#include <stdlib.h>
|
|
@@ -515,7 +515,7 @@ int main(int argc, char **argv) {
|
|
|
515
515
|
printf("}\n");
|
|
516
516
|
return 0;
|
|
517
517
|
}
|
|
518
|
-
`;var fs=null;function ao(e){if(process.platform!=="darwin")return Promise.resolve(null);fs??=eo(e,"cfcounters",Br,["-O2"]).then(t=>{if(!t)fs=null;return t});return fs}async function ws(e,t){const s=await _(e,t.map(String),{timeoutMs:1e4});const i=new Map;try{for(const[o,n]of Object.entries(JSON.parse(s.stdout)))i.set(Number(o),n)}catch{}return i}function Or(e,t){let s=0,i=0,o=0;for(const[n,a]of t){const r=e.get(n);if(!r)continue;s+=a.cpuNs-r.cpuNs;i+=a.topNs-r.topNs;o+=a.instr-r.instr}if(s<=0)return null;return{cpuMs:Math.round(s/1e6),topShare:+(i/s).toFixed(3),mInstr:Math.round(o/1e6)}}async function Tr(){const e=await _("ps",["-A","-o","pid=,ppid=,rss=,pcpu="],{timeoutMs:1e4,env:{...process.env,LC_ALL:"C"}});return e.stdout.trim().split("\n").map(t=>t.trim().split(/\s+/).map(Number)).filter(t=>t.length>=4&&t.every(s=>Number.isFinite(s))).map(([t,s,i,o])=>({pid:t,ppid:s,rssKB:i,cpu:o}))}function Dr(e,t){const s=new Map;for(const r of e){const c=s.get(r.ppid);if(c)c.push(r);else s.set(r.ppid,[r])}const i=e.find(r=>r.pid===t);if(!i)return[];const o=[];const n=new Set;const a=[i];while(a.length){const r=a.pop();if(n.has(r.pid))continue;n.add(r.pid);o.push(r);a.push(...s.get(r.pid)??[])}return o}async function Ar(e,t){if(process.platform!=="darwin"||!e.length||!t)return null;const s=await ao(t).catch(()=>null);if(!s)return null;const i=await ws(s,e).catch(()=>null);return i?new Map([...i].map(([o,n])=>[o,n.footprint])):null}async function ro(e,t){const s=await Tr();const i=new Map(e.map(a=>[a,Dr(s,a)]));const o=await Ar([...i.values()].flat().map(a=>a.pid),t);const n=new Map;for(const[a,r]of i){if(!r.length)continue;const c=r.reduce((l,d)=>l+(o?.get(d.pid)??d.rssKB*1024),0);n.set(a,{pids:r.map(l=>l.pid),procs:r.length,memMB:Math.round(c/1048576),cpuPct:Math.round(r.reduce((l,d)=>l+d.cpu,0))})}return n}var V=(e=A())=>le.join(e,"chrome");var gs=(e=A())=>le.join(V(e),"keeper.sock");var ys=(e=A())=>le.join(V(e),"keeper.json");var _e=2048;var Rr=10*6e4;var co=700;async function Pr(e=A()){const t=Ne(e);if(!t)throw new Error("No Chrome yet. Run: farm chrome setup");no(V(e),{recursive:true,mode:448});io(V(e),448);const s=gs(e);if(oo(s)){if(await ue(e,1e4))throw new Error("A Chrome Farm keeper is already running.");const m=xr(e);if(m&&m.pid!==process.pid&&Nr(m.pid))throw new Error(`A Chrome Farm keeper (pid ${m.pid}) is running but not answering.`);Xe(s,{force:true})}process.on("unhandledRejection",m=>console.error("chrome keeper: unhandled",m));await Cr(e);const i=await Ze().catch(()=>null);const o=new ms({exe:t.path,workDir:le.join(V(e),"profiles.noindex",String(process.pid)),boostDir:V(e),boostSlots:i?.topCores??4});const n=new Map;const a=new Map;const r=[];const c=(m,u)=>{r.push({at:new Date().toISOString(),id:u,what:m});if(r.length>200)r.shift()};let l=Date.now();const d=m=>!!(n.get(m)?.paused||n.get(m)?.quietPaused);const h=m=>{const u=n.get(m.id);const g=a.get(m.id);return{...m,label:u?.label,owner:u?.owner,capMB:u?.capMB??_e,paused:d(m.id)||void 0,lastQuiet:u?.lastQuiet,memMB:g?.memMB,cpuPct:g?.cpuPct,procs:g?.procs}};const f=async(m,u)=>{await o.stop(m);n.delete(m);a.delete(m);c(u,m)};const w=m=>(m.all?o.list().map(u=>u.id).filter(u=>!m.owner||n.get(u)?.owner===m.owner):m.ids??[]).filter(u=>o.get(u));let v=0;let k=false;const y=setInterval(async()=>{if(k)return;k=true;try{v++;const m=o.list();if(!m.length){if(Date.now()-l>Rr)await D("idle");return}l=Date.now();const u=new Map(m.map(S=>[S.id,o.pidOf(S.id)]));const g=await ro([...u.values()],V(e)).catch(()=>new Map);for(const[S,M]of u){const I=g.get(M);if(I)a.set(S,I);const R=n.get(S)?.capMB??_e;if(I&&I.memMB>R)await f(S,`closed: memory ${I.memMB} MB went over its ${R} MB cap`)}if(v%5===0){const S=o.list().filter(I=>!d(I.id));const M=await Promise.all(S.map(I=>o.alive(I.id,1e4)));S.forEach((I,R)=>M[R]||c("not answering for 10 s (hung page?)",I.id))}}catch(m){console.error("chrome keeper: watch",m)}finally{k=false}},3e3);const b={"GET /status":async()=>({pid:process.pid,chrome:t,instances:o.list().map(h),events:r.slice(-30)}),"POST /up":async m=>{const u=Math.floor(Number(m.n??1));if(!Number.isFinite(u)||u<1||u>256)throw new Error("n must be a whole number from 1 to 256.");let g;try{g=new URL(m.url)}catch{throw new Error(`Not a web address: ${m.url}`)}if(g.protocol!=="http:"&&g.protocol!=="https:")throw new Error("Only http and https addresses.");const S=m.capMB===void 0?_e:Number(m.capMB);if(!Number.isFinite(S)||S<128)throw new Error("capMB must be a number of MB, at least 128.");const M=Math.floor((Ir.totalmem()/1048576-8192)/co)-o.list().length;if(u>M&&!m.force)throw new Error(`Only room for about ${Math.max(0,M)} more on this computer (each needs ~${co} MB busy). Close some, or pass force.`);const I=m.viewport==="desktop"?hs:St;const R=await o.startMany({url:m.url,readyText:m.readyText,viewport:I,boost:!!m.boost,noGpu:!!m.noGpu},u,8,G=>{n.set(G.id,{label:m.label,owner:m.owner,capMB:S});c(`started at ${m.url} in ${G.readyMs} ms`,G.id)});const $=[];const L=[];for(const G of R){if(G.status==="fulfilled")$.push(h(G.value));else L.push(String(G.reason?.message??G.reason))}return{started:$,failed:L}},"POST /down":async m=>{const u=w(m);for(const g of u)await f(g,"closed");return{closed:u}},"POST /reset":async m=>{const u=w(m);const g=u.filter(d);if(g.length)throw new Error(`Paused, so they can't reset: ${g.join(", ")}. Resume them first.`);return{reset:await Promise.all(u.map(async S=>({id:S,ms:await o.reset(S)})))}},"POST /pause":async m=>{const u=w(m).filter(g=>!n.get(g)?.paused);for(const g of u){o.signal(g,"SIGSTOP");n.set(g,{...n.get(g)??{capMB:_e},paused:true})}if(u.length)c(`paused ${u.length}`);return{paused:u}},"POST /resume":async m=>{const u=w(m).filter(g=>n.get(g)?.paused);for(const g of u){const S=n.get(g);S.paused=false;if(!S.quietPaused)o.signal(g,"SIGCONT")}if(u.length)c(`resumed ${u.length}`);return{resumed:u}},"POST /quiet":async m=>{if(!o.get(m.id))throw new Error(`No instance ${m.id}.`);if(d(m.id))throw new Error(`${m.id} is paused. Resume it before its quiet window.`);const u=Number(m.seconds??10);if(!Number.isFinite(u)||u<1||u>120)throw new Error("seconds must be from 1 to 120.");const g=o.list().map($=>$.id).filter($=>$!==m.id&&!d($)&&(m.everyone||!m.owner||n.get($)?.owner===m.owner));for(const $ of g){o.signal($,"SIGSTOP");n.set($,{...n.get($)??{capMB:_e},quietPaused:true})}c(`quiet window for ${u} s: ${g.length} others paused`,m.id);const S=await ao(V(e)).catch(()=>null);const M=o.pidOf(m.id);const I=M?(await ro([M]).catch(()=>new Map)).get(M)?.pids??[]:[];const R=S&&I.length?await ws(S,I).catch(()=>null):null;setTimeout(async()=>{for(const $ of g){const L=n.get($);if(!L)continue;L.quietPaused=false;if(!L.paused)o.signal($,"SIGCONT")}try{const $=R&&S?await ws(S,I):null;const L=R&&$?Or(R,$):null;const G=n.get(m.id);if(L&&G)G.lastQuiet={...L,seconds:u,at:new Date().toISOString()};c(L?`quiet window over: ${Math.round(L.topShare*100)}% of ${L.cpuMs} ms CPU on top cores`:"quiet window over",m.id)}catch($){c(`quiet window over (no receipt: ${String($?.message??$).slice(0,80)})`,m.id)}},u*1e3);return{id:m.id,paused:g,until:new Date(Date.now()+u*1e3).toISOString()}},"POST /shot":async m=>{if(d(m.id))throw new Error(`${m.id} is paused. Resume it first.`);return{png:(await o.screenshot(m.id)).toString("base64")}},"POST /stop":async()=>{setTimeout(()=>D("asked"),50);return{stopping:true}}};const E=so.createServer(async(m,u)=>{u.setHeader("Content-Type","application/json");try{const g=[];let S=0;for await(const R of m){S+=R.length;if(S>1<<20)throw new Error("request too large");g.push(R)}const M=b[`${m.method} ${m.url}`];if(!M)return void u.writeHead(404).end(JSON.stringify({error:"not found"}));const I=g.length?JSON.parse(Buffer.concat(g).toString("utf8")):{};u.end(JSON.stringify(await M(I)))}catch(g){if(!u.headersSent&&!u.destroyed)u.writeHead(400).end(JSON.stringify({error:String(g?.message??g)}))}});let N=false;async function D(m){if(N)return;N=true;clearInterval(y);await o.stopAll();Xe(o.workDir,{recursive:true,force:true});E.close();Xe(s,{force:true});Xe(ys(e),{force:true});console.error(`chrome keeper stopped (${m})`);process.exit(0)}process.on("SIGTERM",()=>D("SIGTERM"));process.on("SIGINT",()=>D("SIGINT"));process.on("SIGHUP",()=>D("SIGHUP"));await new Promise(m=>E.listen(s,m));io(s,384);Er(ys(e),JSON.stringify({pid:process.pid,startedAt:new Date().toISOString(),chrome:t}));console.error(`chrome keeper on ${s}`)}function Nr(e){try{process.kill(e,0);return true}catch{return false}}async function Cr(e){const t=le.join(V(e),"profiles.noindex")+le.sep;const s=await _("ps",["-A","-o","pid=,args="],{timeoutMs:1e4});const i=s.stdout.split("\n").map(o=>o.trim().match(/^(\d+)\s+(.*)$/)).filter(o=>!!o).filter(o=>o[2].includes(t)&&!o[2].includes(`${t}${process.pid}${le.sep}`)).map(o=>Number(o[1]));for(const o of i){for(const n of[-o,o]){try{process.kill(n,"SIGCONT");process.kill(n,"SIGKILL")}catch{}}}if(i.length)console.error(`chrome keeper: closed ${i.length} processes left by an earlier keeper`);try{for(const o of _r(t))if(o!==String(process.pid))Xe(le.join(t,o),{recursive:true,force:true})}catch{}}function pe(e,t,s,i=A(),o=12e4){return new Promise((n,a)=>{const r=so.request({socketPath:gs(i),method:e,path:t,timeout:o,headers:{"Content-Type":"application/json"}},c=>{const l=[];c.on("data",d=>l.push(d));c.on("end",()=>{try{const d=JSON.parse(Buffer.concat(l).toString("utf8")||"{}");if(c.statusCode&&c.statusCode>=400)a(new Error(d.error??`keeper ${c.statusCode}`));else n(d)}catch(d){a(d)}})});r.on("error",a);r.on("timeout",()=>r.destroy(new Error(`keeper did not answer ${t} in ${o} ms`)));if(s!==void 0)r.write(JSON.stringify(s));r.end()})}async function ue(e=A(),t=3e3){if(!oo(gs(e)))return false;try{await pe("GET","/status",void 0,e,t);return true}catch{return false}}async function lo(e,t=A(),s=15e3){if(await ue(t))return;no(V(t),{recursive:true,mode:448});const i=Mr(le.join(V(t),"keeper.log"),"a");const o=br(process.execPath,e,{detached:true,stdio:["ignore",i,i],env:st()});kr(i);o.unref();const n=Date.now()+s;while(Date.now()<n){if(await ue(t))return;await T(100)}throw new Error("The Chrome Farm keeper did not start.")}function xr(e=A()){try{return JSON.parse($r(ys(e),"utf8"))}catch{return null}}var po=e=>`Usage: ${e} chrome <command>
|
|
518
|
+
`;var fs=null;function ao(e){if(process.platform!=="darwin")return Promise.resolve(null);fs??=eo(e,"cfcounters",Or,["-O2"]).then(t=>{if(!t)fs=null;return t});return fs}async function ws(e,t){const s=await _(e,t.map(String),{timeoutMs:1e4});const i=new Map;try{for(const[o,n]of Object.entries(JSON.parse(s.stdout)))i.set(Number(o),n)}catch{}return i}function Tr(e,t){let s=0,i=0,o=0;for(const[n,a]of t){const r=e.get(n);if(!r)continue;s+=a.cpuNs-r.cpuNs;i+=a.topNs-r.topNs;o+=a.instr-r.instr}if(s<=0)return null;return{cpuMs:Math.round(s/1e6),topShare:+(i/s).toFixed(3),mInstr:Math.round(o/1e6)}}async function Dr(){const e=await _("ps",["-A","-o","pid=,ppid=,rss=,pcpu="],{timeoutMs:1e4,env:{...process.env,LC_ALL:"C"}});return e.stdout.trim().split("\n").map(t=>t.trim().split(/\s+/).map(Number)).filter(t=>t.length>=4&&t.every(s=>Number.isFinite(s))).map(([t,s,i,o])=>({pid:t,ppid:s,rssKB:i,cpu:o}))}function Ar(e,t){const s=new Map;for(const r of e){const c=s.get(r.ppid);if(c)c.push(r);else s.set(r.ppid,[r])}const i=e.find(r=>r.pid===t);if(!i)return[];const o=[];const n=new Set;const a=[i];while(a.length){const r=a.pop();if(n.has(r.pid))continue;n.add(r.pid);o.push(r);a.push(...s.get(r.pid)??[])}return o}async function Rr(e,t){if(process.platform!=="darwin"||!e.length||!t)return null;const s=await ao(t).catch(()=>null);if(!s)return null;const i=await ws(s,e).catch(()=>null);return i?new Map([...i].map(([o,n])=>[o,n.footprint])):null}async function ro(e,t){const s=await Dr();const i=new Map(e.map(a=>[a,Ar(s,a)]));const o=await Rr([...i.values()].flat().map(a=>a.pid),t);const n=new Map;for(const[a,r]of i){if(!r.length)continue;const c=r.reduce((l,d)=>l+(o?.get(d.pid)??d.rssKB*1024),0);n.set(a,{pids:r.map(l=>l.pid),procs:r.length,memMB:Math.round(c/1048576),cpuPct:Math.round(r.reduce((l,d)=>l+d.cpu,0))})}return n}var V=(e=A())=>le.join(e,"chrome");var gs=(e=A())=>le.join(V(e),"keeper.sock");var ys=(e=A())=>le.join(V(e),"keeper.json");var _e=2048;var Pr=10*6e4;var co=700;async function Nr(e=A()){const t=Ne(e);if(!t)throw new Error("No Chrome yet. Run: farm chrome setup");no(V(e),{recursive:true,mode:448});io(V(e),448);const s=gs(e);if(oo(s)){if(await ue(e,1e4))throw new Error("A Chrome Farm keeper is already running.");const m=Lr(e);if(m&&m.pid!==process.pid&&Cr(m.pid))throw new Error(`A Chrome Farm keeper (pid ${m.pid}) is running but not answering.`);Xe(s,{force:true})}process.on("unhandledRejection",m=>console.error("chrome keeper: unhandled",m));await xr(e);const i=await Ze().catch(()=>null);const o=new ms({exe:t.path,workDir:le.join(V(e),"profiles.noindex",String(process.pid)),boostDir:V(e),boostSlots:i?.topCores??4});const n=new Map;const a=new Map;const r=[];const c=(m,u)=>{r.push({at:new Date().toISOString(),id:u,what:m});if(r.length>200)r.shift()};let l=Date.now();const d=m=>!!(n.get(m)?.paused||n.get(m)?.quietPaused);const h=m=>{const u=n.get(m.id);const g=a.get(m.id);return{...m,label:u?.label,owner:u?.owner,capMB:u?.capMB??_e,paused:d(m.id)||void 0,lastQuiet:u?.lastQuiet,memMB:g?.memMB,cpuPct:g?.cpuPct,procs:g?.procs}};const f=async(m,u)=>{await o.stop(m);n.delete(m);a.delete(m);c(u,m)};const w=m=>(m.all?o.list().map(u=>u.id).filter(u=>!m.owner||n.get(u)?.owner===m.owner):m.ids??[]).filter(u=>o.get(u));let v=0;let k=false;const y=setInterval(async()=>{if(k)return;k=true;try{v++;const m=o.list();if(!m.length){if(Date.now()-l>Pr)await D("idle");return}l=Date.now();const u=new Map(m.map(S=>[S.id,o.pidOf(S.id)]));const g=await ro([...u.values()],V(e)).catch(()=>new Map);for(const[S,M]of u){const I=g.get(M);if(I)a.set(S,I);const R=n.get(S)?.capMB??_e;if(I&&I.memMB>R)await f(S,`closed: memory ${I.memMB} MB went over its ${R} MB cap`)}if(v%5===0){const S=o.list().filter(I=>!d(I.id));const M=await Promise.all(S.map(I=>o.alive(I.id,1e4)));S.forEach((I,R)=>M[R]||c("not answering for 10 s (hung page?)",I.id))}}catch(m){console.error("chrome keeper: watch",m)}finally{k=false}},3e3);const b={"GET /status":async()=>({pid:process.pid,chrome:t,instances:o.list().map(h),events:r.slice(-30)}),"POST /up":async m=>{const u=Math.floor(Number(m.n??1));if(!Number.isFinite(u)||u<1||u>256)throw new Error("n must be a whole number from 1 to 256.");let g;try{g=new URL(m.url)}catch{throw new Error(`Not a web address: ${m.url}`)}if(g.protocol!=="http:"&&g.protocol!=="https:")throw new Error("Only http and https addresses.");const S=m.capMB===void 0?_e:Number(m.capMB);if(!Number.isFinite(S)||S<128)throw new Error("capMB must be a number of MB, at least 128.");const M=Math.floor((Br.totalmem()/1048576-8192)/co)-o.list().length;if(u>M&&!m.force)throw new Error(`Only room for about ${Math.max(0,M)} more on this computer (each needs ~${co} MB busy). Close some, or pass force.`);const I=m.viewport==="desktop"?hs:St;const R=await o.startMany({url:m.url,readyText:m.readyText,viewport:I,boost:!!m.boost,noGpu:!!m.noGpu},u,8,G=>{n.set(G.id,{label:m.label,owner:m.owner,capMB:S});c(`started at ${m.url} in ${G.readyMs} ms`,G.id)});const $=[];const U=[];for(const G of R){if(G.status==="fulfilled")$.push(h(G.value));else U.push(String(G.reason?.message??G.reason))}return{started:$,failed:U}},"POST /down":async m=>{const u=w(m);for(const g of u)await f(g,"closed");return{closed:u}},"POST /reset":async m=>{const u=w(m);const g=u.filter(d);if(g.length)throw new Error(`Paused, so they can't reset: ${g.join(", ")}. Resume them first.`);return{reset:await Promise.all(u.map(async S=>({id:S,ms:await o.reset(S)})))}},"POST /pause":async m=>{const u=w(m).filter(g=>!n.get(g)?.paused);for(const g of u){o.signal(g,"SIGSTOP");n.set(g,{...n.get(g)??{capMB:_e},paused:true})}if(u.length)c(`paused ${u.length}`);return{paused:u}},"POST /resume":async m=>{const u=w(m).filter(g=>n.get(g)?.paused);for(const g of u){const S=n.get(g);S.paused=false;if(!S.quietPaused)o.signal(g,"SIGCONT")}if(u.length)c(`resumed ${u.length}`);return{resumed:u}},"POST /quiet":async m=>{if(!o.get(m.id))throw new Error(`No instance ${m.id}.`);if(d(m.id))throw new Error(`${m.id} is paused. Resume it before its quiet window.`);const u=Number(m.seconds??10);if(!Number.isFinite(u)||u<1||u>120)throw new Error("seconds must be from 1 to 120.");const g=o.list().map($=>$.id).filter($=>$!==m.id&&!d($)&&(m.everyone||!m.owner||n.get($)?.owner===m.owner));for(const $ of g){o.signal($,"SIGSTOP");n.set($,{...n.get($)??{capMB:_e},quietPaused:true})}c(`quiet window for ${u} s: ${g.length} others paused`,m.id);const S=await ao(V(e)).catch(()=>null);const M=o.pidOf(m.id);const I=M?(await ro([M]).catch(()=>new Map)).get(M)?.pids??[]:[];const R=S&&I.length?await ws(S,I).catch(()=>null):null;setTimeout(async()=>{for(const $ of g){const U=n.get($);if(!U)continue;U.quietPaused=false;if(!U.paused)o.signal($,"SIGCONT")}try{const $=R&&S?await ws(S,I):null;const U=R&&$?Tr(R,$):null;const G=n.get(m.id);if(U&&G)G.lastQuiet={...U,seconds:u,at:new Date().toISOString()};c(U?`quiet window over: ${Math.round(U.topShare*100)}% of ${U.cpuMs} ms CPU on top cores`:"quiet window over",m.id)}catch($){c(`quiet window over (no receipt: ${String($?.message??$).slice(0,80)})`,m.id)}},u*1e3);return{id:m.id,paused:g,until:new Date(Date.now()+u*1e3).toISOString()}},"POST /shot":async m=>{if(d(m.id))throw new Error(`${m.id} is paused. Resume it first.`);return{png:(await o.screenshot(m.id)).toString("base64")}},"POST /stop":async()=>{setTimeout(()=>D("asked"),50);return{stopping:true}}};const E=so.createServer(async(m,u)=>{u.setHeader("Content-Type","application/json");try{const g=[];let S=0;for await(const R of m){S+=R.length;if(S>1<<20)throw new Error("request too large");g.push(R)}const M=b[`${m.method} ${m.url}`];if(!M)return void u.writeHead(404).end(JSON.stringify({error:"not found"}));const I=g.length?JSON.parse(Buffer.concat(g).toString("utf8")):{};u.end(JSON.stringify(await M(I)))}catch(g){if(!u.headersSent&&!u.destroyed)u.writeHead(400).end(JSON.stringify({error:String(g?.message??g)}))}});let N=false;async function D(m){if(N)return;N=true;clearInterval(y);await o.stopAll();Xe(o.workDir,{recursive:true,force:true});E.close();Xe(s,{force:true});Xe(ys(e),{force:true});console.error(`chrome keeper stopped (${m})`);process.exit(0)}process.on("SIGTERM",()=>D("SIGTERM"));process.on("SIGINT",()=>D("SIGINT"));process.on("SIGHUP",()=>D("SIGHUP"));await new Promise(m=>E.listen(s,m));io(s,384);$r(ys(e),JSON.stringify({pid:process.pid,startedAt:new Date().toISOString(),chrome:t}));console.error(`chrome keeper on ${s}`)}function Cr(e){try{process.kill(e,0);return true}catch{return false}}async function xr(e){const t=le.join(V(e),"profiles.noindex")+le.sep;const s=await _("ps",["-A","-o","pid=,args="],{timeoutMs:1e4});const i=s.stdout.split("\n").map(o=>o.trim().match(/^(\d+)\s+(.*)$/)).filter(o=>!!o).filter(o=>o[2].includes(t)&&!o[2].includes(`${t}${process.pid}${le.sep}`)).map(o=>Number(o[1]));for(const o of i){for(const n of[-o,o]){try{process.kill(n,"SIGCONT");process.kill(n,"SIGKILL")}catch{}}}if(i.length)console.error(`chrome keeper: closed ${i.length} processes left by an earlier keeper`);try{for(const o of Er(t))if(o!==String(process.pid))Xe(le.join(t,o),{recursive:true,force:true})}catch{}}function pe(e,t,s,i=A(),o=12e4){return new Promise((n,a)=>{const r=so.request({socketPath:gs(i),method:e,path:t,timeout:o,headers:{"Content-Type":"application/json"}},c=>{const l=[];c.on("data",d=>l.push(d));c.on("end",()=>{try{const d=JSON.parse(Buffer.concat(l).toString("utf8")||"{}");if(c.statusCode&&c.statusCode>=400)a(new Error(d.error??`keeper ${c.statusCode}`));else n(d)}catch(d){a(d)}})});r.on("error",a);r.on("timeout",()=>r.destroy(new Error(`keeper did not answer ${t} in ${o} ms`)));if(s!==void 0)r.write(JSON.stringify(s));r.end()})}async function ue(e=A(),t=3e3){if(!oo(gs(e)))return false;try{await pe("GET","/status",void 0,e,t);return true}catch{return false}}async function lo(e,t=A(),s=15e3){if(await ue(t))return;no(V(t),{recursive:true,mode:448});const i=_r(le.join(V(t),"keeper.log"),"a");const o=kr(process.execPath,e,{detached:true,stdio:["ignore",i,i],env:st()});Mr(i);o.unref();const n=Date.now()+s;while(Date.now()<n){if(await ue(t))return;await T(100)}throw new Error("The Chrome Farm keeper did not start.")}function Lr(e=A()){try{return JSON.parse(Ir(ys(e),"utf8"))}catch{return null}}var po=e=>`Usage: ${e} chrome <command>
|
|
519
519
|
|
|
520
520
|
Many slim Chromes on one computer. Each one runs your web app like a lone
|
|
521
521
|
Chrome would, with its own storage and its own debugging endpoint.
|
|
@@ -555,10 +555,10 @@ Chrome would, with its own storage and its own debugging endpoint.
|
|
|
555
555
|
|
|
556
556
|
Perf numbers (frame times, input delay) match a lone Chrome only while no
|
|
557
557
|
more instances are busy than this computer has top cores. See: doctor
|
|
558
|
-
`;function he(e,t){const s=e.indexOf(`--${t}`);if(s<0)return void 0;const i=e[s+1];if(!i||i.startsWith("--"))throw new p("BAD_REQUEST",`--${t} needs a value.`);return i}var
|
|
558
|
+
`;function he(e,t){const s=e.indexOf(`--${t}`);if(s<0)return void 0;const i=e[s+1];if(!i||i.startsWith("--"))throw new p("BAD_REQUEST",`--${t} needs a value.`);return i}var Ur=["url","ready","cap-mb","label","seconds","jobs"];function kt(e){const t=[];for(let s=0;s<e.length;s++){if(e[s]==="--")break;if(e[s].startsWith("--")){if(Ur.includes(e[s].slice(2)))s++;continue}t.push(e[s])}return t}function uo(e,t){const s=Number(he(e,t));if(!Number.isFinite(s)||s<=0)throw new p("BAD_REQUEST",`--${t} needs a number.`);return s}function ho(e,t){const s=kt(e)[0];if(s===void 0)return t;const i=Number(s);if(!Number.isInteger(i)||i<1||i>256)throw new p("BAD_REQUEST","The count must be a whole number from 1 to 256.");return i}function mo(e){const t=he(e,"url");if(!t)throw new p("BAD_REQUEST","Give the app's address with --url.");try{new URL(t)}catch{throw new p("BAD_REQUEST",`Not a web address: ${t}`)}return t}async function Ze(){const e=qi.cpus().length;const t=Math.round(qi.totalmem()/1024**3);if(process.platform!=="darwin")return{cores:e,topCores:null,topName:null,memGB:t};const s=await _("sysctl",["-n","hw.perflevel0.logicalcpu","hw.perflevel0.name"],{timeoutMs:5e3});const[i,o]=s.stdout.trim().split("\n");return{cores:e,topCores:Number(i)||null,topName:o||null,memGB:t}}function vs(e){const t=Math.max(1,e.topCores??Math.floor(e.cores/3));const s=Math.max(t,Math.floor(e.cores*.9));const i=Math.max(1,Math.floor((e.memGB-8)/.7));return{perf:t,functional:Math.min(s,i)}}function fo(e){const t=e;const s=t.memMB!=null?` ${t.memMB} MB ${t.cpuPct??0}% CPU${t.paused?" paused":""}`:"";const i=t.lastQuiet?`
|
|
559
559
|
last quiet window: ${Math.round(t.lastQuiet.topShare*100)}% of ${t.lastQuiet.cpuMs} ms CPU on top cores`:"";return`${e.id} ${e.url}${s}
|
|
560
|
-
${e.browserWs}${i}`}async function wo(e,t,s=[process.argv[1],"chrome","keeper"]){const[i,...o]=e;const n=r=>console.log(r);const a=A();switch(i){case void 0:case"help":case"--help":return n(po(t));case"keeper":return
|
|
561
|
-
${S.length-
|
|
560
|
+
${e.browserWs}${i}`}async function wo(e,t,s=[process.argv[1],"chrome","keeper"]){const[i,...o]=e;const n=r=>console.log(r);const a=A();switch(i){case void 0:case"help":case"--help":return n(po(t));case"keeper":return Nr(a);case"setup":{const r=await yt(a,c=>console.error(c));n(`Ready: ${r.path} (${r.version}).`);return n(`Next: ${t} chrome up 4 --url http://localhost:3000`)}case"doctor":{const r=await Ze();const c=vs(r);const l=Ne(a);n(`Chrome: ${l?`${l.version} at ${l.path}`:`not set up (run: ${t} chrome setup)`}`);n(`Cores: ${r.cores}${r.topCores?` (${r.topCores} ${r.topName??"top"} cores)`:""}. Memory: ${r.memGB} GB.`);n(`Keeper: ${await ue(a)?"running":"not running"}`);n(`Suggested: up to ${c.functional} instances for pass/fail tests, up to ${c.perf} busy at once for perf numbers.`);return}case"up":{if(!Ne(a))await yt(a,c=>console.error(c));await lo(s,a);const r=await pe("POST","/up",{n:ho(o,1),url:mo(o),readyText:he(o,"ready"),viewport:o.includes("--desktop")?"desktop":"phone",capMB:he(o,"cap-mb")?uo(o,"cap-mb"):void 0,label:he(o,"label"),boost:o.includes("--boost"),noGpu:o.includes("--no-gpu"),force:o.includes("--force")},a,60*6e4);for(const c of r.started)n(fo(c));for(const c of r.failed)console.error(`failed: ${c}`);n(`${r.started.length} up${r.failed.length?`, ${r.failed.length} failed`:""}.`);if(r.failed.length&&!r.started.length)process.exitCode=1;return}case"list":{const r=o.includes("--json");if(!await ue(a))return n(r?"[]":"No instances (the keeper is not running).");const c=await pe("GET","/status",void 0,a);if(r)return n(JSON.stringify(c.instances,null,2));if(!c.instances.length)n("No instances.");for(const d of c.instances)n(fo(d));const l=c.events.filter(d=>/closed: memory|not answering/.test(d.what)).slice(-5);for(const d of l)n(`! ${d.id??""} ${d.what}`);return}case"pause":case"resume":case"down":case"reset":{if(!await ue(a))return n("No instances (the keeper is not running).");const r=kt(o);if(!r.length&&!o.includes("--all"))throw new p("BAD_REQUEST",`Name instances or use --all.`);const c=await pe("POST",`/${i}`,{ids:r,all:o.includes("--all")},a);if(i==="down")n(`Closed ${c.closed.length}.`);else if(i==="pause")n(`Paused ${c.paused.length}.`);else if(i==="resume")n(`Resumed ${c.resumed.length}.`);else for(const l of c.reset)n(`${l.id} reset in ${l.ms} ms`);return}case"quiet":{const[r]=kt(o);if(!r)throw new p("BAD_REQUEST","Name the instance that gets the quiet window.");const c=await pe("POST","/quiet",{id:r,seconds:he(o,"seconds")?uo(o,"seconds"):10,everyone:true},a);return n(`${c.paused.length} other instances paused until ${c.until}.`)}case"shot":{const[r,c]=kt(o);if(!r||!c)throw new p("BAD_REQUEST","Usage: chrome shot <id> <file.png>");const l=await pe("POST","/shot",{id:r},a);ja(c,Buffer.from(l.png,"base64"));return n(`Saved ${Fa.resolve(c)}.`)}case"stop":{if(!await ue(a))return n("The keeper is not running.");await pe("POST","/stop",{},a);return n("Stopping the keeper and closing every instance.")}case"run":return jr(o,a);default:throw new p("BAD_REQUEST",`Unknown chrome command: ${i}. Try: ${t} chrome help`)}}async function jr(e,t){const s=e.indexOf("--");const i=s>=0?e.slice(s+1):[];if(!i.length)throw new p("BAD_REQUEST","Put the command after --, like: run 8 --url http://localhost:3000 -- node test.mjs");const o=s>=0?e.slice(0,s):e;const n=mo(o);const a=he(o,"jobs");const r=a?La(a,"utf8").split("\n").map(B=>B.trim()).filter(B=>B&&!B.startsWith("#")):null;const c=vs(await Ze()).functional;const l=ho(o,r?Math.min(r.length,c):1);const d=r??Array.from({length:l},(B,oe)=>String(oe));const h=Ne(t)??await yt(t,B=>console.error(B));const f=await Ze();const w=new ms({exe:h.path,boostDir:V(t),boostSlots:f.topCores??4});const v=o.includes("--desktop")?hs:St;const k=o.includes("--boost");const y=o.includes("--no-gpu");const b=he(o,"ready");console.error(`Starting ${l} Chrome instances\u2026`);const E=Date.now();let N=false;const D=new Set;const m=async()=>{N=true;for(const B of D)B.kill("SIGTERM");await w.stopAll().catch(()=>{});Ua(w.workDir,{recursive:true,force:true})};for(const B of["SIGINT","SIGTERM","SIGHUP"])process.once(B,()=>void m().then(()=>process.exit(130)));const u=(await w.startMany({url:n,readyText:b,viewport:v,boost:k,noGpu:y},l)).flatMap(B=>B.status==="fulfilled"?[B.value]:(console.error(`failed to start: ${B.reason?.message??B.reason}`),[]));if(!u.length){await m();throw new p("HOST_ERROR","No instance started.")}console.error(`${u.length} ready in ${Date.now()-E} ms. Running ${d.length} job${d.length===1?"":"s"}\u2026`);const g=Date.now();const S=[];const M=d.map((B,oe)=>({job:B,index:oe}));const I=async(B,oe)=>{let Ss=true;while(M.length&&!N){const me=M.shift();if(!Ss){try{await w.reset(B.id)}catch(_t){M.unshift(me);console.error(`Chrome ${B.id} stopped working (${_t.message}); its jobs go to the others.`);await w.stop(B.id).catch(()=>{});return}}Ss=false;const bs=Date.now();const fe=await Fr(i,D,r?me.job:void 0,{BUOY_CHROME_CDP:B.browserWs,BUOY_CHROME_PAGE_WS:B.pageWs,BUOY_CHROME_INDEX:String(oe),BUOY_CHROME_URL:n,...r?{BUOY_JOB:me.job}:{}});S.push({job:me.job,index:me.index,instance:B.id,code:fe.code,ms:Date.now()-bs,out:fe.out});const vo=fe.code===0?"pass":`FAIL (${fe.code})`;console.log(`${vo.padEnd(10)} ${String(Date.now()-bs).padStart(6)} ms ${r?me.job:`#${me.index}`}`);if(fe.code!==0&&fe.out.trim())console.log(fe.out.trim().split("\n").slice(-8).map(_t=>` ${_t}`).join("\n"))}};try{await Promise.all(u.map((B,oe)=>I(B,oe)))}finally{await m()}const R=Date.now()-g;const $=S.reduce((B,oe)=>B+oe.ms,0);const U=S.filter(B=>B.code!==0).length;const G=d.length-S.length;console.log(`
|
|
561
|
+
${S.length-U} passed, ${U} failed${G?`, ${G} not run`:""}. ${u.length} Chromes, ${(R/1e3).toFixed(1)} s of jobs, ${($/Math.max(1,R)).toFixed(1)} running at once on average.`);if(U||G)process.exitCode=1}function Fr(e,t,s,i){return new Promise(o=>{const n=xa(e[0],s!==void 0?[...e.slice(1),s]:e.slice(1),{env:{...process.env,...i}});t.add(n);n.once("close",()=>t.delete(n));let a="";const r=c=>{a+=c.toString("utf8");if(a.length>2e4)a=a.slice(-2e4)};n.stdout.on("data",r);n.stderr.on("data",r);n.on("error",c=>o({code:-1,out:String(c.message)}));n.on("close",c=>o({code:c,out:a}))})}var Mt=e=>`Usage: ${e} <command>
|
|
562
562
|
|
|
563
563
|
status [--outside] Pool, budgets, sims, leases, recent events
|
|
564
564
|
setup [options] Make a template (the sim clones are made from)
|
|
@@ -594,4 +594,4 @@ ${S.length-L} passed, ${L} failed${G?`, ${G} not run`:""}. ${u.length} Chromes,
|
|
|
594
594
|
stop [--now] Stop the farm service and park free sims
|
|
595
595
|
--now Stop even if leases are still held
|
|
596
596
|
farmd Run the farm service in this process
|
|
597
|
-
`;var
|
|
597
|
+
`;var Bl=Mt("pnpm farm");function z(e,t){const s=e.indexOf(`--${t}`);if(s<0)return void 0;const i=e[s+1];if(!i||i.startsWith("--"))throw new p("BAD_REQUEST",`--${t} needs a value.`);return i}async function Gr(e){return ts({label:`CLI (${ba.userInfo().username})`,launcher:es(e,["farmd"])})}function P(e){console.log(typeof e==="string"?e:JSON.stringify(e,null,2))}async function go(e,t,s){if(s)return s;const i=await e.call("status");const o=i.templates.filter(n=>n.usable);if(o.length===0)throw new p("NO_TEMPLATE","No template yet.",`Run: ${t} setup`);return o[o.length-1].id}function Wr(e){const t=[`Pool ${e.counts.pool} \xB7 awake ${e.counts.awake} \xB7 in use ${e.counts.leased} \xB7 room for about ${e.budget.roomForMoreSims} more`,`Memory ${e.budget.availableMemGB} GB free (${e.budget.memPressure}) \xB7 disk ${e.budget.freeDiskGB} GB free \xB7 load ${e.budget.loadAvg1}/${e.budget.cpuCount}`];for(const s of e.templates){t.push(`Template ${s.id} ${s.name}: ${s.usable?"ready":s.note??"not ready"} (${s.profile}, ${s.runtime.split(".").pop()})`);t.push(` seed: ${s.seedDir??"none"} \xB7 launch URL: ${s.launchUrl??"none"}`)}for(const s of e.sims){t.push(` ${s.name.padEnd(14)} ${s.power.padEnd(8)} ${s.health.padEnd(7)} ${s.profile.padEnd(7)} ${s.owner?`in use by ${s.owner}`:""} ${s.udid}`);const i=s.phone;if(i?.status==="on"){t.push(` as ${i.phoneName}${i.peakMB?`, peak ${i.peakMB} MB`:""}${i.overLimit?", WENT PAST THE PHONE'S LIMIT":""}${i.ended?` (closed: ${i.ended})`:""}`)}else if(i?.status==="fallback"){t.push(` plain sim app: ${i.note??"the phone profile did not load"}`)}}for(const s of e.orphans??[])t.push(` ${s.name} ${s.udid}: held after crash`);for(const s of e.recentEvents.slice(-8))t.push(` \xB7 ${new Date(s.at).toLocaleTimeString(void 0,{hour12:false})} ${s.message}`);if(e.outsideSims)t.push(`Other sims (never touched): ${e.outsideSims.length}`);return t.join("\n")}async function Hr(e,{prog:t,entry:s,chromeKeeperArgs:i}){const[o,...n]=e;if(!o||o==="help"||o==="--help")return P(Mt(t));if(o==="android")return Hi(n,t,s);if(o==="chrome")return wo(n,t,[s,...i??["chrome","keeper"]]);qr(o,n);if(o==="farmd"){const r=await Pi({hostLabel:"node",idleExitMs:10*6e4,onExit:()=>process.exit(0)});if(!r){console.error("Another farm service is already running.");process.exit(0)}for(const c of["SIGTERM","SIGINT"])process.on(c,()=>void r.stop(c));return}if(o==="phone"&&n[0]!=="use"){const r=n[0];if(r==="list"||!r){const c=Oe();for(const[l,d]of Object.entries(c)){P(`${l}: ${d.name} (${d.productType}), ${d.ramMB} MB RAM, ${d.cpus} CPUs, app limit ${d.limitMB} MB, first warning ${d.warnMB} MB, iOS ${d.ios}, measured ${d.measuredAt}`)}return}if(r==="measure"){const c=await Fi({device:z(n,"device"),log:l=>console.log(l)});P(`Saved "${c.id}": ${c.phone.ramMB} MB RAM, ${c.phone.cpus} CPUs, app limit ${c.phone.limitMB} MB, first warning ${c.phone.warnMB} MB.`);if(c.previous&&c.previous.limitMB!==c.phone.limitMB)P(`(The limit was ${c.previous.limitMB} MB before.)`);P(`Use it with: ${t} phone use ${c.id}`);return}throw new p("BAD_REQUEST",`Unknown phone command ${r}.`,"Try: phone list, phone use <id|off>, phone measure")}if(o==="doctor"){const r=new Zt;const c=await r.hostStats();const l=await qe();const d=await r.listDevices().catch(()=>[]);const h=Ve();const f=h?Ui.join(h,"example-dev-build/artifacts/pool-seed"):null;const w=h?{exampleBuild:ut(),seed:f?Le(f)??"ok":"none"}:{};P({xcode:await r.xcodeBuild(),runtimes:[...new Set(d.map(v=>v.runtime.split(".").pop()))],freeDiskGB:Math.floor(c.freeDiskBytes/1024**3),availableMemGB:Math.floor(c.availableMemBytes/1024**3),memPressure:c.memPressure,farmService:l?{...l,alive:await Ye(l)}:"not running",socket:Sa(X().socket),...w,slimLabels:Ee("slim").length,slimTurnsOff:Ce.map(v=>v.name)});return}const a=await Gr(s);try{switch(o){case"status":{const r=await a.call("status",{outside:n.includes("--outside")});return P(n.includes("--json")?r:Wr(r))}case"setup":{const r=Ve();const c={appPath:z(n,"app")??ut(),seedDir:n.includes("--no-seed")?null:z(n,"seed")??(r?Ui.join(r,"example-dev-build/artifacts/pool-seed"):void 0),launchUrl:z(n,"launch-url"),deviceType:z(n,"device")??"com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro",runtime:z(n,"runtime")??"com.apple.CoreSimulator.SimRuntime.iOS-26-3",profile:z(n,"profile")??"slim",metroPort:Number(z(n,"metro")??8802)};if(!c.appPath)throw new p("BAD_REQUEST","No app build found.","Pass --app <path.app>.");if(!c.seedDir&&!n.includes("--no-seed"))throw new p("NO_SEED","No seed folder found.","Pass --seed <dir>, or --no-seed for an app that needs no saved data.");if(n.includes("--dry-run")){const l=await a.call("budget",{ramGB:6,diskGB:4});return P({wouldCreateTemplate:c,seed:c.seedDir?Le(c.seedDir)??"ok":"none",slimTurnsOff:c.profile==="slim"?Ce.map(d=>`${d.name}: ${d.downside}`):[],budget:l.ok?"ok":l.reason,roomForSims:l.roomFor})}return P(await a.call("setup",c,{timeoutMs:15*6e4,requestId:`setup-${Date.now()}`}))}case"grow":case"shrink":{const r=await go(a,t,z(n,"template"));return P(await a.call(o,{templateId:r,count:Number(n[0]??1)},{timeoutMs:30*6e4,requestId:`${o}-${Date.now()}`}))}case"acquire":{const r=await go(a,t,z(n,"template"));const c=await a.acquire({templateId:r,count:Number(n[0]??1),purpose:z(n,"purpose")??"CLI",launch:n.includes("--launch")});P(c);console.error("Holding these sims. Press Ctrl-C to release them.");await new Promise(l=>process.once("SIGINT",()=>l()));await a.release(c.leaseId,c.epoch);return P("Released.")}case"remove-template":if(!n[0])throw new p("BAD_REQUEST",`Usage: ${t} remove-template <id>`);return P(await a.call("removeTemplate",{templateId:n[0]},{timeoutMs:30*6e4,requestId:`rmt-${n[0]}-${Date.now()}`}));case"park":case"wake":case"reset":case"rebuild":case"delete":if(!n[0])throw new p("BAD_REQUEST",`Usage: ${t} ${o} <udid>`);return P(await a.call(o,{udid:n[0]},{timeoutMs:5*6e4,requestId:`${o}-${n[0]}-${Date.now()}`}));case"stop":return P(await a.call("shutdown",{ifIdle:!n.includes("--now")}));case"end-lease":return P(await a.call("endLease",{leaseId:n[0],force:true},{requestId:`end-${n[0]}`}));case"phone":{if(!n[1])throw new p("BAD_REQUEST","phone use needs a phone id or off.",`See: ${t} phone list`);const r=await a.call("settings",{phoneProfile:n[1]});return P(r.phoneProfile==="off"?"Apps now launch as plain simulator apps.":`Apps now launch as ${Oe()[r.phoneProfile]?.name??r.phoneProfile}. Apps that are already open keep their old setting until the farm launches them again.`)}case"settings":{if(n.length===0)return P((await a.call("status")).settings);const r={};for(const c of n){const[l,d]=c.split("=");r[l]=d==="true"?true:d==="false"?false:Number.isNaN(Number(d))?d:Number(d)}return P(await a.call("settings",r))}default:P(Mt(t));process.exitCode=2}}finally{a.close()}}function qr(e,t){const s={setup:["app","seed","launch-url","device","runtime","profile","metro"],grow:["template"],shrink:["template"],acquire:["template","purpose"],phone:["device"]};for(const i of s[e]??[])z(t,i);if(e==="setup"){if(t.includes("--no-seed")&&t.includes("--seed"))throw new p("BAD_REQUEST","Use --seed or --no-seed.");const i=z(t,"profile");const o=Number(z(t,"metro")??8802);if(i&&!["slim","stock"].includes(i))throw new p("BAD_REQUEST","Use slim or stock.");if(!Number.isInteger(o)||o<1||o>65535)throw new p("BAD_REQUEST","Use a port from 1 to 65535.")}}export{te as PROTOCOL_VERSION,et as STATE_VERSION,Et as DEFAULT_SETTINGS,p as FarmError,ks as ALWAYS_ENABLED,Ce as SLIM_CATEGORIES,Ms as slimLabels,Ee as profileLabels,_s as profileHash,st as bootEnv,it as findDataContainer,Ps as readBuoyInstallId,Cs as LoadMeter,xs as parseCpuTime,xt as DEFAULT_ANDROID_PHONE,zs as BUILT_IN_ANDROID_PHONES,Lt as loadAndroidPhones,A as farmHome,X as paths,ei as Store,_n as farmOwnedUdids,de as ANDROID_ADB_PORT,ze as androidHome,ti as parseGfxinfo,si as classifyBootLog,qt as farmAdbTarget,oi as farmAdbTargets,li as EmulatorHost,pi as BUILT_IN_PHONES,ui as DEFAULT_PHONE,zt as PHONE_OFF,ct as phoneDir,Oe as loadPhones,fi as savePhone,wi as defaultCapMB,gi as profileVars,Kt as joinInsertList,Ke as launchFilePath,lt as eventsFilePath,yi as writeLaunchFile,dt as removeLaunchFile,vi as readLaunchFile,Qt as simLaunchEnv,Si as parseEvents,bi as summarize,pt as readSummary,ki as buildDylib,Mi as LocalDylibBuilder,re as copyCostGB,Bi as Engine,Oi as crashLoop,Zt as SimctlHost,Pi as startFarmd,es as nodeLauncher,Ci as FarmClient,ts as connectFarm,Ve as findRepoRoot,ut as findExampleBuild,xi as defaultSeedDir,ha as farmDefaults,ya as detectAgent,va as agentLabel,Fi as measurePhone,ns as androidHelp,Hi as runAndroidCommand,F as PINNED_VERSION,Ne as findShell,yt as installShell,Ji as PipeCdp,St as PHONE_VIEWPORT,hs as DESKTOP_VIEWPORT,ms as ChromeFarm,V as chromeFarmDir,_e as DEFAULT_CAP_MB,pe as keeperCall,ue as keeperAlive,lo as ensureKeeper,po as chromeHelp,Ze as coreInfo,vs as suggestCounts,wo as runChromeCommand,Mt as farmHelp,Hr as runFarmCommand};
|