@buoy-gg/mcp 7.0.68 → 7.0.69

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.
@@ -1,4 +1,4 @@
1
- import{createRequire as ts}from"module";const xi=ts(import.meta.url);var F=1;var me=1;var Pe={parkAfterMinutes:10,maxConcurrentBoots:3,reserveRamGB:24,reserveDiskGB:40,slimSimGB:2,stockSimGB:6,cloneDiskGB:2,freezePilot:false,runCapMinutes:60,keepRunsHours:24,fairMinFreeShare:.25,fairMaxSwingShare:.12,fairMaxThermalLevel:0,fairMaxSwapGrowthMB:256,phoneProfile:"iphone-se-2nd-gen",phoneOnLimit:"warn",phoneCapMB:0};var d=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 ss}from"crypto";var Je=["com.apple.sharingd"];var se=[{"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 Ve(){const e=new Set(Je);const t=new Set;for(const s of se)for(const i of s.labels)if(!e.has(i))t.add(i);return[...t].sort()}function J(e){return e==="slim"?Ve():[]}function Xe(e){return ss("sha256").update(e+"\n"+J(e).join("\n")).digest("hex").slice(0,16)}import{promises as A,existsSync as Ze}from"fs";import D from"path";import{AsyncLocalStorage as is}from"async_hooks";import{createHash as ns,randomBytes as fe}from"crypto";import{promises as V}from"fs";import Te from"path";import{randomBytes as as}from"crypto";import{spawn as os}from"child_process";var ie=new Set;function Re(){return ie.size}function rs(){for(const e of ie){try{e.kill("SIGKILL")}catch{}}}function M(e,t,s={}){const{timeoutMs:i=3e4,maxBytes:n=4*1024*1024}=s;return new Promise(a=>{const o=os(e,t,{env:s.env??process.env,stdio:[s.input==null?"ignore":"pipe","pipe","pipe"]});ie.add(o);let r="";let l="";let c=false;o.stdout.on("data",h=>{if(r.length<n)r+=h.toString("utf8")});o.stderr.on("data",h=>{if(l.length<n)l+=h.toString("utf8")});if(s.input!=null)o.stdin.end(s.input);const p=setTimeout(()=>{c=true;o.kill("SIGTERM");setTimeout(()=>o.kill("SIGKILL"),2e3).unref?.()},i);o.on("error",h=>{ie.delete(o);clearTimeout(p);a({code:null,stdout:r,stderr:l+String(h),timedOut:c})});o.on("close",h=>{ie.delete(o);clearTimeout(p);a({code:h,stdout:r,stderr:l,timedOut:c})})})}async function P(e,t,s={}){const i=await M(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 et(){const e=await P("ps",["-axww","-o","pid=,ppid=,lstart=,stat=,comm="],{timeoutMs:15e3});const t=await P("ps",["-axww","-o","pid=,command="],{timeoutMs:15e3});const s=new Map;for(const n of t.split("\n")){const a=/^\s*(\d+)\s+(.*)$/.exec(n);if(a)s.set(Number(a[1]),a[2])}const i=[];for(const n of e.split("\n")){const a=/^\s*(\d+)\s+(\d+)\s+(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})\s+(\S+)\s+(.*)$/.exec(n);if(!a)continue;const o=Number(a[1]);i.push({pid:o,ppid:Number(a[2]),start:a[3].replace(/\s+/g," "),stat:a[4],comm:a[5].trim(),command:s.get(o)??a[5].trim()})}return i}function tt(e,t){const s=new Map;const i=new Map;for(const l of e){i.set(l.pid,l);const c=s.get(l.ppid)??[];c.push(l);s.set(l.ppid,c)}const n=[];const a=i.get(t);if(!a)return n;const o=[a];const r=new Set;while(o.length){const l=o.pop();if(r.has(l.pid))continue;r.add(l.pid);n.push(l);for(const c of s.get(l.pid)??[])o.push(c)}return n}async function ne(e){const t=await M("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 q(e,t){if(!Number.isInteger(e)||e<=0)return false;try{process.kill(e,0)}catch(s){if(s.code!=="EPERM")return false}if(!t)return true;return await ne(e)===t}var W=e=>new Promise(t=>setTimeout(t,e));var X=process.env.BUOY_FARM_DISABLED_ROOT||"/private/var/tmp";function st(e,t=X){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return Te.join(t,`com.apple.CoreSimulator.SimDevice.${e}`)}function it(e,t=X){return Te.join(st(e,t),"disabled.plist")}async function nt(e,t=X){const s=it(e,t);let i;try{i=await V.lstat(s)}catch{return{}}if(!i.isFile())throw new Error(`refusing ${s}: not a regular file`);const n=await P("plutil",["-convert","json","-o","-",s],{timeoutMs:1e4});const a=JSON.parse(n);const o={};for(const[r,l]of Object.entries(a))if(typeof l==="boolean")o[r]=l;return o}function ls(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function cs(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>${ls(i)}</key>
1
+ import{createRequire as ts}from"module";const xi=ts(import.meta.url);var F=1;var me=1;var Pe={parkAfterMinutes:10,maxConcurrentBoots:3,reserveRamGB:24,reserveDiskGB:40,slimSimGB:2,stockSimGB:6,cloneDiskGB:2,freezePilot:false,runCapMinutes:60,keepRunsHours:24,fairMinFreeShare:.25,fairMaxSwingShare:.12,fairMaxThermalLevel:0,fairMaxSwapGrowthMB:256,phoneProfile:"iphone-se-2nd-gen",phoneOnLimit:"warn",phoneCapMB:0};var d=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 ss}from"crypto";var Je=["com.apple.sharingd"];var se=[{"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 Ve(){const e=new Set(Je);const t=new Set;for(const s of se)for(const i of s.labels)if(!e.has(i))t.add(i);return[...t].sort()}function J(e){return e==="slim"?Ve():[]}function Xe(e){return ss("sha256").update(e+"\n"+J(e).join("\n")).digest("hex").slice(0,16)}import{promises as A,existsSync as Ze}from"fs";import D from"path";import{AsyncLocalStorage as is}from"async_hooks";import{createHash as ns,randomBytes as fe}from"crypto";import{promises as V}from"fs";import Te from"path";import{randomBytes as as}from"crypto";import{spawn as os}from"child_process";var ie=new Set;function Re(){return ie.size}function rs(){for(const e of ie){try{e.kill("SIGKILL")}catch{}}}function M(e,t,s={}){const{timeoutMs:i=3e4,maxBytes:n=4*1024*1024}=s;return new Promise(a=>{const o=os(e,t,{env:s.env??process.env,stdio:[s.input==null?"ignore":"pipe","pipe","pipe"]});ie.add(o);let r="";let l="";let c=false;o.stdout.on("data",h=>{if(r.length<n)r+=h.toString("utf8")});o.stderr.on("data",h=>{if(l.length<n)l+=h.toString("utf8")});if(s.input!=null)o.stdin.end(s.input);const p=setTimeout(()=>{c=true;o.kill("SIGTERM");setTimeout(()=>o.kill("SIGKILL"),2e3).unref?.()},i);o.on("error",h=>{ie.delete(o);clearTimeout(p);a({code:null,stdout:r,stderr:l+String(h),timedOut:c})});o.on("close",h=>{ie.delete(o);clearTimeout(p);a({code:h,stdout:r,stderr:l,timedOut:c})})})}async function P(e,t,s={}){const i=await M(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 et(){const e=await P("ps",["-axww","-o","pid=,ppid=,lstart=,stat=,comm="],{timeoutMs:15e3});const t=await P("ps",["-axww","-o","pid=,command="],{timeoutMs:15e3});const s=new Map;for(const n of t.split("\n")){const a=/^\s*(\d+)\s+(.*)$/.exec(n);if(a)s.set(Number(a[1]),a[2])}const i=[];for(const n of e.split("\n")){const a=/^\s*(\d+)\s+(\d+)\s+(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})\s+(\S+)\s+(.*)$/.exec(n);if(!a)continue;const o=Number(a[1]);i.push({pid:o,ppid:Number(a[2]),start:a[3].replace(/\s+/g," "),stat:a[4],comm:a[5].trim(),command:s.get(o)??a[5].trim()})}return i}function tt(e,t){const s=new Map;const i=new Map;for(const l of e){i.set(l.pid,l);const c=s.get(l.ppid)??[];c.push(l);s.set(l.ppid,c)}const n=[];const a=i.get(t);if(!a)return n;const o=[a];const r=new Set;while(o.length){const l=o.pop();if(r.has(l.pid))continue;r.add(l.pid);n.push(l);for(const c of s.get(l.pid)??[])o.push(c)}return n}async function ne(e){const t=await M("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 q(e,t,s=false){if(!Number.isInteger(e)||e<=0)return false;try{process.kill(e,0)}catch(n){if(n.code!=="EPERM")return false}if(!t)return true;const i=await ne(e);if(i===null)return s;return i===t}var W=e=>new Promise(t=>setTimeout(t,e));var X=process.env.BUOY_FARM_DISABLED_ROOT||"/private/var/tmp";function st(e,t=X){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return Te.join(t,`com.apple.CoreSimulator.SimDevice.${e}`)}function it(e,t=X){return Te.join(st(e,t),"disabled.plist")}async function nt(e,t=X){const s=it(e,t);let i;try{i=await V.lstat(s)}catch{return{}}if(!i.isFile())throw new Error(`refusing ${s}: not a regular file`);const n=await P("plutil",["-convert","json","-o","-",s],{timeoutMs:1e4});const a=JSON.parse(n);const o={};for(const[r,l]of Object.entries(a))if(typeof l==="boolean")o[r]=l;return o}function ls(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function cs(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>${ls(i)}</key>
2
2
  <${e[i]?"true":"false"}/>
3
3
  `;return s+"</dict>\n</plist>\n"}async function at(e,t,s=X){const i=st(e,s);await V.mkdir(i,{recursive:true});const n=await V.lstat(i);if(!n.isDirectory())throw new Error(`refusing ${i}: not a directory`);const a=await nt(e,s);const o=Te.join(i,"buoy-farm-prior.json");let r={};try{r=JSON.parse(await V.readFile(o,"utf8"))}catch{}const l={...a};for(const c of t){if(!(c in r))r[c]=c in a?a[c]:null;l[c]=true}await ot(o,JSON.stringify(r,null,2)+"\n");await ot(it(e,s),cs(l))}async function ds(e,t,s=X){const i=await nt(e,s);return t.every(n=>i[n]===true)}async function ot(e,t){const s=`${e}.${as(4).toString("hex")}.tmp`;const i=await V.open(s,"w",420);try{await i.writeFile(t);await i.sync()}finally{await i.close()}await V.rename(s,e)}import{promises as T,existsSync as Y,readFileSync as Qi}from"fs";import S from"path";import{createHash as rt}from"crypto";var lt=["@react_buoy_sync_device_id.json","@react_buoy_install.json"];var us="@buoy~1floating-tool~1*";function ae(e){if(!Y(S.join(e,"seed.json")))return`No seed at ${e}. Run: pnpm e2e:pool seed`;if(Y(S.join(e,"Documents/buoy-license"))){for(const t of["buoy-gg-license.json","buoy-gg-device-fingerprint.json"]){if(!Y(S.join(e,"Documents/buoy-license",t)))return`The seed lacks ${t}.`}}return null}async function ps(e){const t=rt("sha256");async function s(i){let n;try{n=await T.readdir(i,{withFileTypes:true})}catch{return}n.sort((a,o)=>a.name.localeCompare(o.name));for(const a of n){const o=S.join(i,a.name);if(a.isDirectory())await s(o);else{const r=await T.stat(o);t.update(`${S.relative(e,o)}:${r.size}:${Math.floor(r.mtimeMs)}
4
4
  `)}}}await s(e);return t.digest("hex").slice(0,16)}async function hs(e){const t=rt("sha256");const s=S.join(e,"Info.plist");t.update(await T.readFile(s));const i=JSON.parse(await P("plutil",["-convert","json","-o","-",s],{timeoutMs:1e4}));const n=S.join(e,String(i.CFBundleExecutable??""));const a=await T.stat(n);t.update(`${a.size}:${Math.floor(a.mtimeMs)}`);return t.digest("hex").slice(0,16)}async function ms(e){const t=JSON.parse(await P("plutil",["-convert","json","-o","-",S.join(e,"Info.plist")],{timeoutMs:1e4}));if(!t.CFBundleIdentifier)throw new Error(`${e} has no CFBundleIdentifier`);return String(t.CFBundleIdentifier)}async function we(e,t){const s=S.join(e,"Containers/Data/Application");let i;try{i=await T.readdir(s)}catch{return null}for(const n of i){const a=S.join(s,n,".com.apple.mobile_container_manager.metadata.plist");if(!Y(a))continue;const o=await M("plutil",["-extract","MCMMetadataIdentifier","raw","-o","-",a],{timeoutMs:5e3});if(o.code===0&&o.stdout.trim()===t)return S.join(s,n)}return null}function fs(e,t){return S.join(e,"Library/Application Support",t,"RCTAsyncLocalStorage_V1")}async function ct(e,t,s,i){i();await T.mkdir(t,{recursive:true});const n=["-a","--delete"];for(const a of s)n.push("--exclude",a);n.push(e.replace(/\/?$/,"/"),t.replace(/\/?$/,"/"));i();await P("rsync",n,{timeoutMs:6e4})}async function dt(e,t,s,i=()=>{}){i();const n=ae(s);if(n)throw new Error(n);const a=[...lt.map(l=>`react-buoy/${l}`),`react-buoy/${us}`];if(Y(S.join(s,"Documents")))await ct(S.join(s,"Documents"),S.join(e,"Documents"),a,i);if(Y(S.join(s,"AsyncStorage/manifest.json"))){await ct(S.join(s,"AsyncStorage"),fs(e,t),[],i)}const o=S.join(s,"Preferences",`${t}.plist`);if(Y(o)){i();await T.mkdir(S.join(e,"Library/Preferences"),{recursive:true});await T.copyFile(o,S.join(e,"Library/Preferences",`${t}.plist`))}const r=S.join(e,"Documents/react-buoy");try{for(const l of await T.readdir(r)){i();if(l.startsWith("@buoy~1floating-tool~1"))await T.rm(S.join(r,l),{force:true,recursive:true})}}catch{}}async function ge(e,t=()=>{}){t();const s=[];const i=S.join(e,"Documents/react-buoy");let n=[];try{n=await T.readdir(i)}catch{return s}for(const a of n){if(lt.includes(a)||a.startsWith("@buoy~1floating-tool~1")){t();await T.rm(S.join(i,a),{force:true,recursive:true});s.push(a)}}return s}async function ut(e){let t;try{t=await T.readFile(S.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 pt=2*60*6e4;var ws=10;var gs=5;var ys=.8;var z=e=>Math.round(e*10)/10;var vs=e=>{if(!e.length)return 0;const t=[...e].sort((i,n)=>i-n);const s=t.length>>1;return t.length%2?t[s]:(t[s-1]+t[s])/2};var ht=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>=ws){const g=await Promise.all(e.udids.map(w=>this.host.simTree(w)));this.pids=g.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%gs===0);this.cpuCount=i.cpuCount;const n=this.now();const a=this.prev;this.prev={...i,at:n,leaseId:e.leaseId};const o=g=>(i.hostTotalMs-g)/Math.max(1,i.cpuCount);if(i.pidsCpuMs!==null&&i.graphicsCpuMs!==null){const g=this.lastProcs;const w=g?o(g.hostTotalMs):0;if(g&&w>0){this.rates.ours=Math.max(0,(i.pidsCpuMs-g.pidsCpuMs)/w);this.rates.graphics=Math.max(0,(i.graphicsCpuMs-g.graphicsCpuMs)/w)}this.rates.gpuPct=i.gpuPct;this.lastProcs={hostTotalMs:i.hostTotalMs,pidsCpuMs:i.pidsCpuMs,graphicsCpuMs:i.graphicsCpuMs}}if(!a||a.leaseId!==e.leaseId)return;const r=o(a.hostTotalMs);if(r<=0)return;const l=Math.max(0,(i.hostBusyMs-a.hostBusyMs)/r);const c=this.rates.ours;const p=this.rates.graphics;this.samples.push({at:Math.round((n+a.at)/2),leaseId:e.leaseId,freeCores:z(Math.max(0,i.cpuCount-l)),ourCores:z(c),graphicsCores:z(p),outsideCores:z(Math.max(0,l-c-p)),gpuPct:this.rates.gpuPct,memLevel:i.memPressureLevel,swapMB:i.swapUsedMB,thermal:i.thermalLevel});const h=n-pt;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 n=t.map(o=>{const r=this.samples.filter(c=>c.at>=o.from-i&&c.at<=o.to+i);const l=r.filter(c=>c.leaseId===e);return{w:o,all:r,mine:l}});const a=vs(n.filter(o=>o.mine.length).map(o=>Ne(o.mine.map(r=>r.outsideCores))));return n.map(({w:o,all:r,mine:l})=>{const c=Math.max(1,Math.round((o.to-o.from)/this.periodMs));const p={id:o.id,fair:false,seconds:l.length};if(!r.length&&(o.from>this.now()+6e4||o.to<this.now()-pt)){return{...p,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(!l.length||l.length<r.length||l.length<Math.floor(c*ys)){return{...p,reason:"No load record for all of this run. Hold perf-slot for the whole test."}}const h=l.map(f=>f.outsideCores);const g=l.map(f=>f.gpuPct).filter(f=>f!==null);const w=l.map(f=>f.thermal).filter(f=>f!==null);const k={...p,freeCoresMin:Math.min(...l.map(f=>f.freeCores)),outsideCoresAvg:z(Ne(h)),outsideCoresMax:Math.max(...h),ourCoresAvg:z(Ne(l.map(f=>f.ourCores))),graphicsCoresMax:Math.max(...l.map(f=>f.graphicsCores)),gpuMaxPct:g.length?Math.max(...g):null,swapGrowthMB:Math.round(Math.max(0,l[l.length-1].swapMB-(this.before(e,o.from)?.swapMB??l[0].swapMB))),thermalMax:w.length?Math.max(...w):null};if(k.thermalMax!=null&&k.thermalMax>s.maxThermalLevel){return{...k,reason:"The Mac got hot and slowed itself down. Let it cool, then run again."}}if(l.some(f=>f.memLevel>=4)){return{...k,reason:"The Mac ran low on memory. Close apps or stop sims you don't need."}}if(k.swapGrowthMB>s.maxSwapGrowthMB){return{...k,reason:`The Mac swapped ${k.swapGrowthMB} MB of memory to disk.`}}if(k.freeCoresMin<s.minFreeCores){return{...k,reason:`The Mac ran out of spare CPU (${k.freeCoresMin} cores free, needs ${s.minFreeCores}).`}}const $=z(k.outsideCoresMax-a);if($>s.maxSwingCores){return{...k,reason:`Other work on the Mac jumped by ${$} cores during this run.`}}return{...k,fair:true}})}};function Ne(e){return e.length?e.reduce((t,s)=>t+s,0)/e.length:0}function mt(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{execFile as Ss}from"child_process";import{promises as E,readFileSync as ks}from"fs";import $e from"os";import R from"path";import{promisify as bs}from"util";var Ms=`// BuoyPhoneProfile: makes an app on the iOS Simulator act like a real iPhone.
@@ -360,7 +360,7 @@ static void buoyPhoneProfileInit(void) {
360
360
  startWatchdog();
361
361
  }
362
362
  }
363
- `;var _s="499c4e3de0f8";var Bs='// 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 ye=bs(Ss);var ft={"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 wt="iphone-se-2nd-gen";var Le="off";function x(){return process.env.BUOY_FARM_HOME||R.join($e.homedir(),".buoy","farm")}function ve(e=x()){return R.join(e,"phone")}function gt(e=x()){return R.join(ve(e),"phones.json")}function yt(e){const t=e;const s=(i,n,a)=>typeof i==="number"&&Number.isInteger(i)&&i>=n&&i<=a;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 Z(e=x()){const t={...ft};try{const s=JSON.parse(ks(gt(e),"utf8"));for(const[i,n]of Object.entries(s?.phones??{})){if(/^[a-z0-9][a-z0-9-]{0,63}$/.test(i)&&yt(n))t[i]=n}}catch{}return t}async function vt(e,t,s=x()){if(!/^[a-z0-9][a-z0-9-]{0,63}$/.test(e))throw new Error(`Bad phone id ${e}.`);if(!yt(t))throw new Error(`Bad phone numbers for ${e}.`);const i=gt(s);let n={phones:{}};try{n=JSON.parse(await E.readFile(i,"utf8"));if(!n||typeof n.phones!=="object")n={phones:{}}}catch{}n.phones[e]=t;await E.mkdir(R.dirname(i),{recursive:true});const a=`${i}.${process.pid}.tmp`;await E.writeFile(a,JSON.stringify(n,null,2)+"\n");await E.rename(a,i)}function St(e){return Math.max(3072,e.limitMB+1024)}function kt(e,t,s){const i=t.capMB>e.limitMB?t.capMB:St(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 Ce(...e){const t=new Set;const s=[];for(const i of e){for(const n of(i??"").split(":")){const a=n.trim();if(a&&!t.has(a)){t.add(a);s.push(a)}}}return s.join(":")}function oe(e,t=x()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return R.join(ve(t),`${e}.launch.json`)}function Se(e,t=x()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return R.join(ve(t),`${e}.events.jsonl`)}async function bt(e,t=x()){const s=oe(e.udid,t);await E.mkdir(R.dirname(s),{recursive:true});const i=`${s}.${process.pid}.tmp`;await E.writeFile(i,JSON.stringify(e,null,2)+"\n");await E.rename(i,s)}async function ke(e,t=x()){await E.rm(oe(e,t),{force:true})}async function Mt(e,t=x()){try{const s=JSON.parse(await E.readFile(oe(e,t),"utf8"));if(s?.udid!==e||typeof s.dylib!=="string"||!s.vars||typeof s.vars!=="object")return null;await E.access(s.dylib);return s}catch{return null}}async function Es(e){try{const{stdout:t}=await ye("xcrun",["simctl","spawn",e,"launchctl","getenv","DYLD_INSERT_LIBRARIES"],{timeout:1e4});return t.trim()}catch{return""}}async function xe(e,t,s,i=x(),n=Es){const a=await Mt(e,i);if(!a||a.bundleId!==t){return s?{SIMCTL_CHILD_DYLD_INSERT_LIBRARIES:Ce(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await n(e),s)}:{}}const o={};for(const[r,l]of Object.entries(a.vars))o[`SIMCTL_CHILD_${r}`]=l;o.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES=Ce(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await n(e),s,a.dylib);return o}function _t(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 Bt(e){let t=-1;for(let a=e.length-1;a>=0;a--)if(e[a].event==="profile_loaded"){t=a;break}if(t<0)return{loaded:false,ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};const s=e[t];const i=e.slice(t).filter(a=>a.pid===s.pid);const n={pid:s.pid,loaded:true,ignored:Array.isArray(s.ignored)?s.ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};for(const a of i){n.peakMB=Math.max(n.peakMB,a.footprintMB??0,typeof a.peakMB==="number"?a.peakMB:0);n.lastFootprintMB=a.footprintMB;if(a.event==="memory_warning")n.memoryWarnings++;if(a.event==="over_limit")n.overLimit=true;if(a.event==="jetsam_kill"){n.overLimit=true;n.ended="limit"}if(a.event==="safety_kill"){n.overLimit=true;n.ended="safety"}}return n}async function be(e,t=x()){const s=Se(e,t);try{const i=await E.open(s,"r");try{const{size:n}=await i.stat();const a=Math.min(n,256*1024);const o=Buffer.alloc(a);await i.read(o,0,a,n-a);let r=o.toString("utf8");if(a<n)r=r.slice(r.indexOf("\n")+1);return Bt(_t(r))}finally{await i.close()}}catch{return null}}async function Is(){try{const{stdout:e}=await ye("xcodebuild",["-version"],{timeout:3e4});return e.trim().split("\n").pop().replace(/^Build version\s*/,"").replace(/[^\w.-]/g,"")||"unknown"}catch{return"unknown"}}async function Et(e){const t=R.join(e,"BuoyPhoneProfile.dylib");try{await E.access(t);return t}catch{}const s=await E.mkdtemp(R.join($e.tmpdir(),"buoy-phone-profile-"));try{const i=R.join(s,"BuoyPhoneProfile.m");await E.writeFile(i,Ms);const n=R.join(s,"BuoyPhoneProfile.dylib");await ye("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",n],{timeout:18e4,maxBuffer:8*1024*1024,env:{...process.env,TMPDIR:s}});await ye("codesign",["-f","-s","-",n],{timeout:6e4});await E.mkdir(e,{recursive:true});const a=`${t}.${process.pid}`;await E.copyFile(n,a);await E.rename(a,t);return t}finally{await E.rm(s,{recursive:true,force:true}).catch(()=>void 0)}}var It=class{constructor(e=R.join($e.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=Is().then(t=>Et(R.join(this.root,`${t}-${_s}`))).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 ee=1024**3;var Ue="Buoy Farm ";var je=".buoy-farm.json";var Os=120;var As=180;var Fe=9e4;var Ot=500;var At=200;var Ds=3;var H=["done","failed","stopped","interrupted"];var Dt=["setting-up","working","waiting","passed","failed","needs-help"];var Ps={now:()=>performance.now()};var Ts=10*6e4;var Pt=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 Rs=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 Ns=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 Tt=class{constructor(e){this.phoneFailed=new Map;this.simLock=new Pt;this.deadlines=new Map;this.reservations=new Map;this.busy=new Rs;this.ticking=false;this.hardDeadlines=new Map;this.acquiring=new Set;this.budgetLock=new Pt;this.labels=new Map;this.inflight=new Map;this.workContext=new is;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:(t,s)=>{const i=Reflect.get(t,s);if(typeof i!=="function")return i;return async(...n)=>{this.store.assertActive();const a=await i.apply(t,n);this.store.assertActive();return a}}});this.store=e.store;this.clock=e.clock??Ps;this.log=e.log??(()=>{});this.pollMs=e.pollMs??1e3;this.readyTimeoutMs=e.readyTimeoutMs??12e4;this.phoneBuilder=e.phoneBuilder??new It;this.phoneLoadTimeoutMs=e.phoneLoadTimeoutMs??1e4;this.bootGate=new Ns(this.state.settings.maxConcurrentBoots);this.meter=new ht(this.host,()=>{const t=this.state.locks["perf-slot"];const s=t&&this.state.leases[t.leaseId];return s?{leaseId:s.id,udids:s.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,n,a){this.touch(this.liveLease(e,t,a));const o=this.state.locks["perf-slot"];if(i==="screenshot"&&o&&o.leaseId!==e)throw new d("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,n,a))}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 W(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[i,n]of Object.entries(this.state.locks)){if(!this.state.leases[n.leaseId])delete this.state.locks[i]}const e=await this.host.listDevices();const t=new Map(e.map(i=>[i.udid,i]));for(const i of Object.values(this.state.sims)){const n=t.get(i.udid);if(!n){this.store.event({kind:"lost",message:`${i.name} was deleted outside the farm.`,udid:i.udid});if(i.leaseId)this.dropFromLease(i.leaseId,i.udid);await this.files(()=>this.forgetPhone(i.udid)).catch(()=>void 0);delete this.state.sims[i.udid];continue}if(n.state==="Booted"){i.power=i.power==="frozen"?"frozen":"awake";if(i.power!=="frozen"){const a=this.state.templates[i.templateId];const o=await this.health(i.udid,a?J(a.profile):[]);i.profile=o.profile;i.lastCheckAt=new Date().toISOString();if(!o.ok||a?.profile==="slim"&&o.profile==="unknown"){i.health="suspect";i.lastError=o.faults.join("; ")||"Slim state is not known."}}}else{i.power=i.power==="parked"?"parked":"cold"}if(["creating","booting","thawing","deleting"].includes(i.power))i.power=n.state==="Booted"?"awake":"cold"}for(const i of Object.values(this.state.templates)){const n=t.get(i.udid);if(!n){i.usable=false;i.note="Its sim was deleted outside the farm."}else if(!Ze(i.appPath)){i.note="Its app build is gone. Clones still work; rebuild the template for a new build."}}for(const i of Object.keys(this.state.orphans)){if(!t.has(i))delete this.state.orphans[i]}for(const i of this.store.pending){if(i.op==="clone"){const n=i.data?.name;const a=e.find(o=>o.name===n&&!this.state.sims[o.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(i.op!=="rebuild")this.store.result(i.seq,i.op,{recovered:true})}const s=new Set;for(const i of[...this.store.pending]){if(i.op!=="rebuild")continue;const{udid:n,name:a,templateId:o}=i.data;if(Object.values(this.state.orphans).some(r=>r.name===a))continue;s.add(n);this.busy.add(n);this.background(`resume rebuild ${a}`,()=>this.work(void 0,async()=>{try{const r=Object.values(this.state.sims).find(l=>l.name===a&&l.udid!==n);if(!r){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(o,a)}this.store.result(i.seq,"rebuild",{recovered:true})}finally{this.busy.delete(n)}}))}for(const i of Object.values(this.state.leases)){this.deadlines.set(i.id,this.clock.now()+Fe);const n=Date.parse(i.createdAt)+i.ttlMs-Date.now();this.hardDeadlines.set(i.id,this.clock.now()+Math.max(n,0));if(n<=0)this.background(`expire ${i.id}`,()=>this.endLease(i.id,"ran out of time while the farm was down","full","deadline"))}for(const i of Object.values(this.state.sims)){if(i.health==="dirty"&&!i.leaseId&&!s.has(i.udid)){this.busy.add(i.udid);this.background(`resume reset ${i.name}`,async()=>{try{await this.rebuildSim(i.udid)}finally{this.busy.delete(i.udid)}})}}this.store.save()}async dataPath(e){const t=(await this.host.listDevices()).find(s=>s.udid===e);if(!t)throw new d("NOT_FOUND",`No sim ${e} on this Mac.`);return t.dataPath}async isOwned(e){const t=this.state.sims[e]??Object.values(this.state.templates).find(s=>s.udid===e);if(!t)return false;try{const s=JSON.parse(await A.readFile(D.join(await this.dataPath(e),je),"utf8"));return s.farmId===this.state.farmId}catch{return false}}async assertOwned(e){if(!await this.isOwned(e)){throw new d("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 A.mkdir(s,{recursive:true});this.checkActive();await A.writeFile(D.join(s,je),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}return t}async budget(e={ramGB:0,diskGB:0}){const t=this.state.settings;const s=await this.host.hostStats();let i=0;let n=0;for(const w of this.reservations.values()){i+=w.ramGB;n+=w.diskGB}const a=s.totalMemBytes/ee;const o=Ls(a)*(s.memPressure==="warn"?2:1);const r=s.availableMemBytes/ee-i;const l=a-$s(a,t.reserveRamGB)-this.farmAwakeGB()-i;const c=s.freeDiskBytes/ee-n-t.reserveDiskGB;const p=Math.max(0,Math.floor(Math.min(r-o,l,c/t.cloneDiskGB*t.slimSimGB)/t.slimSimGB));const h=(w,k)=>({ok:false,code:w,reason:k,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&&c-e.diskGB<0){return h("DISK_LOW",`Need ${t.reserveDiskGB} GB free after this. This Mac has ${Math.floor(s.freeDiskBytes/ee)} GB.`)}if(e.ramGB>0){if(r-e.ramGB<o||l-e.ramGB<0){return h("RAM_LOW",`Not enough free memory for another sim (${Math.max(0,Math.floor(r))} GB free).`)}}const g=this.state.locks["perf-slot"];if(g&&(!this.workContext.getStore()?.active||this.workContext.getStore()?.leaseId!==g.leaseId))return h("HOST_BUSY","A speed test is running. New sims will wait.");return{ok:true,stats:s,roomFor:p}}async withReservation(e,t){const s=fe(4).toString("hex");await this.budgetLock.run("budget",async()=>{const i=await this.budget(e);if(!i.ok)throw new d(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 n=new Set(i.map(r=>r.comm.split("/").pop()));for(const r of["SpringBoard","backboardd"])if(!n.has(r))s.push(`${r} is not running`);const a=Rt(await this.host.launchdLogTail(e));if(a)s.push(`${a} keeps crashing`);if(s.length===0){const r=D.join(this.store.shotsPath,`${e}.png`);await this.files(()=>A.mkdir(D.dirname(r),{recursive:true}));if(!await this.host.screenshot(e,r,1e4))s.push("screenshot did not come back")}let o=i.length<Os?"slim":i.length>As?"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 l=new Set([...r.stdout.matchAll(/"([^"]+)" => (?:disabled|true)/g)].map(p=>p[1]));const c=t.filter(p=>!l.has(p)).length;o=c===0?"slim":c===t.length?"stock":"unknown"}else{o="unknown"}}return{ok:s.length===0,profile:o,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 W(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 d("BAD_REQUEST","Use slim or stock and a port from 1 to 65535.")}const t=e.seedDir==null?null:ae(e.seedDir);if(t)throw new d("NO_SEED",t,"Run: pnpm e2e:pool seed");if(!Ze(D.join(e.appPath,"Info.plist"))){throw new d("BAD_REQUEST",`No app build at ${e.appPath}.`,"Build the app first.")}const s=await ms(e.appPath);const i=`t-${fe(3).toString("hex")}`;const n=e.name??`${Ue}Template ${i.slice(2)}`;return this.withReservation({ramGB:this.state.settings.stockSimGB,diskGB:4},async()=>{const a=this.store.intent("create-template",{id:i,name:n});const o=await this.host.create(n,e.deviceType,e.runtime);const r={id:i,udid:o,name:n,bundleId:s,appPath:e.appPath,appHash:await hs(e.appPath),seedDir:e.seedDir??null,seedHash:e.seedDir?await ps(e.seedDir):null,launchUrl:e.launchUrl,deviceType:e.deviceType,runtime:e.runtime,xcodeBuild:await this.host.xcodeBuild(),profile:e.profile,profileHash:Xe(e.profile),metroPort:e.metroPort,createdAt:new Date().toISOString(),usable:false,note:"Being built."};this.state.templates[i]=r;await this.writeMarker(o,{template:i});this.store.result(a,"create-template",{udid:o});try{await this.buildTemplate(r,e.warmLaunch!==false);r.usable=true;r.note=void 0;this.store.event({kind:"template",message:`Template ${n} is ready.`,udid:o})}catch(l){r.note=`Build failed: ${l.message}`;this.store.event({kind:"template-failed",message:r.note,udid:o});await this.host.shutdown(o).catch(()=>{});throw l}finally{this.store.save()}return r})}async buildTemplate(e,t){const s=J(e.profile);if(s.length)await this.files(()=>at(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 n=await this.host.appContainer(e.udid,e.bundleId);if(!n)throw new Error("app installed but has no data container");if(e.seedDir)await this.files(()=>dt(n,e.bundleId,e.seedDir,this.checkActive));else await this.files(()=>ge(n,this.checkActive));await this.setPrefs(e.udid,e);if(t){await this.launchApp(e.udid,e,{phone:false});await W(Math.min(15e3,this.pollMs*15));await this.host.terminate(e.udid,e.bundleId)}await this.host.shutdown(e.udid);await this.files(()=>ge(n,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 n=B=>{const L=this.state.sims[e];if(L)L.phone=B};const a=async B=>{const L=await this.host.spawn(B,["launchctl","getenv","DYLD_INSERT_LIBRARIES"],1e4).catch(()=>null);return L&&L.code===0?L.stdout.trim():""};const o=async B=>{await this.files(()=>ke(e,s));const L=await xe(e,t,void 0,s,a);await this.host.launch(e,t,L);n(B)};if(i.phoneProfile===Le)return o({status:"off"});const r=Z(s);const l=r[i.phoneProfile]?i.phoneProfile:wt;const c=r[l];const p=l!==i.phoneProfile?`No phone "${i.phoneProfile}" on this Mac, so it uses ${c.name}. `:"";const h=this.phoneFailed.get(t);if(h!==void 0&&performance.now()-h<Ts){return o({status:"fallback",phoneId:l,phoneName:c.name,note:`${p}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 g=await this.phoneBuilder.dylib();if(!g){const B=this.phoneBuilder.lastError()||"unknown error";this.log(`phone profile: could not build the library: ${B}`);return o({status:"fallback",phoneId:l,phoneName:c.name,note:`${p}The phone profile could not be built (${B}), so the app runs as a plain simulator app.`})}const w=Se(e,s);await this.files(async()=>{await A.mkdir(D.dirname(w),{recursive:true});await A.writeFile(w,"");await bt({udid:e,bundleId:t,dylib:g,phoneId:l,vars:kt(c,{phone:l,onLimit:i.phoneOnLimit,capMB:i.phoneCapMB},w),writtenAt:new Date().toISOString()},s)});const k=await xe(e,t,void 0,s,a);await this.host.launch(e,t,k);const $=performance.now()+this.phoneLoadTimeoutMs;let f=null;for(;;){f=await be(e,s);if(f?.loaded||performance.now()>=$)break;await W(Math.min(250,Math.max(10,this.pollMs/4)))}if(f?.loaded){this.phoneFailed.delete(t);const B=f.ignored.length?` Ignored bad settings: ${f.ignored.join(", ")}.`:"";n({status:"on",phoneId:l,phoneName:c.name,pid:f.pid,note:(p+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 o({status:"fallback",phoneId:l,phoneName:c.name,note:`${p}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(`${Ue}${String(s).padStart(2,"0")}`))s++;this.state.nextIndex=s+1;return`${Ue}${String(s).padStart(2,"0")}`}template(e){const t=this.state.templates[e];if(!t)throw new d("NO_TEMPLATE",`No template ${e}.`,"Run sim_farm_setup to make one.");if(!t.usable)throw new d("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 n=this.template(e);return this.withReservation({ramGB:0,diskGB:this.state.settings.cloneDiskGB},()=>this.simLock.run(`template:${n.id}`,async()=>{await this.assertOwned(n.udid);const a=this.store.intent("clone",{templateId:e,name:i});const o=await this.host.clone(n.udid,i);const r={udid:o,name:i,templateId:e,power:"creating",profile:"unknown",health:"ok",bootEpoch:0,failures:0,createdAt:new Date().toISOString()};this.state.sims[o]=r;this.store.result(a,"clone",{udid:o});await this.writeMarker(o,{template:n.id});const l=await we(await this.dataPath(o),n.bundleId);if(l)await this.files(()=>ge(l,this.checkActive));const c=s?this.state.leases[s.leaseId]:void 0;if(c&&!this.leaseOverdue(c)){r.leaseId=c.id;c.sims=c.sims.map(p=>p===s.udid?o:p)}r.power="cold";this.store.event({kind:"clone",message:`Made ${t}.`,udid:o});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 d("BAD_REQUEST",`${s.name} is frozen. Thaw it first.`)}const i=this.state.templates[s.templateId];const n=this.state.settings;const a={ramGB:i?.profile==="slim"?n.slimSimGB:n.stockSimGB,diskGB:0};return this.withReservation(a,()=>this.bootGate.use(()=>this.simLock.run(e,async()=>{this.assertLeaseState(s,t);if(s.power==="awake")return s;const o=i?J(i.profile):[];if(o.length&&!await ds(e,o,this.host.disabledRoot)){await this.files(()=>at(e,o,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"?o:[]);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 d("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 d("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 l=await this.budget({ramGB:n.stockSimGB-n.slimSimGB,diskGB:0});if(!l.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 d("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 d("NOT_FOUND",`${e.name} is gone.`);if(t){const s=this.state.leases[t];if(e.leaseId!==t||!s||this.leaseOverdue(s)){throw new d("LEASE_EXPIRED",`${e.name} is no longer in lease ${t}.`)}}else if(e.leaseId){throw new d("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 d("BAD_REQUEST",`${s.name} is being reset.`);if(s.power==="frozen"||s.power==="thawing")throw new d("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 d("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 n=(await this.host.listDevices()).find(r=>r.udid===e);if(!n)throw new d("NOT_FOUND","This sim is gone.");let a=false;try{const r=JSON.parse(await A.readFile(D.join(n.dataPath,je),"utf8"));a=r.farmId===this.state.farmId}catch(r){a=r.code==="ENOENT"&&n.name===s.cloneIntentName}if(!a)throw new d("NOT_OWNED","This sim has no valid farm mark.");const o=this.store.intent("delete",{udid:e});await this.host.shutdown(e);await this.host.delete(e);await this.files(()=>A.rm(D.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(o,"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 d("BAD_REQUEST",`${i.name} is frozen. Wake it first.`);i.power="deleting";const n=this.store.intent("delete",{udid:e});await this.host.shutdown(e).catch(()=>{});await this.host.delete(e);await this.files(()=>A.rm(D.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(n,"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:n}=s;await this.assertOwned(e);await this.simLock.run(e,async()=>{this.assertLeaseState(s,t);this.busy.add(e)});const a=this.store.intent("rebuild",{udid:e,name:i,templateId:n});try{await this.deleteSim(e,t);const o=await this.cloneSimWork(n,i,t?{udid:e,leaseId:t}:void 0);this.store.result(a,"rebuild",{udid:o.udid});return o}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 d("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 n=await we(await this.dataPath(e),i.bundleId);if(!n)throw new Error("app container not found");if(i.seedDir)await this.files(()=>dt(n,i.bundleId,i.seedDir,this.checkActive));else await this.files(()=>ge(n,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 d("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 d("BAD_REQUEST","count must be 1 to 64.");const s=["cold","parked","awake"];const i=Object.values(this.state.sims).filter(a=>a.templateId===e&&!a.leaseId&&s.includes(a.power)&&!this.busy.has(a.udid)).sort((a,o)=>s.indexOf(a.power)-s.indexOf(o.power));const n=[];for(const a of i.slice(0,t)){try{await this.deleteSim(a.udid);n.push(a.name)}catch(o){if(!(o instanceof d))throw o}}return n}async removeTemplate(e){return this.work(void 0,async()=>{const t=this.state.templates[e];if(!t)throw new d("NO_TEMPLATE",`No template ${e}.`);const s=Object.values(this.state.sims).filter(o=>o.templateId===e);const i=s.filter(o=>o.leaseId||this.busy.has(o.udid));if(i.length){throw new d("HOST_BUSY",`${i.map(o=>o.name).join(", ")} ${i.length===1?"is":"are"} in use.`,"End those uses first.",true)}const n=[];for(const o of s){await this.deleteSim(o.udid);n.push(o.name)}const a=(await this.host.listDevices()).some(o=>o.udid===t.udid);if(a){await this.assertOwned(t.udid);await this.simLock.run(t.udid,async()=>{const o=this.store.intent("delete",{udid:t.udid});await this.host.shutdown(t.udid).catch(()=>{});await this.host.delete(t.udid);await this.files(()=>A.rm(D.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${t.udid}`),{recursive:true,force:true}));this.store.result(o,"delete",{})})}delete this.state.templates[e];n.push(t.name);this.store.event({kind:"delete",message:`Removed template ${t.name} and its ${s.length} sim(s).`});return{removed:n}})}leaseOverdue(e){const t=this.clock.now();if(t>(this.hardDeadlines.get(e.id)??Infinity))return true;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 d("BAD_REQUEST","ttlMs must be a finite number above zero.")}const t=this.template(e.templateId);if(!Number.isInteger(e.count)||e.count<1||e.count>64)throw new d("BAD_REQUEST","count must be 1 to 64.");const s=Math.min(Math.max(e.ttlMs??15*6e4,3e4),24*36e5);const i=e.runId?this.ownRun(e.runId,e.owner.clientId):this.newRun(e,t.id,s);const n=Date.parse(i.deadlineAt)-Date.now();if(n<3e4)throw new d("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 a={id:`l-${fe(4).toString("hex")}`,epoch:1,owner:e.owner,purpose:e.purpose,runId:i.id,templateId:t.id,sims:[],createdAt:new Date().toISOString(),ttlMs:Math.min(s,n)};i.leases.push(a.id);i.lastActivityAt=a.createdAt;if(e.labels?.length)this.labels.set(a.id,e.labels);this.state.leases[a.id]=a;this.hardDeadlines.set(a.id,this.clock.now()+a.ttlMs);this.acquiring.add(a.id);const o=()=>this.deadlines.set(a.id,this.clock.now()+Math.min(a.ttlMs,Fe));o();const r=performance.now()+(e.waitMs??0);const l=()=>{if(!this.state.leases[a.id])throw new d("LEASE_EXPIRED","The lease ended while sims were being prepared.");if(this.leaseOverdue(a))throw new d("LEASE_EXPIRED","The lease ran out of time while sims were being prepared.")};try{while(a.sims.length<e.count){l();o();const c=await this.claimOne(a,t);l();if(c)continue;if(performance.now()>=r)break;if(!a.sims.length&&i.state==="active"&&!Object.keys(i.lanes).length)i.state="waiting";await W(Math.max(this.pollMs,2e3))}if(a.sims.length<e.count&&!e.allowPartial){const c=await this.budget({ramGB:this.state.settings.slimSimGB,diskGB:0});throw new d(c.code??"HOST_BUSY",`Got ${a.sims.length} of ${e.count} sims. ${c.reason??"Others are in use."}`,"Try again later or ask for fewer.",true)}if(a.sims.length===0)throw new d("HOST_BUSY","No sim is free right now.","Try again later.",true);if(e.launch)await this.work(a.id,async()=>{l();await Promise.all(a.sims.map(c=>this.launchApp(c,t)));o();await this.checkIdentities(a,6e4);l()});o();this.store.event({kind:"lease",message:`${e.owner.label} took ${a.sims.length} sim(s) for ${i.goal}.`,leaseId:a.id,runId:i.id});this.note(i,"farm",`Got ${a.sims.length} sim${a.sims.length===1?"":"s"}.`);this.store.save();return{lease:a,run:i,sims:a.sims.map(c=>this.state.sims[c])}}catch(c){if(this.state.leases[a.id])this.note(i,"farm",`Could not get sims: ${c.message}`);await this.endLease(a.id,"acquire failed","fast","acquire-failed").catch(()=>{});throw c}finally{this.acquiring.delete(a.id);this.labels.delete(a.id)}}async claimOneWork(e,t){const s=Object.values(this.state.sims).filter(o=>o.templateId===t.id&&!o.leaseId&&o.health==="ok"&&!this.busy.has(o.udid));const i=s.find(o=>o.power==="awake")??s.find(o=>o.power==="parked"||o.power==="cold");const n=async o=>{const r=await this.isOwned(o.udid);return this.simLock.run(o.udid,async()=>{if(o.leaseId||this.busy.has(o.udid)||o.health!=="ok"||!this.state.sims[o.udid])return null;if(!r){o.health="broken";o.lastError="farm marker missing";this.store.save();return null}if(!this.state.leases[e.id]||this.leaseOverdue(e))return null;o.leaseId=e.id;e.sims.push(o.udid);o.idleSince=void 0;this.addLane(e,o);this.store.save();return o})};if(i?.power==="awake")return n(i);let a=null;try{if(i){a=await n(i)}else{const o=await this.budget({ramGB:t.profile==="slim"?this.state.settings.slimSimGB:this.state.settings.stockSimGB,diskGB:this.state.settings.cloneDiskGB});if(!o.ok)return null;a=await n(await this.cloneSim(t.id))}if(!a)return null;await this.bootSim(a.udid,e.id);return a}catch(o){if(a)this.unclaim(e,a.udid);if(o instanceof d&&(o.code==="RAM_LOW"||o.code==="DISK_LOW"||o.code==="HOST_BUSY"||o.code==="LEASE_EXPIRED")){return null}if(a&&this.state.sims[a.udid]?.health!=="ok"){const r=a.udid;this.background(`rebuild ${r}`,()=>this.rebuildSim(r))}throw o}}unclaim(e,t){e.sims=e.sims.filter(n=>n!==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 n=new Map;for(;;){for(const o of e.sims){if(n.has(o))continue;const r=await we(await this.dataPath(o),s.bundleId);const l=r?await ut(r):null;if(!l)continue;const c=[...n.entries()].find(([,h])=>h===l);if(c)throw new d("ID_CLASH",`${this.state.sims[o].name} and ${this.state.sims[c[0]].name} share one Buoy ID.`);const p=Object.values(this.state.sims).find(h=>h.udid!==o&&!e.sims.includes(h.udid)&&h.buoyDeviceId===l);if(p)throw new d("ID_CLASH",`${this.state.sims[o].name} and ${p.name} share one Buoy ID.`);n.set(o,l);this.state.sims[o].buoyDeviceId=l}if(n.size===e.sims.length||performance.now()>=i)break;await W(Math.max(this.pollMs,1e3))}const a=e.sims.filter(o=>!n.has(o));if(a.length){this.store.event({kind:"no-id",message:`No Buoy ID yet on ${a.map(o=>this.state.sims[o].name).join(", ")}. Is the app loading? Check Metro.`,leaseId:e.id})}}liveLease(e,t,s){const i=this.state.leases[e];if(!i){const n=this.runOfLease(e);if(n&&(n.endedBy==="user"||n.endedBy==="deadline"))this.assertRunOpen(n);throw new d("LEASE_EXPIRED",`Lease ${e} has ended.`,"Acquire new sims.")}if(i.epoch!==t)throw new d("STALE_EPOCH",`Lease ${e} is at epoch ${i.epoch}, not ${t}.`);if(s&&i.owner.clientId!==s)throw new d("NOT_OWNED",`Lease ${e} belongs to ${i.owner.label}.`);if(this.leaseOverdue(i)){const n=this.overdueEnd(i);this.background(`expire ${i.id}`,()=>this.endLease(i.id,"expired","full",n));if(n==="deadline")throw new d("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 d("LEASE_EXPIRED",`Lease ${e} has expired.`,"Acquire new sims.")}return i}renew(e,t,s){const i=this.liveLease(e,t,s);const n=Math.min(this.clock.now()+Math.min(i.ttlMs,Fe),this.hardDeadlines.get(i.id)??Infinity);this.deadlines.set(i.id,n);return{expiresInMs:Math.round(n-this.clock.now())}}async release(e,t,s="fast",i,n=false){if(s!=="fast"&&s!=="full")throw new d("BAD_REQUEST","Use fast or full reset.");this.liveLease(e,t,i);return this.endLease(e,"released",s,n?null:"released")}async endLease(e,t,s,i="released"){const n=this.state.leases[e];if(!n)return{};n.epoch++;delete this.state.leases[e];this.deadlines.delete(e);this.hardDeadlines.delete(e);for(const[r,l]of Object.entries(this.state.locks))if(l.leaseId===e)delete this.state.locks[r];const a=new Set(n.sims.filter(r=>{const l=this.state.sims[r];return l?.bootEpoch===0&&l.power==="cold"&&l.health==="ok"}));const o={};for(const r of n.sims){const l=this.state.sims[r];if(!l||l.leaseId!==e)continue;l.leaseId=void 0;l.health="dirty";l.idleSince=new Date().toISOString();this.busy.add(r);o[l.name]="resetting";void this.files(()=>ke(r,this.store.homeDir)).catch(()=>void 0)}this.store.event({kind:"lease-end",message:`${n.owner.label}'s ${n.purpose} ended (${t}).`,leaseId:e,runId:n.runId});this.settleRun(n,i);this.store.save();this.background(`reset after ${e}`,()=>this.work(void 0,async()=>{for(const r of n.sims){if(!this.state.sims[r])continue;try{const l=Date.parse(n.createdAt)-2e3;for(const c of await this.host.clientsOf(r)){if(Date.parse(c.start)>=l)await this.host.kill(c.pid,c.start)}if(s==="full"&&!a.has(r)||this.state.sims[r].failures>0)await this.rebuildSim(r);else await this.fastReset(r)}catch(l){this.store.event({kind:"reset-failed",message:`${this.state.sims[r]?.name??r}: ${l.message}. Rebuilding.`,udid:r});await this.rebuildSim(r).catch(()=>{})}finally{this.busy.delete(r)}}}));return o}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,n,a){const o=this.liveLease(e,t,a);const r={};for(const l of s){if(!o.sims.includes(l))throw new d("NOT_OWNED",`${l} is not in lease ${e}.`);await this.assertOwned(l);this.liveLease(e,t,a);const c=this.template(o.templateId);switch(i){case"screenshot":{const p=this.state.locks["perf-slot"];if(p&&p.leaseId!==o.id)throw new d("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);const h=D.join(this.store.shotsPath,`${l}.png`);r[l]=await this.host.screenshot(l,h)?h:null;break}case"open-url":if(!n)throw new d("BAD_REQUEST","open-url needs a url.");await this.host.openUrl(l,n);r[l]="opened";break;case"launch":await this.launchApp(l,c);r[l]="launched";break;case"reset":await this.fastReset(l,o.id);r[l]="reset";break;case"rebuild":{const p=await this.rebuildSim(l,o.id);if(!this.state.leases[o.id])throw new d("LEASE_EXPIRED",`Lease ${e} ended during the rebuild.`);p.leaseId=o.id;o.sims=o.sims.map(w=>w===l?p.udid:w);const h=o.runId?this.state.runs[o.runId]:void 0;const g=h?.lanes[l];if(h&&g){delete h.lanes[l];h.lanes[p.udid]={...g,udid:p.udid,name:p.name,state:"setting-up",note:"Rebuilt"}}await this.bootSim(p.udid,o.id);r[l]=p.udid;break}default:throw new d("BAD_REQUEST",`Unknown action ${i}.`)}}this.store.save();return r}async lock(e,t,s,i=0,n){if(typeof e!=="string"||!/^[a-z0-9-]{1,40}$/.test(e))throw new d("BAD_REQUEST","Lock names are short lowercase words.");this.touch(this.liveLease(t,s,n));if(this.state.locks[e]?.leaseId===t)return{held:true};const a=this.lockQueues.get(e)??[];this.lockQueues.set(e,a);const o={};a.push(o);const r=performance.now()+i;try{for(;;){this.store.assertActive();this.liveLease(t,s,n);const l=this.state.locks[e];const c=e==="perf-slot"&&(this.activeWork>0||this.busy.size>0||this.pendingBg>0);if(a[0]===o&&!l&&!c){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:l?this.state.leases[l.leaseId]?.owner.label??"someone":"the farm (work is still running)",since:l?.since}}await W(Math.max(1,Math.min(this.pollMs,r-performance.now())))}}finally{a.splice(a.indexOf(o),1);if(!a.length)this.lockQueues.delete(e)}}fairness(e,t,s,i){this.liveLease(e,t,i);const n=this.state.settings;const a=Cs(n,this.meter.cpuCount);const o=this.meter.verdicts(e,s,a);return{rules:a,fair:o.filter(r=>r.fair).length,unfair:o.filter(r=>!r.fair).length,runs:o}}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 d("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(this.shotsInFlight>=Ds)throw new d("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){await this.assertOwned(e);const t=this.sim(e);if(this.state.locks["perf-slot"])throw new d("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(t.power!=="awake")throw new d("BAD_REQUEST",`${t.name} is ${t.power}.`);const s=D.join(this.store.shotsPath,`${e}.png`);await this.files(()=>A.mkdir(D.dirname(s),{recursive:true}));if(!await this.host.screenshot(e,s,8e3))throw new d("HOST_ERROR",`No still from ${t.name} in time.`,void 0,true);return{file:s,at:new Date().toISOString()}}async show(e){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 d("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=!await q(s.owner.pid,s.owner.pidStart);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.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}`))}}}const t=Object.keys(this.state.requests);if(t.length>Ot)for(const s of t.slice(0,t.length-Ot))delete this.state.requests[s]}finally{this.ticking=false}}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 W(200);return this.busy.size===0&&this.pendingBg===0&&this.activeWork===0}idle(){return Object.keys(this.state.leases).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 n=`${e}:${t}`;const a=ns("sha256").update(JSON.stringify(s)).digest("hex").slice(0,16);const o=this.state.requests[n];if(o){if(o.hash!==a)throw new d("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return o.result}const r=this.inflight.get(n);if(r){if(r.hash!==a)throw new d("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return r.promise}const l=i();this.inflight.set(n,{hash:a,promise:l});try{const c=await l;this.state.requests[n]={hash:a,result:c,at:new Date().toISOString()};this.store.save();return c}finally{this.inflight.delete(n)}}newRun(e,t,s){const i=Date.now();const n=e.owner.agent?this.state.settings.runCapMinutes*6e4:24*36e5;const a=(e.goal??"").trim()||e.purpose;const o={id:`r-${fe(4).toString("hex")}`,goal:a.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,n)).toISOString(),capAt:new Date(i+n).toISOString(),leases:[],lanes:{},notes:[],lastActivityAt:new Date(i).toISOString(),auto:!e.goal};this.state.runs[o.id]=o;return o}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 d("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 d("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(H.includes(e.state)||e.state==="stopping")throw new d("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 d("NOT_FOUND",`No run ${e}.`,"Use the runId that sim_farm_acquire returned.");if(t&&s.owner.clientId!==t)throw new d("NOT_OWNED",`Run ${e} belongs to ${s.owner.label}.`);this.assertRunOpen(s);return s}async forgetPhone(e){await A.rm(oe(e,this.store.homeDir),{force:true});await A.rm(Se(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||H.includes(s.state))continue;const i=await be(e.udid,this.store.homeDir);if(!i?.overLimit||!i.pid||e.phone.notedPid===i.pid)continue;e.phone.notedPid=i.pid;const n=s.lanes[e.udid]?.label??e.name;const a=e.phone.phoneName??"the phone";this.note(s,"farm",i.ended==="safety"?`${n}: the app used ${i.peakMB} MB and hit the farm's safety cap, so it was closed. ${a} would have closed it sooner.`:i.ended==="limit"?`${n}: the app went past ${a}'s memory limit (${i.peakMB} MB) and was closed, like on a real phone.`:`${n}: the app went past ${a}'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>At)e.notes.splice(0,e.notes.length-At)}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 n of e.sims)if(s.lanes[n])s.lanes[n].gone=true;if(H.includes(s.state)||s.state==="stopping"||t===null)return;const i=s.leases.some(n=>this.state.leases[n]);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,n="full"){if(H.includes(e.state))return;e.state="stopping";e.endedBy=s;if(i)this.note(e,s==="user"?"person":"farm",i);for(const a of e.leases){if(this.state.leases[a])void this.endLease(a,i??s,s==="finished"||s==="released"?n:"full",null)}for(const a of Object.values(e.lanes))a.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(n=>n.state==="passed").length;const i=t.filter(n=>n.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 d("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&&!Dt.includes(e.state))throw new d("BAD_REQUEST",`state must be one of ${Dt.join(", ")}.`);if(e.udid!==void 0){const n=s.lanes[e.udid];if(!n)throw new d("NOT_FOUND",`That sim is not in run ${s.id}.`,"Use a UDID from this run's sims.");if(e.state)n.state=e.state;if(e.state==="passed"||e.state==="failed")n.resultSource=e.resultSource==="test"?"test":"agent";if(e.note!==void 0)n.note=String(e.note).slice(0,200);n.at=i}if(e.counts!==void 0){const n=e.counts;const a=o=>o===void 0||Number.isInteger(o)&&o>=0;if(!n||typeof n!=="object"||!a(n.done)||!a(n.total)||!a(n.passed)||!a(n.failed)||n.done>n.total){throw new d("BAD_REQUEST","counts needs whole numbers, with done no more than total.")}s.counts={done:n.done,total:n.total,passed:n.passed,failed:n.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 d("BAD_REQUEST","minutes must be above zero.");const n=Date.parse(i.deadlineAt);const a=Date.now()+t*6e4;const o=Math.min(a,Date.parse(i.capAt));if(o<=n){return{granted:false,partly:false,reason:"This run is at its time limit.",...this.timeLeft(i)}}i.deadlineAt=new Date(o).toISOString();i.lastActivityAt=new Date().toISOString();for(const r of i.leases){const l=this.state.leases[r];if(!l)continue;l.ttlMs=o-Date.parse(l.createdAt);this.hardDeadlines.set(r,this.clock.now()+(o-Date.now()))}this.note(i,"farm",`More time: until ${new Date(o).toLocaleTimeString()}.`);this.store.save();return{granted:true,partly:o<a,...this.timeLeft(i)}}finish(e,t,s,i){const n=this.state.runs[e];if(n?.endedBy==="finished"&&n.owner.clientId===i)return{state:n.state,outcome:n.outcome};const a=this.ownRun(e,i);if(t&&!["passed","failed","mixed","unknown"].includes(t))throw new d("BAD_REQUEST","outcome must be passed, failed, mixed or unknown.");a.outcome=t??this.outcomeOf(a);if(s)a.summary=s.slice(0,600);this.endRun(a,a.outcome==="failed"?"failed":"done","finished",void 0,"fast");this.store.save();return{state:a.state,outcome:a.outcome}}stopRun(e,t="you"){const s=this.state.runs[e];if(!s)throw new d("NOT_FOUND",`No run ${e}.`);if(H.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=>!H.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 n of Object.values(this.state.runs)){if(H.includes(n.state))continue;if(e>=Date.parse(n.deadlineAt)){this.endRun(n,"stopped","deadline","Ran out of time. The sims were taken back.");this.store.save()}else if(!n.leases.some(a=>this.state.leases[a])&&!await q(n.owner.pid,n.owner.pidStart)){this.endRun(n,"interrupted","owner-exited","The chat closed.");this.store.save()}}const t=this.state.settings.keepRunsHours*36e5;const s=Object.values(this.state.runs).filter(n=>H.includes(n.state)).sort((n,a)=>Date.parse(a.endedAt??a.createdAt)-Date.parse(n.endedAt??n.createdAt));let i=false;s.forEach((n,a)=>{if(a>=200||e-Date.parse(n.endedAt??n.createdAt)>t){delete this.state.runs[n.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 n=>{const a=n.leaseId?this.state.leases[n.leaseId]:void 0;const o=a?.runId?this.state.runs[a.runId]:void 0;const r=n.phone?.status==="on"?await be(n.udid,this.store.homeDir):null;const l=n.phone&&{...n.phone,notedPid:void 0,...r?.loaded?{pid:r.pid,peakMB:r.peakMB,memoryWarnings:r.memoryWarnings,overLimit:r.overLimit,ended:r.ended}:{}};return{...n,phone:l,owner:a?.owner.label,purpose:o?.goal??a?.purpose,runId:o?.id,lane:o?.lanes[n.udid]?.label,busy:this.busy.has(n.udid)}}));const i={farmId:this.state.farmId,counts:{pool:s.length,awake:s.filter(n=>n.power==="awake").length,leased:s.filter(n=>n.leaseId).length},budget:{roomForMoreSims:t.roomFor,freeDiskGB:Math.floor(t.stats.freeDiskBytes/ee),availableMemGB:Math.floor(t.stats.availableMemBytes/ee),memPressure:t.stats.memPressure,loadAvg1:Number(t.stats.loadAvg1.toFixed(1)),cpuCount:t.stats.cpuCount},templates:Object.values(this.state.templates),sims:s,orphans:Object.values(this.state.orphans),leases:Object.values(this.state.leases).map(n=>({...n,expiresInMs:Math.max(0,Math.round(this.leaseDeadline(n)-this.clock.now()))})),runs:Object.values(this.state.runs).sort((n,a)=>Date.parse(a.createdAt)-Date.parse(n.createdAt)).map(n=>({...n,notes:n.notes.slice(-50),secondsLeft:H.includes(n.state)?0:Math.max(0,Math.round((Date.parse(n.deadlineAt)-Date.now())/1e3))})),locks:Object.values(this.state.locks),recentEvents:this.state.events.slice(-25),settings:this.state.settings};if(e.outside){const n=new Set([...Object.keys(this.state.sims),...Object.keys(this.state.orphans),...Object.values(this.state.templates).map(a=>a.udid)]);i.outsideSims=(await this.host.listDevices()).filter(a=>!n.has(a.udid)&&a.available).map(a=>({udid:a.udid,name:a.name,state:a.state,runtime:a.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],phoneCapMB:[0,1048576]};if(!e||typeof e!=="object")throw new d("BAD_REQUEST","settings must be an object.");if("phoneProfile"in e){const n=Z(this.store.homeDir);if(e.phoneProfile!==Le&&!(typeof e.phoneProfile==="string"&&n[e.phoneProfile])){throw new d("BAD_REQUEST",`phoneProfile must be "off" or one of: ${Object.keys(n).join(", ")}.`)}}if("phoneOnLimit"in e&&e.phoneOnLimit!=="warn"&&e.phoneOnLimit!=="kill"){throw new d("BAD_REQUEST",`phoneOnLimit must be "warn" or "kill".`)}if("phoneCapMB"in e&&(!Number.isInteger(e.phoneCapMB)||e.phoneCapMB!==0&&e.phoneCapMB<3072)){throw new d("BAD_REQUEST","phoneCapMB must be 0 (automatic) or a whole number of MB, at least 3072.")}for(const[n,a]of Object.entries(e)){if(!(n in t)||typeof a!==typeof t[n])throw new d("BAD_REQUEST",`Bad setting ${n}.`);const o=s[n];if(o&&(typeof a!=="number"||!Number.isFinite(a)||a<o[0]||a>o[1])){throw new d("BAD_REQUEST",`${n} must be ${o[0]} to ${o[1]}.`)}}const i=["phoneProfile","phoneOnLimit","phoneCapMB"].some(n=>n in e&&e[n]!==t[n]);for(const[n,a]of Object.entries(e))t[n]=a;this.store.save();if(i){for(const n of Object.keys(this.state.sims))void ke(n,this.store.homeDir).catch(()=>void 0)}return t}};function $s(e,t){return Math.min(t,Math.max(4,Math.round(e*.375)))}function Ls(e){return Math.min(4,Math.max(1.5,e*.1))}function Cs(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 Rt(e,t=Date.now()-3*6e4){const s=new Map;for(const i of e.split("\n")){const n=/^(\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(!n||n[3]==="SIGKILL"||n[3]==="SIGTERM")continue;const a=Date.parse(n[1].replace(" ","T"));if(!(a>=t))continue;const o=s.get(n[2])??[];o.push(a);s.set(n[2],o)}for(const[i,n]of s){n.sort((a,o)=>a-o);for(let a=2;a<n.length;a++)if(n[a]-n[a-2]<=6e4)return i.replace(/^com\.apple\./,"")}return null}import{promises as xs}from"fs";import te from"os";import We from"path";var Nt=6e4;var Ge=class{constructor(){this.disabledRoot=X;this.devicesDir=We.join(te.homedir(),"Library/Developer/CoreSimulator/Devices");this.graphicsPids=[];this.loadCalls=0}simctl(e,t=Nt){return P("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 n of i){t.push({udid:n.udid,name:n.name,state:n.state,runtime:s,deviceType:n.deviceTypeIdentifier,dataPath:n.dataPath??We.join(this.devicesDir,n.udid,"data"),available:n.isAvailable!==false})}}return t}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 M("xcrun",["simctl","boot",e],{timeoutMs:Nt});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 M("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 P("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 M("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 M("xcrun",["simctl","io",e,"screenshot",t],{timeoutMs:s});return i.code===0&&!i.timedOut}spawn(e,t,s=2e4){return M("xcrun",["simctl","spawn",e,...t],{timeoutMs:s})}async appContainer(e,t){const s=await M("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 et();const s=t.find(i=>/launchd_sim/.test(i.command)&&i.command.includes(e));return s?tt(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=We.join(this.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`,"launchd.log");try{const i=await xs.open(s,"r");try{const{size:n}=await i.stat();const a=Math.max(0,n-t);const o=Buffer.alloc(n-a);await i.read(o,0,o.length,a);return o.toString("utf8")}finally{await i.close()}}catch{return""}}async hostStats(){const[e,t,s,i]=await Promise.all([P("vm_stat",[],{timeoutMs:1e4}),M("sysctl",["-n","kern.memorystatus_vm_pressure_level"],{timeoutMs:5e3}),M("sysctl",["-n","vm.swapusage"],{timeoutMs:5e3}),P("df",["-k",te.homedir()],{timeoutMs:1e4})]);const n=Number(/page size of (\d+)/.exec(e)?.[1]??16384);const a=h=>Number(new RegExp(`${h}:\\s+(\\d+)`).exec(e)?.[1]??0);const o=a("Pages free")+a("Pages inactive")+a("Pages speculative")+a("Pages purgeable");const r=Number(t.stdout.trim());const l=/used = ([\d.]+)M/.exec(s.stdout)?.[1];const c=i.trim().split("\n").pop()??"";const p=Number(c.split(/\s+/)[3]??0);return{totalMemBytes:te.totalmem(),availableMemBytes:o*n,memPressure:r>=4?"critical":r>=2?"warn":"normal",swapUsedBytes:l?Number(l)*1024*1024:0,freeDiskBytes:p*1024,cpuCount:te.cpus().length,loadAvg1:te.loadavg()[0]}}async showWindow(e){const t=await M("open",["-b","com.apple.iphonesimulator","--args","-CurrentDeviceUDID",e],{timeoutMs:2e4});if(t.code===0)return;await P("open",["-b","com.apple.dt.Devices"],{timeoutMs:2e4})}async loadCounters(e,t=true){if(t&&this.loadCalls++%10===0){const $=await M("pgrep",["-x","SimMetalHost"],{timeoutMs:5e3});this.graphicsPids=$.stdout.split("\n").map(Number).filter(f=>f>0)}const s=te.cpus();let i=0;let n=0;for(const $ of s){const f=$.times;const B=f.user+f.nice+f.sys+f.idle+f.irq;n+=B;i+=B-f.idle}const a=[...e,...this.graphicsPids];const[o,r,l,c]=await Promise.all([t&&a.length?M("ps",["-o","pid=,time=","-p",a.join(",")],{timeoutMs:5e3}):null,t?M("ioreg",["-r","-c","IOAccelerator","-d","1"],{timeoutMs:5e3}):null,M("sysctl",["-n","kern.memorystatus_vm_pressure_level","vm.swapusage"],{timeoutMs:5e3}),M("notifyutil",["-g","com.apple.system.thermalpressurelevel"],{timeoutMs:5e3})]);const p=new Set(this.graphicsPids);let h=0;let g=0;for(const $ of o?.stdout.split("\n")??[]){const f=/^\s*(\d+)\s+(\S+)/.exec($);if(!f)continue;const B=mt(f[2]);if(p.has(Number(f[1])))g+=B;else h+=B}const w=r&&/"Device Utilization %"=(\d+)/.exec(r.stdout);const k=/thermalpressurelevel\s+(\d+)/.exec(c.stdout);return{hostBusyMs:i,hostTotalMs:n,cpuCount:s.length,pidsCpuMs:t?h:null,graphicsCpuMs:t?g:null,gpuPct:w?Number(w[1]):null,memPressureLevel:Number(l.stdout.trim().split("\n")[0])||1,swapUsedMB:Number(/used = ([\d.]+)M/.exec(l.stdout)?.[1]??0),thermalLevel:k?Number(k[1]):null}}async xcodeBuild(){const e=await M("xcodebuild",["-version"],{timeoutMs:2e4});return e.stdout.trim().replace(/\s+/g," ")||"unknown"}async clientsOf(e){const t=await et();const s=new Set((await this.simTree(e)).map(n=>n.pid));const i=new Set(tt(t,process.pid).map(n=>n.pid));return t.filter(n=>!i.has(n.pid)&&!s.has(n.pid)&&n.command.includes(e)&&!/launchd_sim|CoreSimulator/.test(n.command))}async kill(e,t){if(!await q(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 q(e,t))return}if(await q(e,t)){try{process.kill(e,"SIGKILL")}catch{}}}};import Us from"net";import{chmodSync as js,rmSync as Fs,appendFileSync as $t,mkdirSync as Ws}from"fs";import{timingSafeEqual as Gs}from"crypto";import{promises as Hs,existsSync as He,mkdirSync as qe,readFileSync as K,renameSync as Ye,writeFileSync as Me,rmSync as re,openSync as qs,fsyncSync as Ys,closeSync as zs,chmodSync as Ks,readdirSync as Qs,statSync as Lt}from"fs";import Js from"os";import I from"path";import{createHash as Vs,randomBytes as le}from"crypto";function G(){return process.env.BUOY_FARM_HOME||I.join(Js.homedir(),".buoy","farm")}var U=(e=G())=>({home:e,lock:I.join(e,"owner.lock"),lockOwner:I.join(e,"owner.lock","owner.json"),socket:I.join(e,"farmd.sock"),token:I.join(e,"token"),state:I.join(e,"state.json"),journal:I.join(e,"journal.ndjson"),log:I.join(e,"farmd.log"),shots:I.join(e,"shots")});function ze(e=G()){qe(e,{recursive:true,mode:448});try{Ks(e,448)}catch{}}function Ct(e=G()){const t=U(e).token;try{const s=K(t,"utf8").trim();if(s)return s}catch{}ze(e);try{Me(t,le(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=K(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 ce(e=G()){try{return JSON.parse(await Hs.readFile(U(e).lockOwner,"utf8"))}catch{return null}}async function de(e){return!!e&&await q(e.pid,e.start)}async function Xs(e,t){const s=I.join(e,"owner.claim");for(let i=0;i<20;i++){try{qe(s,{mode:448});Me(I.join(s,"pid"),`${process.pid}
363
+ `;var _s="499c4e3de0f8";var Bs='// 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 ye=bs(Ss);var ft={"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 wt="iphone-se-2nd-gen";var Le="off";function x(){return process.env.BUOY_FARM_HOME||R.join($e.homedir(),".buoy","farm")}function ve(e=x()){return R.join(e,"phone")}function gt(e=x()){return R.join(ve(e),"phones.json")}function yt(e){const t=e;const s=(i,n,a)=>typeof i==="number"&&Number.isInteger(i)&&i>=n&&i<=a;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 Z(e=x()){const t={...ft};try{const s=JSON.parse(ks(gt(e),"utf8"));for(const[i,n]of Object.entries(s?.phones??{})){if(/^[a-z0-9][a-z0-9-]{0,63}$/.test(i)&&yt(n))t[i]=n}}catch{}return t}async function vt(e,t,s=x()){if(!/^[a-z0-9][a-z0-9-]{0,63}$/.test(e))throw new Error(`Bad phone id ${e}.`);if(!yt(t))throw new Error(`Bad phone numbers for ${e}.`);const i=gt(s);let n={phones:{}};try{n=JSON.parse(await E.readFile(i,"utf8"));if(!n||typeof n.phones!=="object")n={phones:{}}}catch{}n.phones[e]=t;await E.mkdir(R.dirname(i),{recursive:true});const a=`${i}.${process.pid}.tmp`;await E.writeFile(a,JSON.stringify(n,null,2)+"\n");await E.rename(a,i)}function St(e){return Math.max(3072,e.limitMB+1024)}function kt(e,t,s){const i=t.capMB>e.limitMB?t.capMB:St(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 Ce(...e){const t=new Set;const s=[];for(const i of e){for(const n of(i??"").split(":")){const a=n.trim();if(a&&!t.has(a)){t.add(a);s.push(a)}}}return s.join(":")}function oe(e,t=x()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return R.join(ve(t),`${e}.launch.json`)}function Se(e,t=x()){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return R.join(ve(t),`${e}.events.jsonl`)}async function bt(e,t=x()){const s=oe(e.udid,t);await E.mkdir(R.dirname(s),{recursive:true});const i=`${s}.${process.pid}.tmp`;await E.writeFile(i,JSON.stringify(e,null,2)+"\n");await E.rename(i,s)}async function ke(e,t=x()){await E.rm(oe(e,t),{force:true})}async function Mt(e,t=x()){try{const s=JSON.parse(await E.readFile(oe(e,t),"utf8"));if(s?.udid!==e||typeof s.dylib!=="string"||!s.vars||typeof s.vars!=="object")return null;await E.access(s.dylib);return s}catch{return null}}async function Es(e){try{const{stdout:t}=await ye("xcrun",["simctl","spawn",e,"launchctl","getenv","DYLD_INSERT_LIBRARIES"],{timeout:1e4});return t.trim()}catch{return""}}async function xe(e,t,s,i=x(),n=Es){const a=await Mt(e,i);if(!a||a.bundleId!==t){return s?{SIMCTL_CHILD_DYLD_INSERT_LIBRARIES:Ce(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await n(e),s)}:{}}const o={};for(const[r,l]of Object.entries(a.vars))o[`SIMCTL_CHILD_${r}`]=l;o.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES=Ce(process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES,await n(e),s,a.dylib);return o}function _t(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 Bt(e){let t=-1;for(let a=e.length-1;a>=0;a--)if(e[a].event==="profile_loaded"){t=a;break}if(t<0)return{loaded:false,ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};const s=e[t];const i=e.slice(t).filter(a=>a.pid===s.pid);const n={pid:s.pid,loaded:true,ignored:Array.isArray(s.ignored)?s.ignored:[],memoryWarnings:0,overLimit:false,peakMB:0};for(const a of i){n.peakMB=Math.max(n.peakMB,a.footprintMB??0,typeof a.peakMB==="number"?a.peakMB:0);n.lastFootprintMB=a.footprintMB;if(a.event==="memory_warning")n.memoryWarnings++;if(a.event==="over_limit")n.overLimit=true;if(a.event==="jetsam_kill"){n.overLimit=true;n.ended="limit"}if(a.event==="safety_kill"){n.overLimit=true;n.ended="safety"}}return n}async function be(e,t=x()){const s=Se(e,t);try{const i=await E.open(s,"r");try{const{size:n}=await i.stat();const a=Math.min(n,256*1024);const o=Buffer.alloc(a);await i.read(o,0,a,n-a);let r=o.toString("utf8");if(a<n)r=r.slice(r.indexOf("\n")+1);return Bt(_t(r))}finally{await i.close()}}catch{return null}}async function Is(){try{const{stdout:e}=await ye("xcodebuild",["-version"],{timeout:3e4});return e.trim().split("\n").pop().replace(/^Build version\s*/,"").replace(/[^\w.-]/g,"")||"unknown"}catch{return"unknown"}}async function Et(e){const t=R.join(e,"BuoyPhoneProfile.dylib");try{await E.access(t);return t}catch{}const s=await E.mkdtemp(R.join($e.tmpdir(),"buoy-phone-profile-"));try{const i=R.join(s,"BuoyPhoneProfile.m");await E.writeFile(i,Ms);const n=R.join(s,"BuoyPhoneProfile.dylib");await ye("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",n],{timeout:18e4,maxBuffer:8*1024*1024,env:{...process.env,TMPDIR:s}});await ye("codesign",["-f","-s","-",n],{timeout:6e4});await E.mkdir(e,{recursive:true});const a=`${t}.${process.pid}`;await E.copyFile(n,a);await E.rename(a,t);return t}finally{await E.rm(s,{recursive:true,force:true}).catch(()=>void 0)}}var It=class{constructor(e=R.join($e.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=Is().then(t=>Et(R.join(this.root,`${t}-${_s}`))).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 ee=1024**3;var Ue="Buoy Farm ";var je=".buoy-farm.json";var Os=120;var As=180;var Fe=9e4;var Ot=500;var At=200;var Ds=3;var H=["done","failed","stopped","interrupted"];var Dt=["setting-up","working","waiting","passed","failed","needs-help"];var Ps={now:()=>performance.now()};var Ts=10*6e4;var Pt=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 Rs=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 Ns=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 Tt=class{constructor(e){this.phoneFailed=new Map;this.simLock=new Pt;this.deadlines=new Map;this.reservations=new Map;this.busy=new Rs;this.ticking=false;this.hardDeadlines=new Map;this.acquiring=new Set;this.budgetLock=new Pt;this.labels=new Map;this.inflight=new Map;this.workContext=new is;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:(t,s)=>{const i=Reflect.get(t,s);if(typeof i!=="function")return i;return async(...n)=>{this.store.assertActive();const a=await i.apply(t,n);this.store.assertActive();return a}}});this.store=e.store;this.clock=e.clock??Ps;this.log=e.log??(()=>{});this.pollMs=e.pollMs??1e3;this.readyTimeoutMs=e.readyTimeoutMs??12e4;this.phoneBuilder=e.phoneBuilder??new It;this.phoneLoadTimeoutMs=e.phoneLoadTimeoutMs??1e4;this.bootGate=new Ns(this.state.settings.maxConcurrentBoots);this.meter=new ht(this.host,()=>{const t=this.state.locks["perf-slot"];const s=t&&this.state.leases[t.leaseId];return s?{leaseId:s.id,udids:s.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,n,a){this.touch(this.liveLease(e,t,a));const o=this.state.locks["perf-slot"];if(i==="screenshot"&&o&&o.leaseId!==e)throw new d("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,n,a))}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 W(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[i,n]of Object.entries(this.state.locks)){if(!this.state.leases[n.leaseId])delete this.state.locks[i]}const e=await this.host.listDevices();const t=new Map(e.map(i=>[i.udid,i]));for(const i of Object.values(this.state.sims)){const n=t.get(i.udid);if(!n){this.store.event({kind:"lost",message:`${i.name} was deleted outside the farm.`,udid:i.udid});if(i.leaseId)this.dropFromLease(i.leaseId,i.udid);await this.files(()=>this.forgetPhone(i.udid)).catch(()=>void 0);delete this.state.sims[i.udid];continue}if(n.state==="Booted"){i.power=i.power==="frozen"?"frozen":"awake";if(i.power!=="frozen"){const a=this.state.templates[i.templateId];const o=await this.health(i.udid,a?J(a.profile):[]);i.profile=o.profile;i.lastCheckAt=new Date().toISOString();if(!o.ok||a?.profile==="slim"&&o.profile==="unknown"){i.health="suspect";i.lastError=o.faults.join("; ")||"Slim state is not known."}}}else{i.power=i.power==="parked"?"parked":"cold"}if(["creating","booting","thawing","deleting"].includes(i.power))i.power=n.state==="Booted"?"awake":"cold"}for(const i of Object.values(this.state.templates)){const n=t.get(i.udid);if(!n){i.usable=false;i.note="Its sim was deleted outside the farm."}else if(!Ze(i.appPath)){i.note="Its app build is gone. Clones still work; rebuild the template for a new build."}}for(const i of Object.keys(this.state.orphans)){if(!t.has(i))delete this.state.orphans[i]}for(const i of this.store.pending){if(i.op==="clone"){const n=i.data?.name;const a=e.find(o=>o.name===n&&!this.state.sims[o.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(i.op!=="rebuild")this.store.result(i.seq,i.op,{recovered:true})}const s=new Set;for(const i of[...this.store.pending]){if(i.op!=="rebuild")continue;const{udid:n,name:a,templateId:o}=i.data;if(Object.values(this.state.orphans).some(r=>r.name===a))continue;s.add(n);this.busy.add(n);this.background(`resume rebuild ${a}`,()=>this.work(void 0,async()=>{try{const r=Object.values(this.state.sims).find(l=>l.name===a&&l.udid!==n);if(!r){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(o,a)}this.store.result(i.seq,"rebuild",{recovered:true})}finally{this.busy.delete(n)}}))}for(const i of Object.values(this.state.leases)){this.deadlines.set(i.id,this.clock.now()+Fe);const n=Date.parse(i.createdAt)+i.ttlMs-Date.now();this.hardDeadlines.set(i.id,this.clock.now()+Math.max(n,0));if(n<=0)this.background(`expire ${i.id}`,()=>this.endLease(i.id,"ran out of time while the farm was down","full","deadline"))}for(const i of Object.values(this.state.sims)){if(i.health==="dirty"&&!i.leaseId&&!s.has(i.udid)){this.busy.add(i.udid);this.background(`resume reset ${i.name}`,async()=>{try{await this.rebuildSim(i.udid)}finally{this.busy.delete(i.udid)}})}}this.store.save()}async dataPath(e){const t=(await this.host.listDevices()).find(s=>s.udid===e);if(!t)throw new d("NOT_FOUND",`No sim ${e} on this Mac.`);return t.dataPath}async isOwned(e){const t=this.state.sims[e]??Object.values(this.state.templates).find(s=>s.udid===e);if(!t)return false;try{const s=JSON.parse(await A.readFile(D.join(await this.dataPath(e),je),"utf8"));return s.farmId===this.state.farmId}catch{return false}}async assertOwned(e){if(!await this.isOwned(e)){throw new d("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 A.mkdir(s,{recursive:true});this.checkActive();await A.writeFile(D.join(s,je),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}return t}async budget(e={ramGB:0,diskGB:0}){const t=this.state.settings;const s=await this.host.hostStats();let i=0;let n=0;for(const w of this.reservations.values()){i+=w.ramGB;n+=w.diskGB}const a=s.totalMemBytes/ee;const o=Ls(a)*(s.memPressure==="warn"?2:1);const r=s.availableMemBytes/ee-i;const l=a-$s(a,t.reserveRamGB)-this.farmAwakeGB()-i;const c=s.freeDiskBytes/ee-n-t.reserveDiskGB;const p=Math.max(0,Math.floor(Math.min(r-o,l,c/t.cloneDiskGB*t.slimSimGB)/t.slimSimGB));const h=(w,k)=>({ok:false,code:w,reason:k,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&&c-e.diskGB<0){return h("DISK_LOW",`Need ${t.reserveDiskGB} GB free after this. This Mac has ${Math.floor(s.freeDiskBytes/ee)} GB.`)}if(e.ramGB>0){if(r-e.ramGB<o||l-e.ramGB<0){return h("RAM_LOW",`Not enough free memory for another sim (${Math.max(0,Math.floor(r))} GB free).`)}}const g=this.state.locks["perf-slot"];if(g&&(!this.workContext.getStore()?.active||this.workContext.getStore()?.leaseId!==g.leaseId))return h("HOST_BUSY","A speed test is running. New sims will wait.");return{ok:true,stats:s,roomFor:p}}async withReservation(e,t){const s=fe(4).toString("hex");await this.budgetLock.run("budget",async()=>{const i=await this.budget(e);if(!i.ok)throw new d(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 n=new Set(i.map(r=>r.comm.split("/").pop()));for(const r of["SpringBoard","backboardd"])if(!n.has(r))s.push(`${r} is not running`);const a=Rt(await this.host.launchdLogTail(e));if(a)s.push(`${a} keeps crashing`);if(s.length===0){const r=D.join(this.store.shotsPath,`${e}.png`);await this.files(()=>A.mkdir(D.dirname(r),{recursive:true}));if(!await this.host.screenshot(e,r,1e4))s.push("screenshot did not come back")}let o=i.length<Os?"slim":i.length>As?"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 l=new Set([...r.stdout.matchAll(/"([^"]+)" => (?:disabled|true)/g)].map(p=>p[1]));const c=t.filter(p=>!l.has(p)).length;o=c===0?"slim":c===t.length?"stock":"unknown"}else{o="unknown"}}return{ok:s.length===0,profile:o,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 W(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 d("BAD_REQUEST","Use slim or stock and a port from 1 to 65535.")}const t=e.seedDir==null?null:ae(e.seedDir);if(t)throw new d("NO_SEED",t,"Run: pnpm e2e:pool seed");if(!Ze(D.join(e.appPath,"Info.plist"))){throw new d("BAD_REQUEST",`No app build at ${e.appPath}.`,"Build the app first.")}const s=await ms(e.appPath);const i=`t-${fe(3).toString("hex")}`;const n=e.name??`${Ue}Template ${i.slice(2)}`;return this.withReservation({ramGB:this.state.settings.stockSimGB,diskGB:4},async()=>{const a=this.store.intent("create-template",{id:i,name:n});const o=await this.host.create(n,e.deviceType,e.runtime);const r={id:i,udid:o,name:n,bundleId:s,appPath:e.appPath,appHash:await hs(e.appPath),seedDir:e.seedDir??null,seedHash:e.seedDir?await ps(e.seedDir):null,launchUrl:e.launchUrl,deviceType:e.deviceType,runtime:e.runtime,xcodeBuild:await this.host.xcodeBuild(),profile:e.profile,profileHash:Xe(e.profile),metroPort:e.metroPort,createdAt:new Date().toISOString(),usable:false,note:"Being built."};this.state.templates[i]=r;await this.writeMarker(o,{template:i});this.store.result(a,"create-template",{udid:o});try{await this.buildTemplate(r,e.warmLaunch!==false);r.usable=true;r.note=void 0;this.store.event({kind:"template",message:`Template ${n} is ready.`,udid:o})}catch(l){r.note=`Build failed: ${l.message}`;this.store.event({kind:"template-failed",message:r.note,udid:o});await this.host.shutdown(o).catch(()=>{});throw l}finally{this.store.save()}return r})}async buildTemplate(e,t){const s=J(e.profile);if(s.length)await this.files(()=>at(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 n=await this.host.appContainer(e.udid,e.bundleId);if(!n)throw new Error("app installed but has no data container");if(e.seedDir)await this.files(()=>dt(n,e.bundleId,e.seedDir,this.checkActive));else await this.files(()=>ge(n,this.checkActive));await this.setPrefs(e.udid,e);if(t){await this.launchApp(e.udid,e,{phone:false});await W(Math.min(15e3,this.pollMs*15));await this.host.terminate(e.udid,e.bundleId)}await this.host.shutdown(e.udid);await this.files(()=>ge(n,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 n=B=>{const L=this.state.sims[e];if(L)L.phone=B};const a=async B=>{const L=await this.host.spawn(B,["launchctl","getenv","DYLD_INSERT_LIBRARIES"],1e4).catch(()=>null);return L&&L.code===0?L.stdout.trim():""};const o=async B=>{await this.files(()=>ke(e,s));const L=await xe(e,t,void 0,s,a);await this.host.launch(e,t,L);n(B)};if(i.phoneProfile===Le)return o({status:"off"});const r=Z(s);const l=r[i.phoneProfile]?i.phoneProfile:wt;const c=r[l];const p=l!==i.phoneProfile?`No phone "${i.phoneProfile}" on this Mac, so it uses ${c.name}. `:"";const h=this.phoneFailed.get(t);if(h!==void 0&&performance.now()-h<Ts){return o({status:"fallback",phoneId:l,phoneName:c.name,note:`${p}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 g=await this.phoneBuilder.dylib();if(!g){const B=this.phoneBuilder.lastError()||"unknown error";this.log(`phone profile: could not build the library: ${B}`);return o({status:"fallback",phoneId:l,phoneName:c.name,note:`${p}The phone profile could not be built (${B}), so the app runs as a plain simulator app.`})}const w=Se(e,s);await this.files(async()=>{await A.mkdir(D.dirname(w),{recursive:true});await A.writeFile(w,"");await bt({udid:e,bundleId:t,dylib:g,phoneId:l,vars:kt(c,{phone:l,onLimit:i.phoneOnLimit,capMB:i.phoneCapMB},w),writtenAt:new Date().toISOString()},s)});const k=await xe(e,t,void 0,s,a);await this.host.launch(e,t,k);const $=performance.now()+this.phoneLoadTimeoutMs;let f=null;for(;;){f=await be(e,s);if(f?.loaded||performance.now()>=$)break;await W(Math.min(250,Math.max(10,this.pollMs/4)))}if(f?.loaded){this.phoneFailed.delete(t);const B=f.ignored.length?` Ignored bad settings: ${f.ignored.join(", ")}.`:"";n({status:"on",phoneId:l,phoneName:c.name,pid:f.pid,note:(p+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 o({status:"fallback",phoneId:l,phoneName:c.name,note:`${p}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(`${Ue}${String(s).padStart(2,"0")}`))s++;this.state.nextIndex=s+1;return`${Ue}${String(s).padStart(2,"0")}`}template(e){const t=this.state.templates[e];if(!t)throw new d("NO_TEMPLATE",`No template ${e}.`,"Run sim_farm_setup to make one.");if(!t.usable)throw new d("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 n=this.template(e);return this.withReservation({ramGB:0,diskGB:this.state.settings.cloneDiskGB},()=>this.simLock.run(`template:${n.id}`,async()=>{await this.assertOwned(n.udid);const a=this.store.intent("clone",{templateId:e,name:i});const o=await this.host.clone(n.udid,i);const r={udid:o,name:i,templateId:e,power:"creating",profile:"unknown",health:"ok",bootEpoch:0,failures:0,createdAt:new Date().toISOString()};this.state.sims[o]=r;this.store.result(a,"clone",{udid:o});await this.writeMarker(o,{template:n.id});const l=await we(await this.dataPath(o),n.bundleId);if(l)await this.files(()=>ge(l,this.checkActive));const c=s?this.state.leases[s.leaseId]:void 0;if(c&&!this.leaseOverdue(c)){r.leaseId=c.id;c.sims=c.sims.map(p=>p===s.udid?o:p)}r.power="cold";this.store.event({kind:"clone",message:`Made ${t}.`,udid:o});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 d("BAD_REQUEST",`${s.name} is frozen. Thaw it first.`)}const i=this.state.templates[s.templateId];const n=this.state.settings;const a={ramGB:i?.profile==="slim"?n.slimSimGB:n.stockSimGB,diskGB:0};return this.withReservation(a,()=>this.bootGate.use(()=>this.simLock.run(e,async()=>{this.assertLeaseState(s,t);if(s.power==="awake")return s;const o=i?J(i.profile):[];if(o.length&&!await ds(e,o,this.host.disabledRoot)){await this.files(()=>at(e,o,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"?o:[]);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 d("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 d("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 l=await this.budget({ramGB:n.stockSimGB-n.slimSimGB,diskGB:0});if(!l.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 d("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 d("NOT_FOUND",`${e.name} is gone.`);if(t){const s=this.state.leases[t];if(e.leaseId!==t||!s||this.leaseOverdue(s)){throw new d("LEASE_EXPIRED",`${e.name} is no longer in lease ${t}.`)}}else if(e.leaseId){throw new d("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 d("BAD_REQUEST",`${s.name} is being reset.`);if(s.power==="frozen"||s.power==="thawing")throw new d("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 d("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 n=(await this.host.listDevices()).find(r=>r.udid===e);if(!n)throw new d("NOT_FOUND","This sim is gone.");let a=false;try{const r=JSON.parse(await A.readFile(D.join(n.dataPath,je),"utf8"));a=r.farmId===this.state.farmId}catch(r){a=r.code==="ENOENT"&&n.name===s.cloneIntentName}if(!a)throw new d("NOT_OWNED","This sim has no valid farm mark.");const o=this.store.intent("delete",{udid:e});await this.host.shutdown(e);await this.host.delete(e);await this.files(()=>A.rm(D.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(o,"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 d("BAD_REQUEST",`${i.name} is frozen. Wake it first.`);i.power="deleting";const n=this.store.intent("delete",{udid:e});await this.host.shutdown(e).catch(()=>{});await this.host.delete(e);await this.files(()=>A.rm(D.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(n,"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:n}=s;await this.assertOwned(e);await this.simLock.run(e,async()=>{this.assertLeaseState(s,t);this.busy.add(e)});const a=this.store.intent("rebuild",{udid:e,name:i,templateId:n});try{await this.deleteSim(e,t);const o=await this.cloneSimWork(n,i,t?{udid:e,leaseId:t}:void 0);this.store.result(a,"rebuild",{udid:o.udid});return o}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 d("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 n=await we(await this.dataPath(e),i.bundleId);if(!n)throw new Error("app container not found");if(i.seedDir)await this.files(()=>dt(n,i.bundleId,i.seedDir,this.checkActive));else await this.files(()=>ge(n,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 d("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 d("BAD_REQUEST","count must be 1 to 64.");const s=["cold","parked","awake"];const i=Object.values(this.state.sims).filter(a=>a.templateId===e&&!a.leaseId&&s.includes(a.power)&&!this.busy.has(a.udid)).sort((a,o)=>s.indexOf(a.power)-s.indexOf(o.power));const n=[];for(const a of i.slice(0,t)){try{await this.deleteSim(a.udid);n.push(a.name)}catch(o){if(!(o instanceof d))throw o}}return n}async removeTemplate(e){return this.work(void 0,async()=>{const t=this.state.templates[e];if(!t)throw new d("NO_TEMPLATE",`No template ${e}.`);const s=Object.values(this.state.sims).filter(o=>o.templateId===e);const i=s.filter(o=>o.leaseId||this.busy.has(o.udid));if(i.length){throw new d("HOST_BUSY",`${i.map(o=>o.name).join(", ")} ${i.length===1?"is":"are"} in use.`,"End those uses first.",true)}const n=[];for(const o of s){await this.deleteSim(o.udid);n.push(o.name)}const a=(await this.host.listDevices()).some(o=>o.udid===t.udid);if(a){await this.assertOwned(t.udid);await this.simLock.run(t.udid,async()=>{const o=this.store.intent("delete",{udid:t.udid});await this.host.shutdown(t.udid).catch(()=>{});await this.host.delete(t.udid);await this.files(()=>A.rm(D.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${t.udid}`),{recursive:true,force:true}));this.store.result(o,"delete",{})})}delete this.state.templates[e];n.push(t.name);this.store.event({kind:"delete",message:`Removed template ${t.name} and its ${s.length} sim(s).`});return{removed:n}})}leaseOverdue(e){const t=this.clock.now();if(t>(this.hardDeadlines.get(e.id)??Infinity))return true;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 d("BAD_REQUEST","ttlMs must be a finite number above zero.")}const t=this.template(e.templateId);if(!Number.isInteger(e.count)||e.count<1||e.count>64)throw new d("BAD_REQUEST","count must be 1 to 64.");const s=Math.min(Math.max(e.ttlMs??15*6e4,3e4),24*36e5);const i=e.runId?this.ownRun(e.runId,e.owner.clientId):this.newRun(e,t.id,s);const n=Date.parse(i.deadlineAt)-Date.now();if(n<3e4)throw new d("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 a={id:`l-${fe(4).toString("hex")}`,epoch:1,owner:e.owner,purpose:e.purpose,runId:i.id,templateId:t.id,sims:[],createdAt:new Date().toISOString(),ttlMs:Math.min(s,n)};i.leases.push(a.id);i.lastActivityAt=a.createdAt;if(e.labels?.length)this.labels.set(a.id,e.labels);this.state.leases[a.id]=a;this.hardDeadlines.set(a.id,this.clock.now()+a.ttlMs);this.acquiring.add(a.id);const o=()=>this.deadlines.set(a.id,this.clock.now()+Math.min(a.ttlMs,Fe));o();const r=performance.now()+(e.waitMs??0);const l=()=>{if(!this.state.leases[a.id])throw new d("LEASE_EXPIRED","The lease ended while sims were being prepared.");if(this.leaseOverdue(a))throw new d("LEASE_EXPIRED","The lease ran out of time while sims were being prepared.")};try{while(a.sims.length<e.count){l();o();const c=await this.claimOne(a,t);l();if(c)continue;if(performance.now()>=r)break;if(!a.sims.length&&i.state==="active"&&!Object.keys(i.lanes).length)i.state="waiting";await W(Math.max(this.pollMs,2e3))}if(a.sims.length<e.count&&!e.allowPartial){const c=await this.budget({ramGB:this.state.settings.slimSimGB,diskGB:0});throw new d(c.code??"HOST_BUSY",`Got ${a.sims.length} of ${e.count} sims. ${c.reason??"Others are in use."}`,"Try again later or ask for fewer.",true)}if(a.sims.length===0)throw new d("HOST_BUSY","No sim is free right now.","Try again later.",true);if(e.launch)await this.work(a.id,async()=>{l();await Promise.all(a.sims.map(c=>this.launchApp(c,t)));o();await this.checkIdentities(a,6e4);l()});o();this.store.event({kind:"lease",message:`${e.owner.label} took ${a.sims.length} sim(s) for ${i.goal}.`,leaseId:a.id,runId:i.id});this.note(i,"farm",`Got ${a.sims.length} sim${a.sims.length===1?"":"s"}.`);this.store.save();return{lease:a,run:i,sims:a.sims.map(c=>this.state.sims[c])}}catch(c){if(this.state.leases[a.id])this.note(i,"farm",`Could not get sims: ${c.message}`);await this.endLease(a.id,"acquire failed","fast","acquire-failed").catch(()=>{});throw c}finally{this.acquiring.delete(a.id);this.labels.delete(a.id)}}async claimOneWork(e,t){const s=Object.values(this.state.sims).filter(o=>o.templateId===t.id&&!o.leaseId&&o.health==="ok"&&!this.busy.has(o.udid));const i=s.find(o=>o.power==="awake")??s.find(o=>o.power==="parked"||o.power==="cold");const n=async o=>{const r=await this.isOwned(o.udid);return this.simLock.run(o.udid,async()=>{if(o.leaseId||this.busy.has(o.udid)||o.health!=="ok"||!this.state.sims[o.udid])return null;if(!r){o.health="broken";o.lastError="farm marker missing";this.store.save();return null}if(!this.state.leases[e.id]||this.leaseOverdue(e))return null;o.leaseId=e.id;e.sims.push(o.udid);o.idleSince=void 0;this.addLane(e,o);this.store.save();return o})};if(i?.power==="awake")return n(i);let a=null;try{if(i){a=await n(i)}else{const o=await this.budget({ramGB:t.profile==="slim"?this.state.settings.slimSimGB:this.state.settings.stockSimGB,diskGB:this.state.settings.cloneDiskGB});if(!o.ok)return null;a=await n(await this.cloneSim(t.id))}if(!a)return null;await this.bootSim(a.udid,e.id);return a}catch(o){if(a)this.unclaim(e,a.udid);if(o instanceof d&&(o.code==="RAM_LOW"||o.code==="DISK_LOW"||o.code==="HOST_BUSY"||o.code==="LEASE_EXPIRED")){return null}if(a&&this.state.sims[a.udid]?.health!=="ok"){const r=a.udid;this.background(`rebuild ${r}`,()=>this.rebuildSim(r))}throw o}}unclaim(e,t){e.sims=e.sims.filter(n=>n!==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 n=new Map;for(;;){for(const o of e.sims){if(n.has(o))continue;const r=await we(await this.dataPath(o),s.bundleId);const l=r?await ut(r):null;if(!l)continue;const c=[...n.entries()].find(([,h])=>h===l);if(c)throw new d("ID_CLASH",`${this.state.sims[o].name} and ${this.state.sims[c[0]].name} share one Buoy ID.`);const p=Object.values(this.state.sims).find(h=>h.udid!==o&&!e.sims.includes(h.udid)&&h.buoyDeviceId===l);if(p)throw new d("ID_CLASH",`${this.state.sims[o].name} and ${p.name} share one Buoy ID.`);n.set(o,l);this.state.sims[o].buoyDeviceId=l}if(n.size===e.sims.length||performance.now()>=i)break;await W(Math.max(this.pollMs,1e3))}const a=e.sims.filter(o=>!n.has(o));if(a.length){this.store.event({kind:"no-id",message:`No Buoy ID yet on ${a.map(o=>this.state.sims[o].name).join(", ")}. Is the app loading? Check Metro.`,leaseId:e.id})}}liveLease(e,t,s){const i=this.state.leases[e];if(!i){const n=this.runOfLease(e);if(n&&(n.endedBy==="user"||n.endedBy==="deadline"))this.assertRunOpen(n);throw new d("LEASE_EXPIRED",`Lease ${e} has ended.`,"Acquire new sims.")}if(i.epoch!==t)throw new d("STALE_EPOCH",`Lease ${e} is at epoch ${i.epoch}, not ${t}.`);if(s&&i.owner.clientId!==s)throw new d("NOT_OWNED",`Lease ${e} belongs to ${i.owner.label}.`);if(this.leaseOverdue(i)){const n=this.overdueEnd(i);this.background(`expire ${i.id}`,()=>this.endLease(i.id,"expired","full",n));if(n==="deadline")throw new d("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 d("LEASE_EXPIRED",`Lease ${e} has expired.`,"Acquire new sims.")}return i}renew(e,t,s){const i=this.liveLease(e,t,s);const n=Math.min(this.clock.now()+Math.min(i.ttlMs,Fe),this.hardDeadlines.get(i.id)??Infinity);this.deadlines.set(i.id,n);return{expiresInMs:Math.round(n-this.clock.now())}}async release(e,t,s="fast",i,n=false){if(s!=="fast"&&s!=="full")throw new d("BAD_REQUEST","Use fast or full reset.");this.liveLease(e,t,i);return this.endLease(e,"released",s,n?null:"released")}async endLease(e,t,s,i="released"){const n=this.state.leases[e];if(!n)return{};n.epoch++;delete this.state.leases[e];this.deadlines.delete(e);this.hardDeadlines.delete(e);for(const[r,l]of Object.entries(this.state.locks))if(l.leaseId===e)delete this.state.locks[r];const a=new Set(n.sims.filter(r=>{const l=this.state.sims[r];return l?.bootEpoch===0&&l.power==="cold"&&l.health==="ok"}));const o={};for(const r of n.sims){const l=this.state.sims[r];if(!l||l.leaseId!==e)continue;l.leaseId=void 0;l.health="dirty";l.idleSince=new Date().toISOString();this.busy.add(r);o[l.name]="resetting";void this.files(()=>ke(r,this.store.homeDir)).catch(()=>void 0)}this.store.event({kind:"lease-end",message:`${n.owner.label}'s ${n.purpose} ended (${t}).`,leaseId:e,runId:n.runId});this.settleRun(n,i);this.store.save();this.background(`reset after ${e}`,()=>this.work(void 0,async()=>{for(const r of n.sims){if(!this.state.sims[r])continue;try{const l=Date.parse(n.createdAt)-2e3;for(const c of await this.host.clientsOf(r)){if(Date.parse(c.start)>=l)await this.host.kill(c.pid,c.start)}if(s==="full"&&!a.has(r)||this.state.sims[r].failures>0)await this.rebuildSim(r);else await this.fastReset(r)}catch(l){this.store.event({kind:"reset-failed",message:`${this.state.sims[r]?.name??r}: ${l.message}. Rebuilding.`,udid:r});await this.rebuildSim(r).catch(()=>{})}finally{this.busy.delete(r)}}}));return o}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,n,a){const o=this.liveLease(e,t,a);const r={};for(const l of s){if(!o.sims.includes(l))throw new d("NOT_OWNED",`${l} is not in lease ${e}.`);await this.assertOwned(l);this.liveLease(e,t,a);const c=this.template(o.templateId);switch(i){case"screenshot":{const p=this.state.locks["perf-slot"];if(p&&p.leaseId!==o.id)throw new d("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);const h=D.join(this.store.shotsPath,`${l}.png`);r[l]=await this.host.screenshot(l,h)?h:null;break}case"open-url":if(!n)throw new d("BAD_REQUEST","open-url needs a url.");await this.host.openUrl(l,n);r[l]="opened";break;case"launch":await this.launchApp(l,c);r[l]="launched";break;case"reset":await this.fastReset(l,o.id);r[l]="reset";break;case"rebuild":{const p=await this.rebuildSim(l,o.id);if(!this.state.leases[o.id])throw new d("LEASE_EXPIRED",`Lease ${e} ended during the rebuild.`);p.leaseId=o.id;o.sims=o.sims.map(w=>w===l?p.udid:w);const h=o.runId?this.state.runs[o.runId]:void 0;const g=h?.lanes[l];if(h&&g){delete h.lanes[l];h.lanes[p.udid]={...g,udid:p.udid,name:p.name,state:"setting-up",note:"Rebuilt"}}await this.bootSim(p.udid,o.id);r[l]=p.udid;break}default:throw new d("BAD_REQUEST",`Unknown action ${i}.`)}}this.store.save();return r}async lock(e,t,s,i=0,n){if(typeof e!=="string"||!/^[a-z0-9-]{1,40}$/.test(e))throw new d("BAD_REQUEST","Lock names are short lowercase words.");this.touch(this.liveLease(t,s,n));if(this.state.locks[e]?.leaseId===t)return{held:true};const a=this.lockQueues.get(e)??[];this.lockQueues.set(e,a);const o={};a.push(o);const r=performance.now()+i;try{for(;;){this.store.assertActive();this.liveLease(t,s,n);const l=this.state.locks[e];const c=e==="perf-slot"&&(this.activeWork>0||this.busy.size>0||this.pendingBg>0);if(a[0]===o&&!l&&!c){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:l?this.state.leases[l.leaseId]?.owner.label??"someone":"the farm (work is still running)",since:l?.since}}await W(Math.max(1,Math.min(this.pollMs,r-performance.now())))}}finally{a.splice(a.indexOf(o),1);if(!a.length)this.lockQueues.delete(e)}}fairness(e,t,s,i){this.liveLease(e,t,i);const n=this.state.settings;const a=Cs(n,this.meter.cpuCount);const o=this.meter.verdicts(e,s,a);return{rules:a,fair:o.filter(r=>r.fair).length,unfair:o.filter(r=>!r.fair).length,runs:o}}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 d("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(this.shotsInFlight>=Ds)throw new d("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){await this.assertOwned(e);const t=this.sim(e);if(this.state.locks["perf-slot"])throw new d("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(t.power!=="awake")throw new d("BAD_REQUEST",`${t.name} is ${t.power}.`);const s=D.join(this.store.shotsPath,`${e}.png`);await this.files(()=>A.mkdir(D.dirname(s),{recursive:true}));if(!await this.host.screenshot(e,s,8e3))throw new d("HOST_ERROR",`No still from ${t.name} in time.`,void 0,true);return{file:s,at:new Date().toISOString()}}async show(e){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 d("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=!await q(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.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}`))}}}const t=Object.keys(this.state.requests);if(t.length>Ot)for(const s of t.slice(0,t.length-Ot))delete this.state.requests[s]}finally{this.ticking=false}}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 W(200);return this.busy.size===0&&this.pendingBg===0&&this.activeWork===0}idle(){return Object.keys(this.state.leases).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 n=`${e}:${t}`;const a=ns("sha256").update(JSON.stringify(s)).digest("hex").slice(0,16);const o=this.state.requests[n];if(o){if(o.hash!==a)throw new d("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return o.result}const r=this.inflight.get(n);if(r){if(r.hash!==a)throw new d("REQUEST_REUSED",`Request ${t} was already used for a different call.`);return r.promise}const l=i();this.inflight.set(n,{hash:a,promise:l});try{const c=await l;this.state.requests[n]={hash:a,result:c,at:new Date().toISOString()};this.store.save();return c}finally{this.inflight.delete(n)}}newRun(e,t,s){const i=Date.now();const n=e.owner.agent?this.state.settings.runCapMinutes*6e4:24*36e5;const a=(e.goal??"").trim()||e.purpose;const o={id:`r-${fe(4).toString("hex")}`,goal:a.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,n)).toISOString(),capAt:new Date(i+n).toISOString(),leases:[],lanes:{},notes:[],lastActivityAt:new Date(i).toISOString(),auto:!e.goal};this.state.runs[o.id]=o;return o}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 d("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 d("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(H.includes(e.state)||e.state==="stopping")throw new d("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 d("NOT_FOUND",`No run ${e}.`,"Use the runId that sim_farm_acquire returned.");if(t&&s.owner.clientId!==t)throw new d("NOT_OWNED",`Run ${e} belongs to ${s.owner.label}.`);this.assertRunOpen(s);return s}async forgetPhone(e){await A.rm(oe(e,this.store.homeDir),{force:true});await A.rm(Se(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||H.includes(s.state))continue;const i=await be(e.udid,this.store.homeDir);if(!i?.overLimit||!i.pid||e.phone.notedPid===i.pid)continue;e.phone.notedPid=i.pid;const n=s.lanes[e.udid]?.label??e.name;const a=e.phone.phoneName??"the phone";this.note(s,"farm",i.ended==="safety"?`${n}: the app used ${i.peakMB} MB and hit the farm's safety cap, so it was closed. ${a} would have closed it sooner.`:i.ended==="limit"?`${n}: the app went past ${a}'s memory limit (${i.peakMB} MB) and was closed, like on a real phone.`:`${n}: the app went past ${a}'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>At)e.notes.splice(0,e.notes.length-At)}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 n of e.sims)if(s.lanes[n])s.lanes[n].gone=true;if(H.includes(s.state)||s.state==="stopping"||t===null)return;const i=s.leases.some(n=>this.state.leases[n]);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,n="full"){if(H.includes(e.state))return;e.state="stopping";e.endedBy=s;if(i)this.note(e,s==="user"?"person":"farm",i);for(const a of e.leases){if(this.state.leases[a])void this.endLease(a,i??s,s==="finished"||s==="released"?n:"full",null)}for(const a of Object.values(e.lanes))a.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(n=>n.state==="passed").length;const i=t.filter(n=>n.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 d("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&&!Dt.includes(e.state))throw new d("BAD_REQUEST",`state must be one of ${Dt.join(", ")}.`);if(e.udid!==void 0){const n=s.lanes[e.udid];if(!n)throw new d("NOT_FOUND",`That sim is not in run ${s.id}.`,"Use a UDID from this run's sims.");if(e.state)n.state=e.state;if(e.state==="passed"||e.state==="failed")n.resultSource=e.resultSource==="test"?"test":"agent";if(e.note!==void 0)n.note=String(e.note).slice(0,200);n.at=i}if(e.counts!==void 0){const n=e.counts;const a=o=>o===void 0||Number.isInteger(o)&&o>=0;if(!n||typeof n!=="object"||!a(n.done)||!a(n.total)||!a(n.passed)||!a(n.failed)||n.done>n.total){throw new d("BAD_REQUEST","counts needs whole numbers, with done no more than total.")}s.counts={done:n.done,total:n.total,passed:n.passed,failed:n.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 d("BAD_REQUEST","minutes must be above zero.");const n=Date.parse(i.deadlineAt);const a=Date.now()+t*6e4;const o=Math.min(a,Date.parse(i.capAt));if(o<=n){return{granted:false,partly:false,reason:"This run is at its time limit.",...this.timeLeft(i)}}i.deadlineAt=new Date(o).toISOString();i.lastActivityAt=new Date().toISOString();for(const r of i.leases){const l=this.state.leases[r];if(!l)continue;l.ttlMs=o-Date.parse(l.createdAt);this.hardDeadlines.set(r,this.clock.now()+(o-Date.now()))}this.note(i,"farm",`More time: until ${new Date(o).toLocaleTimeString()}.`);this.store.save();return{granted:true,partly:o<a,...this.timeLeft(i)}}finish(e,t,s,i){const n=this.state.runs[e];if(n?.endedBy==="finished"&&n.owner.clientId===i)return{state:n.state,outcome:n.outcome};const a=this.ownRun(e,i);if(t&&!["passed","failed","mixed","unknown"].includes(t))throw new d("BAD_REQUEST","outcome must be passed, failed, mixed or unknown.");a.outcome=t??this.outcomeOf(a);if(s)a.summary=s.slice(0,600);this.endRun(a,a.outcome==="failed"?"failed":"done","finished",void 0,"fast");this.store.save();return{state:a.state,outcome:a.outcome}}stopRun(e,t="you"){const s=this.state.runs[e];if(!s)throw new d("NOT_FOUND",`No run ${e}.`);if(H.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=>!H.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 n of Object.values(this.state.runs)){if(H.includes(n.state))continue;if(e>=Date.parse(n.deadlineAt)){this.endRun(n,"stopped","deadline","Ran out of time. The sims were taken back.");this.store.save()}else if(!n.leases.some(a=>this.state.leases[a])&&!await q(n.owner.pid,n.owner.pidStart,true)){this.endRun(n,"interrupted","owner-exited","The chat closed.");this.store.save()}}const t=this.state.settings.keepRunsHours*36e5;const s=Object.values(this.state.runs).filter(n=>H.includes(n.state)).sort((n,a)=>Date.parse(a.endedAt??a.createdAt)-Date.parse(n.endedAt??n.createdAt));let i=false;s.forEach((n,a)=>{if(a>=200||e-Date.parse(n.endedAt??n.createdAt)>t){delete this.state.runs[n.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 n=>{const a=n.leaseId?this.state.leases[n.leaseId]:void 0;const o=a?.runId?this.state.runs[a.runId]:void 0;const r=n.phone?.status==="on"?await be(n.udid,this.store.homeDir):null;const l=n.phone&&{...n.phone,notedPid:void 0,...r?.loaded?{pid:r.pid,peakMB:r.peakMB,memoryWarnings:r.memoryWarnings,overLimit:r.overLimit,ended:r.ended}:{}};return{...n,phone:l,owner:a?.owner.label,purpose:o?.goal??a?.purpose,runId:o?.id,lane:o?.lanes[n.udid]?.label,busy:this.busy.has(n.udid)}}));const i={farmId:this.state.farmId,counts:{pool:s.length,awake:s.filter(n=>n.power==="awake").length,leased:s.filter(n=>n.leaseId).length},budget:{roomForMoreSims:t.roomFor,freeDiskGB:Math.floor(t.stats.freeDiskBytes/ee),availableMemGB:Math.floor(t.stats.availableMemBytes/ee),memPressure:t.stats.memPressure,loadAvg1:Number(t.stats.loadAvg1.toFixed(1)),cpuCount:t.stats.cpuCount},templates:Object.values(this.state.templates),sims:s,orphans:Object.values(this.state.orphans),leases:Object.values(this.state.leases).map(n=>({...n,expiresInMs:Math.max(0,Math.round(this.leaseDeadline(n)-this.clock.now()))})),runs:Object.values(this.state.runs).sort((n,a)=>Date.parse(a.createdAt)-Date.parse(n.createdAt)).map(n=>({...n,notes:n.notes.slice(-50),secondsLeft:H.includes(n.state)?0:Math.max(0,Math.round((Date.parse(n.deadlineAt)-Date.now())/1e3))})),locks:Object.values(this.state.locks),recentEvents:this.state.events.slice(-25),settings:this.state.settings};if(e.outside){const n=new Set([...Object.keys(this.state.sims),...Object.keys(this.state.orphans),...Object.values(this.state.templates).map(a=>a.udid)]);i.outsideSims=(await this.host.listDevices()).filter(a=>!n.has(a.udid)&&a.available).map(a=>({udid:a.udid,name:a.name,state:a.state,runtime:a.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],phoneCapMB:[0,1048576]};if(!e||typeof e!=="object")throw new d("BAD_REQUEST","settings must be an object.");if("phoneProfile"in e){const n=Z(this.store.homeDir);if(e.phoneProfile!==Le&&!(typeof e.phoneProfile==="string"&&n[e.phoneProfile])){throw new d("BAD_REQUEST",`phoneProfile must be "off" or one of: ${Object.keys(n).join(", ")}.`)}}if("phoneOnLimit"in e&&e.phoneOnLimit!=="warn"&&e.phoneOnLimit!=="kill"){throw new d("BAD_REQUEST",`phoneOnLimit must be "warn" or "kill".`)}if("phoneCapMB"in e&&(!Number.isInteger(e.phoneCapMB)||e.phoneCapMB!==0&&e.phoneCapMB<3072)){throw new d("BAD_REQUEST","phoneCapMB must be 0 (automatic) or a whole number of MB, at least 3072.")}for(const[n,a]of Object.entries(e)){if(!(n in t)||typeof a!==typeof t[n])throw new d("BAD_REQUEST",`Bad setting ${n}.`);const o=s[n];if(o&&(typeof a!=="number"||!Number.isFinite(a)||a<o[0]||a>o[1])){throw new d("BAD_REQUEST",`${n} must be ${o[0]} to ${o[1]}.`)}}const i=["phoneProfile","phoneOnLimit","phoneCapMB"].some(n=>n in e&&e[n]!==t[n]);for(const[n,a]of Object.entries(e))t[n]=a;this.store.save();if(i){for(const n of Object.keys(this.state.sims))void ke(n,this.store.homeDir).catch(()=>void 0)}return t}};function $s(e,t){return Math.min(t,Math.max(4,Math.round(e*.375)))}function Ls(e){return Math.min(4,Math.max(1.5,e*.1))}function Cs(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 Rt(e,t=Date.now()-3*6e4){const s=new Map;for(const i of e.split("\n")){const n=/^(\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(!n||n[3]==="SIGKILL"||n[3]==="SIGTERM")continue;const a=Date.parse(n[1].replace(" ","T"));if(!(a>=t))continue;const o=s.get(n[2])??[];o.push(a);s.set(n[2],o)}for(const[i,n]of s){n.sort((a,o)=>a-o);for(let a=2;a<n.length;a++)if(n[a]-n[a-2]<=6e4)return i.replace(/^com\.apple\./,"")}return null}import{promises as xs}from"fs";import te from"os";import We from"path";var Nt=6e4;var Ge=class{constructor(){this.disabledRoot=X;this.devicesDir=We.join(te.homedir(),"Library/Developer/CoreSimulator/Devices");this.graphicsPids=[];this.loadCalls=0}simctl(e,t=Nt){return P("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 n of i){t.push({udid:n.udid,name:n.name,state:n.state,runtime:s,deviceType:n.deviceTypeIdentifier,dataPath:n.dataPath??We.join(this.devicesDir,n.udid,"data"),available:n.isAvailable!==false})}}return t}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 M("xcrun",["simctl","boot",e],{timeoutMs:Nt});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 M("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 P("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 M("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 M("xcrun",["simctl","io",e,"screenshot",t],{timeoutMs:s});return i.code===0&&!i.timedOut}spawn(e,t,s=2e4){return M("xcrun",["simctl","spawn",e,...t],{timeoutMs:s})}async appContainer(e,t){const s=await M("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 et();const s=t.find(i=>/launchd_sim/.test(i.command)&&i.command.includes(e));return s?tt(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=We.join(this.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`,"launchd.log");try{const i=await xs.open(s,"r");try{const{size:n}=await i.stat();const a=Math.max(0,n-t);const o=Buffer.alloc(n-a);await i.read(o,0,o.length,a);return o.toString("utf8")}finally{await i.close()}}catch{return""}}async hostStats(){const[e,t,s,i]=await Promise.all([P("vm_stat",[],{timeoutMs:1e4}),M("sysctl",["-n","kern.memorystatus_vm_pressure_level"],{timeoutMs:5e3}),M("sysctl",["-n","vm.swapusage"],{timeoutMs:5e3}),P("df",["-k",te.homedir()],{timeoutMs:1e4})]);const n=Number(/page size of (\d+)/.exec(e)?.[1]??16384);const a=h=>Number(new RegExp(`${h}:\\s+(\\d+)`).exec(e)?.[1]??0);const o=a("Pages free")+a("Pages inactive")+a("Pages speculative")+a("Pages purgeable");const r=Number(t.stdout.trim());const l=/used = ([\d.]+)M/.exec(s.stdout)?.[1];const c=i.trim().split("\n").pop()??"";const p=Number(c.split(/\s+/)[3]??0);return{totalMemBytes:te.totalmem(),availableMemBytes:o*n,memPressure:r>=4?"critical":r>=2?"warn":"normal",swapUsedBytes:l?Number(l)*1024*1024:0,freeDiskBytes:p*1024,cpuCount:te.cpus().length,loadAvg1:te.loadavg()[0]}}async showWindow(e){const t=await M("open",["-b","com.apple.iphonesimulator","--args","-CurrentDeviceUDID",e],{timeoutMs:2e4});if(t.code===0)return;await P("open",["-b","com.apple.dt.Devices"],{timeoutMs:2e4})}async loadCounters(e,t=true){if(t&&this.loadCalls++%10===0){const $=await M("pgrep",["-x","SimMetalHost"],{timeoutMs:5e3});this.graphicsPids=$.stdout.split("\n").map(Number).filter(f=>f>0)}const s=te.cpus();let i=0;let n=0;for(const $ of s){const f=$.times;const B=f.user+f.nice+f.sys+f.idle+f.irq;n+=B;i+=B-f.idle}const a=[...e,...this.graphicsPids];const[o,r,l,c]=await Promise.all([t&&a.length?M("ps",["-o","pid=,time=","-p",a.join(",")],{timeoutMs:5e3}):null,t?M("ioreg",["-r","-c","IOAccelerator","-d","1"],{timeoutMs:5e3}):null,M("sysctl",["-n","kern.memorystatus_vm_pressure_level","vm.swapusage"],{timeoutMs:5e3}),M("notifyutil",["-g","com.apple.system.thermalpressurelevel"],{timeoutMs:5e3})]);const p=new Set(this.graphicsPids);let h=0;let g=0;for(const $ of o?.stdout.split("\n")??[]){const f=/^\s*(\d+)\s+(\S+)/.exec($);if(!f)continue;const B=mt(f[2]);if(p.has(Number(f[1])))g+=B;else h+=B}const w=r&&/"Device Utilization %"=(\d+)/.exec(r.stdout);const k=/thermalpressurelevel\s+(\d+)/.exec(c.stdout);return{hostBusyMs:i,hostTotalMs:n,cpuCount:s.length,pidsCpuMs:t?h:null,graphicsCpuMs:t?g:null,gpuPct:w?Number(w[1]):null,memPressureLevel:Number(l.stdout.trim().split("\n")[0])||1,swapUsedMB:Number(/used = ([\d.]+)M/.exec(l.stdout)?.[1]??0),thermalLevel:k?Number(k[1]):null}}async xcodeBuild(){const e=await M("xcodebuild",["-version"],{timeoutMs:2e4});return e.stdout.trim().replace(/\s+/g," ")||"unknown"}async clientsOf(e){const t=await et();const s=new Set((await this.simTree(e)).map(n=>n.pid));const i=new Set(tt(t,process.pid).map(n=>n.pid));return t.filter(n=>!i.has(n.pid)&&!s.has(n.pid)&&n.command.includes(e)&&!/launchd_sim|CoreSimulator/.test(n.command))}async kill(e,t){if(!await q(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 q(e,t))return}if(await q(e,t)){try{process.kill(e,"SIGKILL")}catch{}}}};import Us from"net";import{chmodSync as js,rmSync as Fs,appendFileSync as $t,mkdirSync as Ws}from"fs";import{timingSafeEqual as Gs}from"crypto";import{promises as Hs,existsSync as He,mkdirSync as qe,readFileSync as K,renameSync as Ye,writeFileSync as Me,rmSync as re,openSync as qs,fsyncSync as Ys,closeSync as zs,chmodSync as Ks,readdirSync as Qs,statSync as Lt}from"fs";import Js from"os";import I from"path";import{createHash as Vs,randomBytes as le}from"crypto";function G(){return process.env.BUOY_FARM_HOME||I.join(Js.homedir(),".buoy","farm")}var U=(e=G())=>({home:e,lock:I.join(e,"owner.lock"),lockOwner:I.join(e,"owner.lock","owner.json"),socket:I.join(e,"farmd.sock"),token:I.join(e,"token"),state:I.join(e,"state.json"),journal:I.join(e,"journal.ndjson"),log:I.join(e,"farmd.log"),shots:I.join(e,"shots")});function ze(e=G()){qe(e,{recursive:true,mode:448});try{Ks(e,448)}catch{}}function Ct(e=G()){const t=U(e).token;try{const s=K(t,"utf8").trim();if(s)return s}catch{}ze(e);try{Me(t,le(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=K(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 ce(e=G()){try{return JSON.parse(await Hs.readFile(U(e).lockOwner,"utf8"))}catch{return null}}async function de(e){return!!e&&await q(e.pid,e.start,true)}async function Xs(e,t){const s=I.join(e,"owner.claim");for(let i=0;i<20;i++){try{qe(s,{mode:448});Me(I.join(s,"pid"),`${process.pid}
364
364
  ${await ne(process.pid)??""}`);try{return await t()}finally{re(s,{recursive:true,force:true})}}catch(n){if(n.code!=="EEXIST")throw n;const a=Lt(s,{throwIfNoEntry:false});if(a&&Date.now()-a.mtimeMs>3e4){let o=true;try{const[r,l]=K(I.join(s,"pid"),"utf8").split("\n");o=!await q(Number(r),l||void 0)}catch{}if(o){try{Ye(s,I.join(e,`owner.claim-dead-${le(4).toString("hex")}`))}catch{}}}await new Promise(o=>setTimeout(o,100))}}return null}async function Zs(e,t,s=G()){ze(s);const i=U(s);const n=await ne(process.pid)??"";for(let a=0;a<3;a++){try{qe(i.lock,{mode:448})}catch(r){if(r.code!=="EEXIST")throw r;const l=await ce(s);if(l&&await de(l))return null;if(!l){await new Promise(h=>setTimeout(h,300));const p=await ce(s);if(p&&await de(p))return null}const c=await Xs(s,async()=>{const p=await ce(s);if(p&&await de(p))return false;if(!He(i.lock))return true;Ye(i.lock,I.join(s,`owner.stale-${le(4).toString("hex")}`));return true});if(c===false)return null;continue}const o={pid:process.pid,start:n,version:e,host:t,since:new Date().toISOString()};Me(i.lockOwner,JSON.stringify(o),{mode:384});ei(s);return{info:o,release(){try{const r=JSON.parse(K(i.lockOwner,"utf8"));if(r.pid!==process.pid)return}catch{return}try{re(i.socket,{force:true})}catch{}re(i.lock,{recursive:true,force:true})}}}return null}function ei(e){try{for(const t of Qs(e)){if(t.startsWith("owner.stale-")||t.startsWith("owner.claim-dead-"))re(I.join(e,t),{recursive:true,force:true})}}catch{}}function xt(){return{version:me,seq:0,farmId:le(6).toString("hex"),nextIndex:1,templates:{},sims:{},orphans:{},leases:{},runs:{},locks:{},requests:{},events:[],settings:{...Pe}}}var Ut=e=>Vs("sha256").update(JSON.stringify(e)).digest("hex").slice(0,12);function jt(e,t,s){const i=qs(e,s,384);try{Me(i,t);Ys(i)}finally{zs(i)}}var Ft=class{constructor(e=G()){this.fenced=false;this.pending=[];this.open=new Map;this.p=U(e);ze(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=xt();if(He(this.p.state)){const n=JSON.parse(K(this.p.state,"utf8"));if(n.version>me){throw new Error(`farm state is version ${n.version}; this farmd understands ${me}. Update Buoy.`)}e={...xt(),...n,settings:{...Pe,...n.settings}}}delete e.settings.activeSlots;const t=new Map;const s=new Set;let i=e.seq;if(He(this.p.journal)){for(const n of K(this.p.journal,"utf8").split("\n")){if(!n.trim())continue;let a;try{a=JSON.parse(n)}catch{continue}const{sum:o,...r}=a;if(Ut(r)!==o||a.seq<=e.seq)continue;i=Math.max(i,a.seq);if(a.type==="intent")t.set(a.seq,a);else{t.delete(a.seq);s.add(a.seq)}}}for(const n of e.openIntents??[]){if(!s.has(n.seq)&&!t.has(n.seq))t.set(n.seq,{...n,type:"intent",sum:""})}this.pending=[...t.values()].sort((n,a)=>n.seq-a.seq);for(const n of this.pending)this.open.set(n.seq,n);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();jt(this.p.journal,JSON.stringify({...e,sum:Ut(e)})+"\n","a")}save(){this.assertActive();const e=`${this.p.state}.${le(4).toString("hex")}.tmp`;this.state.openIntents=[...this.open.values()].map(({seq:t,at:s,op:i,data:n})=>({seq:t,at:s,op:i,data:n}));jt(e,JSON.stringify(this.state,null,1),"w");Ye(e,this.p.state);try{if(Lt(this.p.journal).size>2*1024*1024)re(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 ti(e=G()){try{const t=JSON.parse(K(U(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 Wt=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"]);async function Gt(e={}){const t=e.home??G();const s=U(t);if(Buffer.byteLength(s.socket)>103){const a=new d("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{Ws(t,{recursive:true});$t(s.log,`${new Date().toISOString()} ${a.message} ${a.next}
365
365
  `)}catch{}throw a}const i=await Zs(F,e.hostLabel??"node",t);if(!i)return null;const n=a=>{try{$t(s.log,`${new Date().toISOString()} ${a}
366
366
  `)}catch{}e.log?.(a)};try{return await si(e,t,i,n)}catch(a){i.release();throw a}}async function si(e,t,s,i){const n=U(t);const a=Ct(t);const o=new Ft(t);const r=new Tt({host:e.host??new Ge,store:o,log:i,pollMs:e.pollMs,readyTimeoutMs:e.readyTimeoutMs,phoneBuilder:e.phoneBuilder});await r.recover();Fs(n.socket,{force:true});const l=new Set;let c=Date.now();let p=false;let h=0;const g=Us.createServer(y=>{l.add(y);y.on("close",()=>l.delete(y));y.on("error",()=>{});let u="";y.on("data",m=>{u+=m.toString("utf8");if(u.length>1024*1024)return y.destroy();let v;while((v=u.indexOf("\n"))>=0){const b=u.slice(0,v);u=u.slice(v+1);if(b.trim())void k(y,b)}})});const w=(y,u,m)=>{if(!y.destroyed)y.write(JSON.stringify({id:u,...m})+"\n")};async function k(y,u){c=Date.now();let m;try{m=JSON.parse(u)}catch{return w(y,-1,{ok:false,error:{code:"BAD_REQUEST",message:"Not JSON."}})}h++;try{if(!m||typeof m!=="object")throw new d("BAD_REQUEST","Use a request object.");if(!ii(m.token,a))throw new d("UNAUTHORIZED","Bad farm token.");if(!m.client||typeof m.client.clientId!=="string"||!m.client.clientId.trim())throw new d("BAD_REQUEST","Each call needs a clientId.");if(p&&!["hello","status"].includes(m.method)){throw new d("HOST_BUSY","The farm is stopping.","Try again in a few seconds.",true)}if(m.v>F&&Wt.has(m.method)){throw new d("FARMD_VERSION",`This farm runs protocol ${F}; the client speaks ${m.v}.`,"Ask it to stop when idle (method shutdown), then start a newer farm.")}if(m.v<F&&Wt.has(m.method)){throw new d("READ_ONLY",`This client speaks farm protocol ${m.v}; the farm runs ${F}.`,"Update Buoy to change the farm.")}ni(m);const v=await $(m);w(y,m.id,{ok:true,result:v})}catch(v){const b=v instanceof d?v.toJSON():{code:"HOST_ERROR",message:v.message,retryable:false};w(y,m?.id??-1,{ok:false,error:b})}finally{h--}}async function $(y){const u=y.params??{};const m=y.client;const v=b=>r.once(m.clientId,y.requestId,{m:y.method,q:u},b);switch(y.method){case"hello":return{protocol:F,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 v(()=>r.createTemplate(u));case"grow":return v(async()=>(await r.grow(u.templateId,u.count)).map(b=>b.name));case"shrink":return v(()=>r.shrink(u.templateId,u.count));case"removeTemplate":return v(()=>r.removeTemplate(u.templateId));case"acquire":return v(async()=>{const{lease:b,run:C,sims:De}=await r.acquire({...u,owner:m});return{runId:C.id,leaseId:b.id,epoch:b.epoch,deadlineAt:C.deadlineAt,capAt:C.capAt,sims:De.map(he=>({udid:he.udid,name:he.name,buoyDeviceId:he.buoyDeviceId,profile:he.profile}))}});case"renew":return r.renew(u.leaseId,u.epoch,m.clientId);case"release":return v(()=>r.release(u.leaseId,u.epoch,u.reset,m.clientId,u.keepRun===true));case"action":return v(()=>r.action(u.leaseId,u.epoch,u.udids,u.action,u.arg,m.clientId));case"lock":return r.lock(u.name,u.leaseId,u.epoch,u.waitMs??0,m.clientId);case"unlock":return r.unlock(u.name,u.leaseId,u.epoch,m.clientId);case"fairness":return r.fairness(u.leaseId,u.epoch,u.windows,m.clientId);case"park":return v(async()=>(await r.parkSim(u.udid,"asked"),"parked"));case"wake":return v(async()=>(await r.bootSim(u.udid)).power);case"reset":return v(async()=>(await r.fastReset(u.udid),"reset"));case"rebuild":return v(async()=>(await r.rebuildSim(u.udid)).udid);case"delete":return v(async()=>(await r.deleteSim(u.udid),"deleted"));case"endLease":{const b=r.state.leases[u.leaseId];if(!b)return{};if(b.owner.clientId!==m.clientId&&u.force!==true){throw new d("NOT_OWNED",`Lease ${u.leaseId} belongs to ${b.owner.label}.`,"Pass force: true only after a person confirms.")}return v(()=>r.endLease(u.leaseId,`ended by ${m.label}`,"full",b.owner.clientId===m.clientId?"released":"user"))}case"report":return v(async()=>r.report(u,m.clientId));case"extend":return v(async()=>r.extend(u.runId,u.minutes,m.clientId));case"finish":return v(async()=>r.finish(u.runId,u.outcome,u.summary,m.clientId));case"stopRun":{const b=r.state.runs[u.runId];if(b&&b.owner.clientId!==m.clientId&&u.force!==true){throw new d("NOT_OWNED",`Run ${u.runId} belongs to ${b.owner.label}.`,"Pass force: true only after a person confirms.")}return v(async()=>r.stopRun(u.runId))}case"stopAll":if(u.force!==true)throw new d("BAD_REQUEST","stopAll needs force: true, after a person confirms.");return v(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 Ae("asked to stop");return{stopping:true};default:throw new d("BAD_REQUEST",`Unknown method ${y.method}.`)}}await new Promise((y,u)=>{g.once("error",u);g.listen(n.socket,()=>y())});js(n.socket,384);i(`farmd ${F} up, pid ${process.pid}, host ${e.hostLabel??"node"}`);const f=setInterval(()=>{void r.tick().catch(u=>i(`tick: ${u.message}`));const y=Date.now()-c;if(e.idleExitMs&&l.size===0&&y>e.idleExitMs){void(async()=>{await r.parkAllIdle();if(r.idle()&&l.size===0&&h===0&&Date.now()-c>e.idleExitMs)await Ae("idle")})()}},e.tickMs??5e3);f.unref?.();const B=setInterval(()=>void r.meter.tick().catch(y=>i(`meter: ${y.message}`)),1e3);B.unref?.();let L=null;function Ae(y,u=10*6e4){if(!L)L=es(y,u);return L}async function es(y,u){if(p)return;p=true;clearInterval(f);clearInterval(B);i(`farmd stopping: ${y}`);const m=Date.now()+u;while(h>0&&Date.now()<m)await new Promise(C=>setTimeout(C,200));const v=await r.drain(Math.max(0,m-Date.now()));let b=false;if(h>0||!v||Re()>0){o.save();r.fence();b=true;i(`farmd stopping with ${h} call(s) and ${Re()} child process(es) still running; killing them`);rs();const C=Date.now()+5e3;while(Re()>0&&Date.now()<C)await new Promise(De=>setTimeout(De,100))}await r.settleFiles();if(!b&&h===0){try{await r.parkAllIdle()}catch{}}for(const C of l)C.destroy();await new Promise(C=>g.close(()=>C()));if(!b){o.save();r.fence()}s.release();e.onExit?.(y)}return{engine:r,owner:s,stop:Ae}}function ii(e,t){if(typeof e!=="string"||e.length!==t.length)return false;return Gs(Buffer.from(e),Buffer.from(t))}function ni(e){const t=o=>{throw new d("BAD_REQUEST",o)};const s=e.params??{};if(typeof s!=="object"||Array.isArray(s))t("Params must be an object.");const i=(o,r=false)=>{if(r&&s[o]===void 0)return;if(typeof s[o]!=="string"||!s[o].trim())t(`${o} must be text.`)};const n=o=>{if(s[o]!==void 0&&typeof s[o]!=="boolean")t(`${o} must be true or false.`)};const a=(o,r,l,c=false,p=false)=>{const h=s[o];if(p&&h===void 0)return;if(typeof h!=="number"||!Number.isFinite(h)||h<r||h>l||c&&!Number.isInteger(h))t(`${o} must be a number from ${r} to ${l}.`)};switch(e.method){case"status":n("outside");break;case"budget":a("ramGB",0,Number.MAX_VALUE,false,true);a("diskGB",0,Number.MAX_VALUE,false,true);break;case"setup":for(const o of["appPath","deviceType","runtime"])i(o);if(s.seedDir!=null)i("seedDir");i("name",true);i("launchUrl",true);n("warmLaunch");if(!["slim","stock"].includes(s.profile))t("Use slim or stock.");a("metroPort",1,65535,true);break;case"removeTemplate":i("templateId");break;case"grow":case"shrink":case"acquire":i("templateId");a("count",1,64,true);if(e.method==="acquire"){i("purpose");a("ttlMs",1,Number.MAX_VALUE,false,true);a("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);n("launch");n("allowPartial");i("goal",true);i("runId",true);i("kind",true);if(s.labels!==void 0&&(!Array.isArray(s.labels)||s.labels.some(o=>typeof o!=="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(o=>typeof o!=="string"||!o))t("udids must be a list of sim ids.");if(!["reset","rebuild","open-url","screenshot","launch"].includes(s.action))t("Unknown sim action.");i("arg",s.action!=="open-url");break;case"lock":case"unlock":i("name");if(!/^[a-z0-9-]{1,40}$/.test(s.name))t("Lock names are short lowercase words.");a("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);break;case"fairness":if(!Array.isArray(s.windows)||s.windows.length>500||s.windows.some(o=>!o||typeof o.id!=="string"||!Number.isFinite(o.from)||!Number.isFinite(o.to)||o.to<o.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);a("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");a("minutes",1,24*60);break;case"finish":i("runId");i("summary",true);i("outcome",true);break;case"stopRun":i("runId");n("force");break;case"stopAll":n("force");break;case"shutdown":n("ifIdle");break}if(["renew","release","action","lock","unlock","fairness"].includes(e.method))i("leaseId")}import ai from"net";import{existsSync as oi}from"fs";import{spawn as ri}from"child_process";import{randomBytes as Ht}from"crypto";function qt(e,t=["farmd"]){return()=>{const s=ri(process.execPath,[...process.execArgv,e,...t],{detached:true,stdio:"ignore",env:{...process.env}});s.unref()}}var Yt=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.closed=false;this.home=e.home??G();this.clientIdValue=`c-${Ht(5).toString("hex")}`}get clientId(){return this.owner?.clientId}async connect(){this.token=Ct(this.home);this.owner={clientId:this.clientIdValue,pid:process.pid,pidStart:await ne(process.pid)??void 0,label:this.opts.label,...this.opts.agent?{agent:this.opts.agent}:{}};if(await this.tryOpen())return this.checkVersion();const e=await ce(this.home);if(!await de(e)){if(!this.opts.launcher)throw new d("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 W(250);if(await this.tryOpen())return this.checkVersion()}throw new d("HOST_ERROR","The farm did not start in time.",`Check ${U(this.home).log}.`)}async checkVersion(){const e=await this.send("hello",void 0,{});if(e.protocol<F&&this.opts.launcher){const t=await this.send("shutdown",{ifIdle:true},{});if(t.stopping){this.close();await W(500);return this.connect()}}return this}tryOpen(){const e=U(this.home).socket;if(!oi(e))return Promise.resolve(false);return new Promise(t=>{const s=ai.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 n=this.pending.get(i.id);if(!n)continue;this.pending.delete(i.id);clearTimeout(n.timer);if(i.ok)n.resolve(i.result);else{const a=i.error??{};n.reject(new d(a.code??"HOST_ERROR",a.message??"farm error",a.next,a.retryable))}}}onClose(){this.sock=null;for(const[,e]of this.pending){clearTimeout(e.timer);e.reject(new d("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 d("HOST_ERROR","Not connected to the farm.",void 0,true));const i=this.nextId++;const n={id:i,v:F,token:this.token,client:this.owner,method:e,params:t,requestId:s.requestId};return new Promise((a,o)=>{const r=setTimeout(()=>{this.pending.delete(i);o(new d("HOST_ERROR",`Farm call ${e} timed out.`,void 0,true))},s.timeoutMs??3e4);this.pending.set(i,{resolve:a,reject:o,timer:r});this.sock.write(JSON.stringify(n)+"\n")})}async acquire(e){const{requestId:t,...s}=e;const i=t??`acq-${Ht(4).toString("hex")}`;const n=await this.call("acquire",s,{timeoutMs:15*6e4+(e.waitMs??0),requestId:i});if(this.renewals.has(n.leaseId))return n;const a=setInterval(()=>{this.call("renew",{leaseId:n.leaseId,epoch:n.epoch}).catch(o=>{if(["LEASE_EXPIRED","STALE_EPOCH","RUN_STOPPED","RUN_ENDED"].includes(o.code))this.stopRenew(n.leaseId)})},15e3);a.unref?.();this.renewals.set(n.leaseId,a);return n}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 zt(e){return new Yt(e).connect()}import ue from"path";import li from"os";import{existsSync as Ke,readdirSync as ci,statSync as di}from"fs";function pe(e=process.cwd()){let t=e;for(;;){if(Ke(ue.join(t,"buoy.tools.json")))return t;const s=ue.dirname(t);if(s===t)return null;t=s}}function _e(){const e=ue.join(li.homedir(),"Library/Developer/Xcode/DerivedData");let t=null;try{for(const s of ci(e)){if(!s.startsWith("exampledevbuild-"))continue;const i=ue.join(e,s,"Build/Products/Debug-iphonesimulator/exampledevbuild.app");if(!Ke(i))continue;const n=di(i).mtimeMs;if(!t||n>t.t)t={p:i,t:n}}}catch{}return t?.p??null}function Kt(e=pe()){return e?ue.join(e,"example-dev-build/artifacts/pool-seed"):null}async function ui(e=pe()){const t=Kt(e);let s=[];let i=[];try{const n=JSON.parse(await P("xcrun",["simctl","list","runtimes","--json"],{timeoutMs:3e4}));s=(n.runtimes??[]).filter(o=>o.isAvailable!==false&&/^iOS/.test(o.name??"")).map(o=>({id:o.identifier,name:o.name}));const a=JSON.parse(await P("xcrun",["simctl","list","devicetypes","--json"],{timeoutMs:3e4}));i=(a.devicetypes??[]).filter(o=>/iPhone|iPad/.test(o.name??"")).map(o=>({id:o.identifier,name:o.name}))}catch{}return{appPath:_e(),seedDir:t&&Ke(t)?t:null,seedProblem:t?ae(t):"Could not find the repo's seed folder.",runtimes:s,deviceTypes:i,slimTurnsOff:se.map(n=>({name:n.name,downside:n.downside}))}}import pi from"path";var Qt=[{app:"claude",name:"Claude",match:/(^|\/)claude$/i},{app:"codex",name:"Codex",match:/(^|\/)codex$/i},{app:"cursor",name:"Cursor",match:/cursor/i}];async function hi(e){const t=await M("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 mi(e){const t=await M("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 fi(e){if(!e)return void 0;return Qt.find(t=>e.toLowerCase().includes(t.app))}async function wi(e={}){const t=e.env??process.env;let s=e.pid??process.pid;let i=null;for(let p=0;p<5&&s>1;p++){const h=await hi(s).catch(()=>null);if(!h)break;const g=Qt.find(w=>w.match.test(h.comm));if(p>0&&g){i={app:g,pid:s};break}s=h.ppid}const n=i?.app??fi(e.clientName);const a=(i?await mi(i.pid).catch(()=>null):null)??process.cwd();const o=(await M("git",["-C",a,"rev-parse","--abbrev-ref","HEAD"],{timeoutMs:3e3}).catch(()=>null))?.stdout.trim();const r=t.CLAUDE_CODE_SESSION_ID?.trim();let l;let c="none";if(r&&(n?.app??"claude")==="claude"){l=`claude:${r}`;c="claude-session"}else if(i){l=`${i.app.app}:${i.pid}:${await ne(i.pid)??""}`;c="parent-process"}return{app:n?.app??"other",appName:n?.name??(e.clientName||"Agent"),chatKey:l,chatKeySource:c,project:pi.basename(a)||void 0,projectPath:a,branch:o&&o!=="HEAD"?o:void 0,clientName:e.clientName}}function gi(e){return e.project?`${e.appName} \xB7 ${e.project}`:e.appName}import Jt from"path";import{existsSync as yi}from"fs";import vi from"os";import{execFile as Si,spawn as ki}from"child_process";import{createHash as bi}from"crypto";import{promises as j}from"fs";import Be from"os";import N from"path";import{promisify as Mi}from"util";var Q=Mi(Si);var Ee="gg.buoy.phoneprobe";var Ie=1024*1024;function _i(e){const t=e.match(/BUOY_PROBE physicalMemory=(\d+) cpus=(\d+) footprint=(\d+) available=(\d+)/);if(!t)return null;const[s,i,n,a]=t.slice(1).map(Number);const o=n+a;if(!s||!i||a<=0||o>s)return null;return{ramMB:Math.floor(s/Ie),cpus:i,limitMB:Math.floor(o/Ie)}}function Vt(e,t){return e.match(new RegExp(`<key>${t}</key>\\s*<string>([^<]*)</string>`))?.[1]}function Qe(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(n=>n[1].replace(/\s+/g,""))}function Bi(e,t){const s=t.match(/<key>Entitlements<\/key>\s*<dict>([\s\S]*?)<\/dict>/)?.[1]??"";const i=Vt(s,"application-identifier");const n=Qe(t,"TeamIdentifier","string")[0];const a=t.match(/<key>ExpirationDate<\/key>\s*<date>([^<]+)<\/date>/)?.[1];if(!i||!n||!a)return null;return{file:e,name:Vt(t,"Name")??N.basename(e),team:n,appId:i,expires:new Date(a),devices:Qe(t,"ProvisionedDevices","string"),allDevices:/<key>ProvisionsAllDevices<\/key>\s*<true\/>/.test(t),getTaskAllow:/<key>get-task-allow<\/key>\s*<true\/>/.test(s),certSha1s:Qe(t,"DeveloperCertificates","data").map(o=>bi("sha1").update(Buffer.from(o,"base64")).digest("hex").toUpperCase())}}function Ei(e,t,s,i=new Date){const n=e.filter(o=>o.getTaskAllow&&o.expires>i&&(o.allDevices||o.devices.includes(t))&&(o.appId===`${o.team}.*`||o.appId===`${o.team}.${Ee}`)&&o.certSha1s.some(r=>s.has(r)));n.sort((o,r)=>Number(r.appId.endsWith(".*"))-Number(o.appId.endsWith(".*"))||r.expires.getTime()-o.expires.getTime());const a=n[0];return a?{profile:a,identity:a.certSha1s.find(o=>s.has(o))}:null}async function Ii(){const e=[N.join(Be.homedir(),"Library/Developer/Xcode/UserData/Provisioning Profiles"),N.join(Be.homedir(),"Library/MobileDevice/Provisioning Profiles")];const t=[];for(const s of e){let i=[];try{i=(await j.readdir(s)).filter(n=>n.endsWith(".mobileprovision"))}catch{continue}for(const n of i){const a=N.join(s,n);try{const{stdout:o}=await Q("security",["cms","-D","-i",a],{timeout:15e3,maxBuffer:16*Ie});const r=Bi(a,o);if(r)t.push(r)}catch{}}}return t}async function Oi(){const{stdout:e}=await Q("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 Ai(){const e=await j.mkdtemp(N.join(Be.tmpdir(),"buoy-phones-"));try{const t=N.join(e,"devices.json");await Q("xcrun",["devicectl","list","devices","--json-output",t],{timeout:3e4});const s=JSON.parse(await j.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 j.rm(e,{recursive:true,force:true}).catch(()=>void 0)}}function Di(e,t,s){const i=Object.keys(e).find(a=>e[a].productType===t);if(i)return i;const n=a=>a.toLowerCase().replace(/[()]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");return`${n(s)}-${n(t)}`.slice(0,64).replace(/-$/,"")}async function Pi(e,t,s,i){const n=N.join(e,"PhoneProbe.app");await j.mkdir(n,{recursive:true});const a=N.join(e,"PhoneProbe.m");await j.writeFile(a,Bs);await Q("xcrun",["--sdk","iphoneos","clang","-arch","arm64","-miphoneos-version-min=13.0","-fobjc-arc","-O2","-framework","Foundation","-framework","UIKit",a,"-o",N.join(n,"PhoneProbe")],{timeout:18e4,maxBuffer:8*Ie,env:{...process.env,TMPDIR:e}});const o=Ee;await j.writeFile(N.join(n,"Info.plist"),`<?xml version="1.0" encoding="UTF-8"?>
@@ -0,0 +1,48 @@
1
+ import{createRequire as A}from"module";const G=A(import.meta.url);import{PACKAGE_NAME as l,VERSION as b}from"./chunk-XUN4M5BJ.mjs";import{spawnSync as m}from"child_process";import s from"fs";import E from"os";import c from"path";function g(){return{BUOY_VERIFY:"auto"}}var w="https://registry.npmjs.org";var f=3e3;function C(e={}){const t=e.cwd??process.cwd();const r=e.skillSrcDir??c.resolve(__dirname,"../skill");const n=e.log??(a=>process.stdout.write(a+"\n"));const o=k(e,n);const i=[];d(c.join(t,".mcp.json"),"mcpServers",o,t,i);s.mkdirSync(c.join(t,".cursor"),{recursive:true});d(c.join(t,".cursor","mcp.json"),"mcpServers",o,t,i);if(s.existsSync(c.join(t,".vscode"))){d(c.join(t,".vscode","mcp.json"),"servers",o,t,i)}D(r,t,i);x(c.join(t,"CLAUDE.md"),t,i);x(c.join(t,"AGENTS.md"),t,i);n("Buoy MCP configuration written:");for(const a of i)n(" \u2022 "+a);n("");n("Next steps:");n(" 1. Restart your editor (or reconnect the MCP server) to load `buoy`.");n(" 2. Complete account setup, then open an app with the required Buoy tools and sync integration.");n(" 3. Confirm the device and tools are available. Free accounts get the basic MCP tools; Pro adds the rest.");n("");n(`Want your AI to use the web too? Run \`npx -y ${l}@latest init --web\` for Buoy Web (free, no account).`);if(o.command==="npx"){n("");n(`The editor launches the server with \`npx -y ${l}@latest\`, so it stays`);n(`Registry access may be needed. Re-run \`npx -y ${l}@latest init\` to refresh configuration.`)}else if(!e.cliPath){n("");n(`This uses a pinned local install (no network on startup). To update it later,`);n(`To update the local package, run: npx -y ${o.args[0]?.replace(/dist[/\\]cli\.js$/,"")??""}@latest init --local`)}}function k(e,t){if(e.cliPath){return{type:"stdio",command:"node",args:[e.cliPath],env:g()}}const r=e.mode??"auto";if(r==="npx"){return y()}if(r==="local"){return $(e,t)}const n=I();if(n.reachable){return y()}t(`\u26A0 npm registry ${n.registry?`(${n.registry}) `:""}looks unreachable \u2014 \`npx @latest\` would hang your editor at startup. Falling back to a pinned local install.`);return $(e,t)}function y(){return{type:"stdio",command:"npx",args:["-y",`${l}@latest`],env:g()}}function $(e,t){const r=e.installDir??process.env.BUOY_MCP_HOME??c.join(E.homedir(),".buoy-mcp");const n=e.registry??w;const o=R(r,n,t);if(o){return{type:"stdio",command:"node",args:[o],env:g()}}t("\u26A0 local install failed \u2014 writing the npx entry instead (see the error above).");return y()}function R(e,t,r){const n=`${l}@${b==="0.0.0-dev"?"latest":b}`;s.mkdirSync(e,{recursive:true});const o=process.platform==="win32"?"npm.cmd":"npm";r(`Installing ${n} into ${e} from ${t} \u2026`);const i=m(o,["install",n,"--prefix",e,"--registry",t,`--@buoy-gg:registry=${t}`,"--no-audit","--no-fund","--loglevel=error"],{stdio:["ignore","ignore","inherit"],encoding:"utf8"});if(i.status!==0){r(`\u26A0 npm install exited with code ${i.status??"?"}${i.error?` (${i.error.message})`:""}.`);return null}const a=c.join(e,"node_modules",l,"dist","cli.js");if(!s.existsSync(a)){r(`\u26A0 install finished but ${a} is missing.`);return null}return a}function I(){const e=P();if(!e)return{reachable:false,registry:null};if(/^https?:\/\/registry\.npmjs\.org\/?$/i.test(e)){return{reachable:true,registry:e}}return{reachable:T(e),registry:e}}function P(){const e=S("@buoy-gg:registry");if(e)return e;const t=S("registry");return t??w}function S(e){const t=process.platform==="win32"?"npm.cmd":"npm";const r=m(t,["config","get",e],{encoding:"utf8",timeout:f});if(r.status!==0||typeof r.stdout!=="string")return null;const n=r.stdout.trim();if(!n||n==="undefined"||n==="null")return null;return n}function T(e){const t=m(process.execPath,["-e",F,e,String(f)],{encoding:"utf8",timeout:f+2e3});return t.status===0}var F=`
2
+ const url = process.argv[1];
3
+ const ms = Number(process.argv[2]) || 3000;
4
+ const c = new AbortController();
5
+ const t = setTimeout(() => c.abort(), ms);
6
+ fetch(url, { method: 'HEAD', signal: c.signal })
7
+ .then(() => { clearTimeout(t); process.exit(0); })
8
+ .catch(() => {
9
+ // A HEAD may be rejected while the host is up; a bare TCP-level failure is
10
+ // what we care about. fetch throwing a TypeError = network unreachable.
11
+ clearTimeout(t);
12
+ process.exit(1);
13
+ });
14
+ `;function d(e,t,r,n,o,i="buoy"){let a={};if(s.existsSync(e)){try{a=JSON.parse(s.readFileSync(e,"utf8"))}catch{const v=s.existsSync(e+".bak")?`${e}.${Date.now()}.bak`:`${e}.bak`;s.copyFileSync(e,v);o.push(`existing ${u(e,n)} was invalid JSON \u2014 backed up to ${u(v,n)}`);a={}}}else{s.mkdirSync(c.dirname(e),{recursive:true})}const h=a[t]&&typeof a[t]==="object"?a[t]:{};const j=i in h;h[i]=r;a[t]=h;s.writeFileSync(e,JSON.stringify(a,null,2)+"\n");o.push(`${j?"updated":"added"} \`${i}\` in ${u(e,n)}`)}var p="# Debugging with Buoy";var B=`${p}
15
+
16
+ Use Buoy to inspect a connected app when runtime evidence helps the task.
17
+ Complete account, tool integration and connection setup first. A free app gets
18
+ the basic tools (screen, taps, flows, screenshots, logs, request list); Pro adds
19
+ the rest. Follow the user's scope and repository instructions.
20
+
21
+ - \`get_triage\`: summarize available device and tool observations, then choose
22
+ a relevant tool. Missing data is not proof that no problem occurred.
23
+ - \`run_flow\`: run supported tap, type, wait and assertion steps to reproduce
24
+ a failure or check a change. Review the flow before running it.
25
+ - \`get_events\`: inspect retained events from connected tools.
26
+ - \`get_network_requests\`: inspect captured requests and responses. Capture
27
+ depends on installed hooks, transport and when recording began.
28
+ - \`network_conditions\`: take the app offline or add latency to check error
29
+ and retry paths. Development builds only. Restore \`normal\` when finished.
30
+ - \`get_console\`: inspect retained console and error output. A terminated or
31
+ disconnected app may not provide new data.
32
+ - \`get_storage\`: inspect registered storage backends and available keys.
33
+ - \`reload_app\`: reload where supported. Confirm the device remains connected;
34
+ a reload does not guarantee recovery from a crash.
35
+ - \`get_snapshot\`: inspect the available state of a specific tool.
36
+
37
+ After a behavior change, repeat the failing interaction when the environment
38
+ allows it. Use relevant tests and report what was actually checked. For a
39
+ performance change, compare \`measure_renders\` runs with the same steps and
40
+ \`compareToPrevious: true\`; inspect the measurements and their limitations.
41
+ Use \`run_flow\` for supported UI checks. Do not claim a fix was verified when
42
+ only source review or a build was performed.
43
+
44
+ \`BUOY_VERIFY\` controls verification reminders: \`auto\` uses edit-based
45
+ reminders, \`always\` repeats until checked, and \`never\` disables them.
46
+ A reminder is not a test result. Choose checks appropriate to the change;
47
+ prose-only edits do not require running device actions.
48
+ `;function x(e,t,r){let n="";if(s.existsSync(e)){try{n=s.readFileSync(e,"utf8")}catch{r.push(`\u26A0 could not read ${u(e,t)} \u2014 skipped the debugging guide`);return}if(n.includes(p)){r.push(`${u(e,t)} already has the "${p}" block; preserved existing text. Review it against the current bundled guidance before relying on older instructions.`);return}}const o=n===""?"":n.endsWith("\n")?"\n":"\n\n";try{s.writeFileSync(e,n+o+B);r.push(`${n?"appended":"created"} "${p}" block in ${u(e,t)}`)}catch{r.push(`\u26A0 could not write ${u(e,t)} \u2014 skipped the debugging guide`)}}function D(e,t,r){if(!s.existsSync(e)){r.push("\u26A0 no bundled skills found \u2014 skipped");return}const n=s.readdirSync(e,{withFileTypes:true}).filter(o=>o.isDirectory()&&s.existsSync(c.join(e,o.name,"SKILL.md")));if(n.length===0){r.push("\u26A0 no skills with a SKILL.md found \u2014 skipped");return}for(const o of n){const i=c.join(e,o.name);const a=c.join(t,".claude","skills",o.name);_(i,a);r.push(`installed the ${o.name} skill \u2192 ${u(c.join(a,"SKILL.md"),t)}`)}}function _(e,t){s.mkdirSync(t,{recursive:true});for(const r of s.readdirSync(e,{withFileTypes:true})){const n=c.join(e,r.name);const o=c.join(t,r.name);if(r.isDirectory())_(n,o);else s.copyFileSync(n,o)}}function u(e,t){const r=c.relative(t,e);return r&&!r.startsWith("..")?r:e}export{C as runInit,k as resolveEntry,d as upsertMcpConfig};
@@ -0,0 +1 @@
1
+ import{createRequire as e}from"module";const a=e(import.meta.url);var r="7.0.69"?"7.0.69":"0.0.0-dev";var o="@buoy-gg/mcp";export{r as VERSION,o as PACKAGE_NAME};