@buoy-gg/mcp 7.0.67 → 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.
- package/README.md +28 -1
- package/dist/camera-KL6KTSFE.mjs +1 -0
- package/dist/chunk-5ZQ6OP5I.mjs +4 -0
- package/dist/chunk-DVRR7W22.mjs +146 -0
- package/dist/chunk-FOMLVT7K.mjs +1 -0
- package/dist/chunk-K6AJXL4X.mjs +4 -0
- package/dist/chunk-OP23W722.mjs +3 -0
- package/dist/chunk-OUCB2Y5J.mjs +5 -0
- package/dist/{chunk-LDULNQCZ.mjs → chunk-QXS5X2WL.mjs} +145 -139
- package/dist/chunk-TKDN24C2.mjs +422 -0
- package/dist/chunk-XFE5QEXH.mjs +48 -0
- package/dist/chunk-XUN4M5BJ.mjs +1 -0
- package/dist/cli.js +592 -138
- package/dist/cli.mjs +15 -11
- package/dist/flowRun-HQR7LNLI.mjs +2 -0
- package/dist/freeView-3LHV3RBG.mjs +1 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +193 -151
- package/dist/index.mjs +1 -1
- package/dist/init-VSU462YF.mjs +1 -0
- package/dist/main-6CDJH7KT.mjs +22 -0
- package/dist/policy-D342AXEF.mjs +1 -0
- package/dist/simFarm-XWU2SR4S.mjs +1 -0
- package/dist/src-MZAB6TNP.mjs +1 -0
- package/dist/{webBench-QCHYXYCA.mjs → webBench-BEMJWKB3.mjs} +1 -1
- package/package.json +7 -6
- package/skill/buoy-optimize/MEASURING.md +3 -1
- package/skill/buoy-optimize/SKILL.md +10 -0
- package/dist/camera-F5Q7WMHF.mjs +0 -1
- package/dist/chunk-2MSKDRYW.mjs +0 -2
- package/dist/chunk-7XCEVM3W.mjs +0 -48
- package/dist/chunk-AMU2EOGE.mjs +0 -6
- package/dist/chunk-OPHPYPJD.mjs +0 -146
- package/dist/chunk-VKWX4XAY.mjs +0 -5
- package/dist/flowRun-WBOCHITQ.mjs +0 -2
- package/dist/simFarm-OFT42BGC.mjs +0 -1
- package/dist/src-DPWEFWQY.mjs +0 -1
package/dist/chunk-AMU2EOGE.mjs
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import{createRequire as dt}from"module";const bs=dt(import.meta.url);var $=1;var V=1;var le={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};var c=class extends Error{constructor(e,s,t,i=false){super(s);this.code=e;this.next=t;this.retryable=i;this.name="FarmError"}toJSON(){return{code:this.code,message:this.message,next:this.next,retryable:this.retryable}}};import{createHash as ht}from"crypto";var Ie=["com.apple.sharingd"];var ce=[{"id":"widgets","name":"Widgets & Wallpaper","downside":"Home and Lock Screen widgets, wallpaper posters, and Live Activities stop updating.","approxMemoryMB":675,"labels":["com.apple.PosterBoard","com.apple.chronod","com.apple.liveactivitiesd"]},{"id":"siri","name":"Siri & Intelligence","downside":"Siri, speech features, and Apple Intelligence services are unavailable.","approxMemoryMB":265,"labels":["com.apple.assistantd","com.apple.assistant_cdmd","com.apple.assistant_service","com.apple.siriactionsd","com.apple.siriinferenced","com.apple.siriknowledged","com.apple.sirittsd","com.apple.siri.context.service","com.apple.siri.acousticsignature","com.apple.corespeechd","com.apple.voiced","com.apple.voicebankingd","com.apple.speechmodeltrainingd","com.apple.intelligenceplatformd","com.apple.intelligencecontextd","com.apple.intelligenceflowd","com.apple.intelligencetasksd","com.apple.generativeexperiencesd","com.apple.knowledgeconstructiond","com.apple.naturallanguaged","com.apple.textunderstandingd","com.apple.modelcatalogd","com.apple.modelmanagerd","com.apple.mlhostd","com.apple.mlruntimed","com.apple.suggestd","com.apple.parsecd","com.apple.parsec-fbf","com.apple.proactiveeventtrackerd"]},{"id":"search","name":"Spotlight & Search","downside":"Spotlight and Settings search return no results.","approxMemoryMB":50,"labels":["com.apple.searchd","com.apple.searchtoold","com.apple.spotlightknowledged","com.apple.spotlightknowledged.updater","com.apple.corespotlightservice"]},{"id":"icloud","name":"iCloud & Apple Account","downside":"iCloud sync, Apple Account, Keychain, and backup workflows will not work.","approxMemoryMB":100,"labels":["com.apple.appleaccountd","com.apple.appleaccounttransparencyd","com.apple.appleidsetupd","com.apple.akd","com.apple.amsaccountsd","com.apple.amsengagementd","com.apple.amsondevicestoraged","com.apple.cloudd","com.apple.cloudphotod","com.apple.ckdiscretionaryd","com.apple.cloudsettingssyncagent","com.apple.bird","com.apple.syncdefaultsd","com.apple.cdpd","com.apple.sosd","com.apple.SecureBackupDaemon","com.apple.TrustedPeersHelper","com.apple.protectedcloudstorage.protectedcloudkeysyncing","com.apple.icloudmailagent","com.apple.icloudsubscriptionoptimizerd","com.apple.communicationtrustd"]},{"id":"store","name":"App Store, Push & Media","downside":"Remote push notifications and StoreKit or App Store testing will not work.","approxMemoryMB":80,"labels":["com.apple.appstored","com.apple.appstorecomponentsd","com.apple.apsd","com.apple.itunescloudd","com.apple.itunesstored","com.apple.storekitd","com.apple.amsaccountsd","com.apple.amsengagementd","com.apple.amsondevicestoraged","com.apple.passd","com.apple.financed","com.apple.videosubscriptionsd","com.apple.assetsubscriptiond","com.apple.musicd"]},{"id":"pim","name":"Mail, Calendar & Contacts","downside":"Contacts, Calendar, Reminders, and Mail-backed pickers or sync may fail.","approxMemoryMB":80,"labels":["com.apple.email.maild","com.apple.exchangesyncd","com.apple.dataaccess.dataaccessd","com.apple.calaccessd","com.apple.remindd","com.apple.contactsd","com.apple.contacts.postersyncd","com.apple.peopled"]},{"id":"web","name":"Safari Sync & Web Services","downside":"Universal links and Safari sync or background web services will not work.","approxMemoryMB":50,"labels":["com.apple.SafariBookmarksSyncAgent","com.apple.Safari.History","com.apple.Safari.passwordbreachd","com.apple.Safari.SafeBrowsing.Service","com.apple.safarifetcherd","com.apple.WebBookmarks.webbookmarksd","com.apple.webkit.adattributiond","com.apple.webkit.webpushd","com.apple.webprivacyd","com.apple.swcd"]},{"id":"family","name":"Family & Screen Time","downside":"Family Sharing, Screen Time, and usage tracking stop working.","approxMemoryMB":65,"labels":["com.apple.familycircled","com.apple.FamilyControlsAgent","com.apple.familynotification","com.apple.askpermissiond","com.apple.asktod","com.apple.ScreenTimeAgent","com.apple.ScreenTimeSettingsAgent","com.apple.UsageTrackingAgent"]},{"id":"health","name":"Health, Home & Fitness","downside":"HealthKit, HomeKit, and Fitness integrations will not work.","approxMemoryMB":135,"labels":["com.apple.healthd","com.apple.healthappd","com.apple.healthcontentd","com.apple.healtheventsd","com.apple.healthrecordsd","com.apple.finhealthd","com.apple.homed","com.apple.homeeventsd","com.apple.fitcore","com.apple.fitcore.session","com.apple.fitnesscoachingd","com.apple.fitnessintelligenced","com.apple.activityawardsd","com.apple.activitysharingd"]},{"id":"photos","name":"Photos & Media Analysis","downside":"Photo picker, Photos-library workflows, and media analysis may fail.","approxMemoryMB":60,"labels":["com.apple.photoanalysisd","com.apple.photosface","com.apple.mediaanalysisd","com.apple.mediaanalysisd.service","com.apple.mediastream.mstreamd","com.apple.medialibraryd","com.apple.assetsd","com.apple.assetsd.nebulad"]},{"id":"apps","name":"News, Weather, Maps & Games","downside":"News, Weather, Maps background data, and game-controller services are unavailable.","approxMemoryMB":90,"labels":["com.apple.newsd","com.apple.weatherd","com.apple.Maps.mapssyncd","com.apple.Maps.mapspushd","com.apple.Maps.geocorrectiond","com.apple.maps.destinationd","com.apple.MapKit.SnapshotService","com.apple.jetpackassetd","com.apple.tipsd","com.apple.gamed","com.apple.gamesaved","com.apple.GameController.gamecontrollerd"]},{"id":"messaging","name":"Messaging & FaceTime","downside":"iMessage, FaceTime, and related identity services will not work.","approxMemoryMB":60,"labels":["com.apple.identityservicesd","com.apple.ids_simd","com.apple.imautomatichistorydeletionagent","com.apple.imcore.imtransferagent","com.apple.imdpersistence.IMDPersistenceAgent","com.apple.facetimemessagestored","com.apple.telephonyutilities.callservicesd"]},{"id":"connectivity","name":"Sharing & Device Connectivity","downside":"AirDrop, Continuity, CarPlay, Watch, and Find My connectivity will not work.","approxMemoryMB":65,"labels":["com.apple.rapportd","com.apple.companiond","com.apple.carkitd","com.apple.wcd","com.apple.tvremoted","com.apple.avatarsd","com.apple.stickersd","com.apple.sociallayerd","com.apple.announced","com.apple.navd","com.apple.findmy.findmylocated"]},{"id":"telemetry","name":"Ads, Diagnostics & Telemetry","downside":"DeviceCheck plus analytics, diagnostics, and feedback services are unavailable.","approxMemoryMB":105,"labels":["com.apple.ap.adprivacyd","com.apple.ap.promotedcontentd","com.apple.diagnosticextensionsd","com.apple.feedbackd","com.apple.rtcreportingd","com.apple.securityuploadd","com.apple.geoanalyticsd","com.apple.triald","com.apple.followupd","com.apple.purplebuddy.budd","com.apple.devicecheckd"]},{"id":"other","name":"Other Background Services","downside":"Wallet, merchant, business, asset, and miscellaneous background services are unavailable.","approxMemoryMB":195,"labels":["com.apple.financed","com.apple.passd","com.apple.merchantd","com.apple.coreidvd","com.apple.businessservicesd","com.apple.deviceaccessd","com.apple.replicatord","com.apple.linkd","com.apple.ind","com.apple.storagedatad","com.apple.StatusKitAgent","com.apple.countryd","com.apple.mobileassetd","com.apple.managedconfiguration.passcodenagd"]}];function Ee(){const e=new Set(Ie);const s=new Set;for(const t of ce)for(const i of t.labels)if(!e.has(i))s.add(i);return[...s].sort()}function H(e){return e==="slim"?Ee():[]}function Oe(e){return ht("sha256").update(e+"\n"+H(e).join("\n")).digest("hex").slice(0,16)}import{promises as R,existsSync as De}from"fs";import O from"path";import{AsyncLocalStorage as ut}from"async_hooks";import{createHash as pt,randomBytes as Z}from"crypto";import{promises as G}from"fs";import de from"path";import{randomBytes as mt}from"crypto";import{spawn as ft}from"child_process";var Q=new Set;function he(){return Q.size}function wt(){for(const e of Q){try{e.kill("SIGKILL")}catch{}}}function b(e,s,t={}){const{timeoutMs:i=3e4,maxBytes:a=4*1024*1024}=t;return new Promise(n=>{const o=ft(e,s,{env:t.env??process.env,stdio:[t.input==null?"ignore":"pipe","pipe","pipe"]});Q.add(o);let r="";let l="";let d=false;o.stdout.on("data",u=>{if(r.length<a)r+=u.toString("utf8")});o.stderr.on("data",u=>{if(l.length<a)l+=u.toString("utf8")});if(t.input!=null)o.stdin.end(t.input);const p=setTimeout(()=>{d=true;o.kill("SIGTERM");setTimeout(()=>o.kill("SIGKILL"),2e3).unref?.()},i);o.on("error",u=>{Q.delete(o);clearTimeout(p);n({code:null,stdout:r,stderr:l+String(u),timedOut:d})});o.on("close",u=>{Q.delete(o);clearTimeout(p);n({code:u,stdout:r,stderr:l,timedOut:d})})})}async function D(e,s,t={}){const i=await b(e,s,t);if(i.timedOut)throw new Error(`${e} ${s.slice(0,3).join(" ")} timed out after ${t.timeoutMs??3e4} ms`);if(i.code!==0)throw new Error(`${e} ${s.slice(0,3).join(" ")} failed (${i.code}): ${(i.stderr||i.stdout).trim().slice(0,500)}`);return i.stdout}async function Ae(){const e=await D("ps",["-axww","-o","pid=,ppid=,lstart=,stat=,comm="],{timeoutMs:15e3});const s=await D("ps",["-axww","-o","pid=,command="],{timeoutMs:15e3});const t=new Map;for(const a of s.split("\n")){const n=/^\s*(\d+)\s+(.*)$/.exec(a);if(n)t.set(Number(n[1]),n[2])}const i=[];for(const a of e.split("\n")){const n=/^\s*(\d+)\s+(\d+)\s+(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})\s+(\S+)\s+(.*)$/.exec(a);if(!n)continue;const o=Number(n[1]);i.push({pid:o,ppid:Number(n[2]),start:n[3].replace(/\s+/g," "),stat:n[4],comm:n[5].trim(),command:t.get(o)??n[5].trim()})}return i}function Te(e,s){const t=new Map;const i=new Map;for(const l of e){i.set(l.pid,l);const d=t.get(l.ppid)??[];d.push(l);t.set(l.ppid,d)}const a=[];const n=i.get(s);if(!n)return a;const o=[n];const r=new Set;while(o.length){const l=o.pop();if(r.has(l.pid))continue;r.add(l.pid);a.push(l);for(const d of t.get(l.pid)??[])o.push(d)}return a}async function z(e){const s=await b("ps",["-o","lstart=","-p",String(e)],{timeoutMs:5e3});const t=s.stdout.trim().replace(/\s+/g," ");return s.code===0&&t?t:null}async function P(e,s){if(!Number.isInteger(e)||e<=0)return false;try{process.kill(e,0)}catch(t){if(t.code!=="EPERM")return false}if(!s)return true;return await z(e)===s}var _=e=>new Promise(s=>setTimeout(s,e));var F=process.env.BUOY_FARM_DISABLED_ROOT||"/private/var/tmp";function $e(e,s=F){if(!/^[0-9A-F-]{36}$/i.test(e))throw new Error(`not a UDID: ${e}`);return de.join(s,`com.apple.CoreSimulator.SimDevice.${e}`)}function Be(e,s=F){return de.join($e(e,s),"disabled.plist")}async function xe(e,s=F){const t=Be(e,s);let i;try{i=await G.lstat(t)}catch{return{}}if(!i.isFile())throw new Error(`refusing ${t}: not a regular file`);const a=await D("plutil",["-convert","json","-o","-",t],{timeoutMs:1e4});const n=JSON.parse(a);const o={};for(const[r,l]of Object.entries(n))if(typeof l==="boolean")o[r]=l;return o}function gt(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function yt(e){const s=Object.keys(e).sort();let t='<?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 s)t+=` <key>${gt(i)}</key>
|
|
2
|
-
<${e[i]?"true":"false"}/>
|
|
3
|
-
`;return t+"</dict>\n</plist>\n"}async function Re(e,s,t=F){const i=$e(e,t);await G.mkdir(i,{recursive:true});const a=await G.lstat(i);if(!a.isDirectory())throw new Error(`refusing ${i}: not a directory`);const n=await xe(e,t);const o=de.join(i,"buoy-farm-prior.json");let r={};try{r=JSON.parse(await G.readFile(o,"utf8"))}catch{}const l={...n};for(const d of s){if(!(d in r))r[d]=d in n?n[d]:null;l[d]=true}await _e(o,JSON.stringify(r,null,2)+"\n");await _e(Be(e,t),yt(l))}async function kt(e,s,t=F){const i=await xe(e,t);return s.every(a=>i[a]===true)}async function _e(e,s){const t=`${e}.${mt(4).toString("hex")}.tmp`;const i=await G.open(t,"w",420);try{await i.writeFile(s);await i.sync()}finally{await i.close()}await G.rename(t,e)}import{promises as E,existsSync as C,readFileSync as Rs}from"fs";import v from"path";import{createHash as Ne}from"crypto";var je=["@react_buoy_sync_device_id.json","@react_buoy_install.json"];var vt="@buoy~1floating-tool~1*";function ue(e){if(!C(v.join(e,"seed.json")))return`No seed at ${e}. Run: pnpm e2e:pool seed`;if(C(v.join(e,"Documents/buoy-license"))){for(const s of["buoy-gg-license.json","buoy-gg-device-fingerprint.json"]){if(!C(v.join(e,"Documents/buoy-license",s)))return`The seed lacks ${s}.`}}return null}async function St(e){const s=Ne("sha256");async function t(i){let a;try{a=await E.readdir(i,{withFileTypes:true})}catch{return}a.sort((n,o)=>n.name.localeCompare(o.name));for(const n of a){const o=v.join(i,n.name);if(n.isDirectory())await t(o);else{const r=await E.stat(o);s.update(`${v.relative(e,o)}:${r.size}:${Math.floor(r.mtimeMs)}
|
|
4
|
-
`)}}}await t(e);return s.digest("hex").slice(0,16)}async function bt(e){const s=Ne("sha256");const t=v.join(e,"Info.plist");s.update(await E.readFile(t));const i=JSON.parse(await D("plutil",["-convert","json","-o","-",t],{timeoutMs:1e4}));const a=v.join(e,String(i.CFBundleExecutable??""));const n=await E.stat(a);s.update(`${n.size}:${Math.floor(n.mtimeMs)}`);return s.digest("hex").slice(0,16)}async function Mt(e){const s=JSON.parse(await D("plutil",["-convert","json","-o","-",v.join(e,"Info.plist")],{timeoutMs:1e4}));if(!s.CFBundleIdentifier)throw new Error(`${e} has no CFBundleIdentifier`);return String(s.CFBundleIdentifier)}async function ee(e,s){const t=v.join(e,"Containers/Data/Application");let i;try{i=await E.readdir(t)}catch{return null}for(const a of i){const n=v.join(t,a,".com.apple.mobile_container_manager.metadata.plist");if(!C(n))continue;const o=await b("plutil",["-extract","MCMMetadataIdentifier","raw","-o","-",n],{timeoutMs:5e3});if(o.code===0&&o.stdout.trim()===s)return v.join(t,a)}return null}function It(e,s){return v.join(e,"Library/Application Support",s,"RCTAsyncLocalStorage_V1")}async function Pe(e,s,t,i){i();await E.mkdir(s,{recursive:true});const a=["-a","--delete"];for(const n of t)a.push("--exclude",n);a.push(e.replace(/\/?$/,"/"),s.replace(/\/?$/,"/"));i();await D("rsync",a,{timeoutMs:6e4})}async function Ce(e,s,t,i=()=>{}){i();const a=ue(t);if(a)throw new Error(a);const n=[...je.map(l=>`react-buoy/${l}`),`react-buoy/${vt}`];if(C(v.join(t,"Documents")))await Pe(v.join(t,"Documents"),v.join(e,"Documents"),n,i);if(C(v.join(t,"AsyncStorage/manifest.json"))){await Pe(v.join(t,"AsyncStorage"),It(e,s),[],i)}const o=v.join(t,"Preferences",`${s}.plist`);if(C(o)){i();await E.mkdir(v.join(e,"Library/Preferences"),{recursive:true});await E.copyFile(o,v.join(e,"Library/Preferences",`${s}.plist`))}const r=v.join(e,"Documents/react-buoy");try{for(const l of await E.readdir(r)){i();if(l.startsWith("@buoy~1floating-tool~1"))await E.rm(v.join(r,l),{force:true,recursive:true})}}catch{}}async function te(e,s=()=>{}){s();const t=[];const i=v.join(e,"Documents/react-buoy");let a=[];try{a=await E.readdir(i)}catch{return t}for(const n of a){if(je.includes(n)||n.startsWith("@buoy~1floating-tool~1")){s();await E.rm(v.join(i,n),{force:true,recursive:true});t.push(n)}}return t}async function Le(e){let s;try{s=await E.readFile(v.join(e,"Documents/react-buoy/@react_buoy_sync_device_id.json"),"utf8")}catch{return null}try{const t=JSON.parse(s);if(typeof t==="string")return t||null;if(typeof t?.value==="string")return t.value||null}catch{}return s.trim()||null}var Ue=2*60*6e4;var Et=10;var Ot=5;var Dt=.8;var L=e=>Math.round(e*10)/10;var At=e=>{if(!e.length)return 0;const s=[...e].sort((i,a)=>i-a);const t=s.length>>1;return s.length%2?s[t]:(s[t-1]+s[t])/2};var Ge=class{constructor(e,s,t=1e3,i=Date.now){this.host=e;this.target=s;this.periodMs=t;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 s=`${e.leaseId}:${e.udids.join(",")}`;if(s!==this.treeKey||++this.treeAge>=Et){const y=await Promise.all(e.udids.map(k=>this.host.simTree(k)));this.pids=y.flat().map(k=>k.pid);this.treeAge=0;if(s!==this.treeKey){this.prev=null;this.lastProcs=null;this.ticks=0;this.rates={ours:0,graphics:0,gpuPct:null}}this.treeKey=s}const t=this.ticks++;const i=await this.host.loadCounters(this.pids,t<2||t%Ot===0);this.cpuCount=i.cpuCount;const a=this.now();const n=this.prev;this.prev={...i,at:a,leaseId:e.leaseId};const o=y=>(i.hostTotalMs-y)/Math.max(1,i.cpuCount);if(i.pidsCpuMs!==null&&i.graphicsCpuMs!==null){const y=this.lastProcs;const k=y?o(y.hostTotalMs):0;if(y&&k>0){this.rates.ours=Math.max(0,(i.pidsCpuMs-y.pidsCpuMs)/k);this.rates.graphics=Math.max(0,(i.graphicsCpuMs-y.graphicsCpuMs)/k)}this.rates.gpuPct=i.gpuPct;this.lastProcs={hostTotalMs:i.hostTotalMs,pidsCpuMs:i.pidsCpuMs,graphicsCpuMs:i.graphicsCpuMs}}if(!n||n.leaseId!==e.leaseId)return;const r=o(n.hostTotalMs);if(r<=0)return;const l=Math.max(0,(i.hostBusyMs-n.hostBusyMs)/r);const d=this.rates.ours;const p=this.rates.graphics;this.samples.push({at:Math.round((a+n.at)/2),leaseId:e.leaseId,freeCores:L(Math.max(0,i.cpuCount-l)),ourCores:L(d),graphicsCores:L(p),outsideCores:L(Math.max(0,l-d-p)),gpuPct:this.rates.gpuPct,memLevel:i.memPressureLevel,swapMB:i.swapUsedMB,thermal:i.thermalLevel});const u=a-Ue;while(this.samples.length&&this.samples[0].at<u)this.samples.shift()}finally{this.sampling=false}}before(e,s){for(let t=this.samples.length-1;t>=0;t--){const i=this.samples[t];if(i.at<s)return i.leaseId===e?i:void 0}return void 0}latest(){return this.samples[this.samples.length-1]}verdicts(e,s,t){const i=this.periodMs/2;const a=s.map(o=>{const r=this.samples.filter(d=>d.at>=o.from-i&&d.at<=o.to+i);const l=r.filter(d=>d.leaseId===e);return{w:o,all:r,mine:l}});const n=At(a.filter(o=>o.mine.length).map(o=>pe(o.mine.map(r=>r.outsideCores))));return a.map(({w:o,all:r,mine:l})=>{const d=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()-Ue)){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(d*Dt)){return{...p,reason:"No load record for all of this run. Hold perf-slot for the whole test."}}const u=l.map(f=>f.outsideCores);const y=l.map(f=>f.gpuPct).filter(f=>f!==null);const k=l.map(f=>f.thermal).filter(f=>f!==null);const M={...p,freeCoresMin:Math.min(...l.map(f=>f.freeCores)),outsideCoresAvg:L(pe(u)),outsideCoresMax:Math.max(...u),ourCoresAvg:L(pe(l.map(f=>f.ourCores))),graphicsCoresMax:Math.max(...l.map(f=>f.graphicsCores)),gpuMaxPct:y.length?Math.max(...y):null,swapGrowthMB:Math.round(Math.max(0,l[l.length-1].swapMB-(this.before(e,o.from)?.swapMB??l[0].swapMB))),thermalMax:k.length?Math.max(...k):null};if(M.thermalMax!=null&&M.thermalMax>t.maxThermalLevel){return{...M,reason:"The Mac got hot and slowed itself down. Let it cool, then run again."}}if(l.some(f=>f.memLevel>=4)){return{...M,reason:"The Mac ran low on memory. Close apps or stop sims you don't need."}}if(M.swapGrowthMB>t.maxSwapGrowthMB){return{...M,reason:`The Mac swapped ${M.swapGrowthMB} MB of memory to disk.`}}if(M.freeCoresMin<t.minFreeCores){return{...M,reason:`The Mac ran out of spare CPU (${M.freeCoresMin} cores free, needs ${t.minFreeCores}).`}}const T=L(M.outsideCoresMax-n);if(T>t.maxSwingCores){return{...M,reason:`Other work on the Mac jumped by ${T} cores during this run.`}}return{...M,fair:true}})}};function pe(e){return e.length?e.reduce((s,t)=>s+t,0)/e.length:0}function Fe(e){const s=e.trim().split(":").map(Number);if(!s.length||s.some(t=>!Number.isFinite(t)))return 0;return Math.round(s.reduce((t,i)=>t*60+i,0)*1e3)}var W=1024**3;var me="Buoy Farm ";var fe=".buoy-farm.json";var Tt=120;var $t=180;var we=9e4;var We=500;var qe=200;var Bt=3;var j=["done","failed","stopped","interrupted"];var He=["setting-up","working","waiting","passed","failed","needs-help"];var xt={now:()=>performance.now()};var Qe=class{constructor(){this.tails=new Map}run(e,s){const t=this.tails.get(e)??Promise.resolve();const i=t.catch(()=>{}).then(s);this.tails.set(e,i);i.finally(()=>{if(this.tails.get(e)===i)this.tails.delete(e)}).catch(()=>{});return i}};var Rt=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 s=(this.counts.get(e)??0)-1;if(s>0)this.counts.set(e,s);else this.counts.delete(e)}};var _t=class{constructor(e){this.free=e;this.waiting=[]}async use(e){if(this.free>0)this.free--;else await new Promise(s=>this.waiting.push(s));try{return await e()}finally{const s=this.waiting.shift();if(s)s();else this.free++}}};var ze=class{constructor(e){this.simLock=new Qe;this.deadlines=new Map;this.reservations=new Map;this.busy=new Rt;this.ticking=false;this.hardDeadlines=new Map;this.acquiring=new Set;this.budgetLock=new Qe;this.labels=new Map;this.inflight=new Map;this.workContext=new ut;this.activeWork=0;this.lockQueues=new Map;this.fileTasks=new Set;this.checkActive=()=>this.store.assertActive();this.shotsInFlight=0;this.pendingBg=0;this.host=new Proxy(e.host,{get:(s,t)=>{const i=Reflect.get(s,t);if(typeof i!=="function")return i;return async(...a)=>{this.store.assertActive();const n=await i.apply(s,a);this.store.assertActive();return n}}});this.store=e.store;this.clock=e.clock??xt;this.log=e.log??(()=>{});this.pollMs=e.pollMs??1e3;this.readyTimeoutMs=e.readyTimeoutMs??12e4;this.bootGate=new _t(this.state.settings.maxConcurrentBoots);this.meter=new Ge(this.host,()=>{const s=this.state.locks["perf-slot"];const t=s&&this.state.leases[s.leaseId];return t?{leaseId:t.id,udids:t.sims}:null})}async createTemplate(e){return this.work(void 0,()=>this.createTemplateWork(e))}async cloneSim(e,s){return this.work(void 0,()=>this.cloneSimWork(e,s))}async bootSim(e,s){return this.work(s,()=>this.bootSimWork(e,s))}async parkSim(e,s="idle"){return this.work(void 0,()=>this.parkSimWork(e,s))}async deleteSim(e,s){return this.work(s,()=>this.deleteSimWork(e,s))}async rebuildSim(e,s){return this.work(void 0,()=>this.simLock.run(`rebuild:${e}`,()=>this.rebuildSimWork(e,s)))}async fastReset(e,s){return this.work(void 0,()=>this.fastResetWork(e,s))}async claimOne(e,s){return this.work(e.id,()=>this.claimOneWork(e,s))}async action(e,s,t,i,a,n){this.touch(this.liveLease(e,s,n));const o=this.state.locks["perf-slot"];if(i==="screenshot"&&o&&o.leaseId!==e)throw new c("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,s,t,i,a,n))}async work(e,s){this.store.assertActive();if(this.workContext.getStore()?.active)return s();while(this.state.locks["perf-slot"]&&this.state.locks["perf-slot"].leaseId!==e){await _(this.pollMs);this.store.assertActive()}const t={active:true,leaseId:e};this.activeWork++;try{return await this.workContext.run(t,s)}finally{t.active=false;this.activeWork--}}async files(e){this.checkActive();const s=e();this.fileTasks.add(s);try{const t=await s;this.checkActive();return t}finally{this.fileTasks.delete(s)}}fence(){this.store.fence()}async settleFiles(){await Promise.allSettled([...this.fileTasks])}get state(){return this.store.state}async recover(){for(const[i,a]of Object.entries(this.state.locks)){if(!this.state.leases[a.leaseId])delete this.state.locks[i]}const e=await this.host.listDevices();const s=new Map(e.map(i=>[i.udid,i]));for(const i of Object.values(this.state.sims)){const a=s.get(i.udid);if(!a){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);delete this.state.sims[i.udid];continue}if(a.state==="Booted"){i.power=i.power==="frozen"?"frozen":"awake";if(i.power!=="frozen"){const n=this.state.templates[i.templateId];const o=await this.health(i.udid,n?H(n.profile):[]);i.profile=o.profile;i.lastCheckAt=new Date().toISOString();if(!o.ok||n?.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=a.state==="Booted"?"awake":"cold"}for(const i of Object.values(this.state.templates)){const a=s.get(i.udid);if(!a){i.usable=false;i.note="Its sim was deleted outside the farm."}else if(!De(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(!s.has(i))delete this.state.orphans[i]}for(const i of this.store.pending){if(i.op==="clone"){const a=i.data?.name;const n=e.find(o=>o.name===a&&!this.state.sims[o.udid]);if(n){this.state.orphans[n.udid]={udid:n.udid,name:n.name,cloneIntentName:a};this.store.event({kind:"orphan",message:`Found ${n.name} with no record after a crash. Held for review. Use farm delete to remove it.`,udid:n.udid})}}if(i.op!=="rebuild")this.store.result(i.seq,i.op,{recovered:true})}const t=new Set;for(const i of[...this.store.pending]){if(i.op!=="rebuild")continue;const{udid:a,name:n,templateId:o}=i.data;if(Object.values(this.state.orphans).some(r=>r.name===n))continue;t.add(a);this.busy.add(a);this.background(`resume rebuild ${n}`,()=>this.work(void 0,async()=>{try{const r=Object.values(this.state.sims).find(l=>l.name===n&&l.udid!==a);if(!r){if(this.state.sims[a]){const l=this.state.sims[a];if(l.leaseId)this.dropFromLease(l.leaseId,a);l.leaseId=void 0;await this.deleteSim(a)}await this.cloneSim(o,n)}this.store.result(i.seq,"rebuild",{recovered:true})}finally{this.busy.delete(a)}}))}for(const i of Object.values(this.state.leases)){this.deadlines.set(i.id,this.clock.now()+we);const a=Date.parse(i.createdAt)+i.ttlMs-Date.now();this.hardDeadlines.set(i.id,this.clock.now()+Math.max(a,0));if(a<=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&&!t.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 s=(await this.host.listDevices()).find(t=>t.udid===e);if(!s)throw new c("NOT_FOUND",`No sim ${e} on this Mac.`);return s.dataPath}async isOwned(e){const s=this.state.sims[e]??Object.values(this.state.templates).find(t=>t.udid===e);if(!s)return false;try{const t=JSON.parse(await R.readFile(O.join(await this.dataPath(e),fe),"utf8"));return t.farmId===this.state.farmId}catch{return false}}async assertOwned(e){if(!await this.isOwned(e)){throw new c("NOT_OWNED",`Sim ${e} is not a farm sim. The farm never changes sims it did not make.`)}}async writeMarker(e,s){const t=await this.dataPath(e);await this.files(async()=>{await R.mkdir(t,{recursive:true});this.checkActive();await R.writeFile(O.join(t,fe),JSON.stringify({farmId:this.state.farmId,...s})+"\n")})}farmAwakeGB(){const e=this.state.settings;let s=0;for(const t of Object.values(this.state.sims)){if(["awake","booting","frozen","thawing"].includes(t.power))s+=t.profile==="slim"?e.slimSimGB:e.stockSimGB}return s}async budget(e={ramGB:0,diskGB:0}){const s=this.state.settings;const t=await this.host.hostStats();let i=0;let a=0;for(const k of this.reservations.values()){i+=k.ramGB;a+=k.diskGB}const n=t.totalMemBytes/W;const o=jt(n)*(t.memPressure==="warn"?2:1);const r=t.availableMemBytes/W-i;const l=n-Nt(n,s.reserveRamGB)-this.farmAwakeGB()-i;const d=t.freeDiskBytes/W-a-s.reserveDiskGB;const p=Math.max(0,Math.floor(Math.min(r-o,l,d/s.cloneDiskGB*s.slimSimGB)/s.slimSimGB));const u=(k,M)=>({ok:false,code:k,reason:M,stats:t,roomFor:0});if(t.memPressure==="critical")return u("RAM_LOW","This Mac is low on memory. New sims will wait.");if(e.diskGB>0&&d-e.diskGB<0){return u("DISK_LOW",`Need ${s.reserveDiskGB} GB free after this. This Mac has ${Math.floor(t.freeDiskBytes/W)} GB.`)}if(e.ramGB>0){if(r-e.ramGB<o||l-e.ramGB<0){return u("RAM_LOW",`Not enough free memory for another sim (${Math.max(0,Math.floor(r))} GB free).`)}}const y=this.state.locks["perf-slot"];if(y&&(!this.workContext.getStore()?.active||this.workContext.getStore()?.leaseId!==y.leaseId))return u("HOST_BUSY","A speed test is running. New sims will wait.");return{ok:true,stats:t,roomFor:p}}async withReservation(e,s){const t=Z(4).toString("hex");await this.budgetLock.run("budget",async()=>{const i=await this.budget(e);if(!i.ok)throw new c(i.code,i.reason,void 0,true);this.reservations.set(t,e)});try{return await s()}finally{this.reservations.delete(t)}}async health(e,s=[]){const t=[];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 a=new Set(i.map(r=>r.comm.split("/").pop()));for(const r of["SpringBoard","backboardd"])if(!a.has(r))t.push(`${r} is not running`);const n=Je(await this.host.launchdLogTail(e));if(n)t.push(`${n} keeps crashing`);if(t.length===0){const r=O.join(this.store.shotsPath,`${e}.png`);await this.files(()=>R.mkdir(O.dirname(r),{recursive:true}));if(!await this.host.screenshot(e,r,1e4))t.push("screenshot did not come back")}let o=i.length<Tt?"slim":i.length>$t?"stock":"unknown";if(t.length===0&&s.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 d=s.filter(p=>!l.has(p)).length;o=d===0?"slim":d===s.length?"stock":"unknown"}else{o="unknown"}}return{ok:t.length===0,profile:o,faults:t,procs:i.length}}async waitHealthy(e,s=[]){const t=performance.now()+this.readyTimeoutMs;let i=await this.health(e,s);while(!i.ok&&performance.now()<t){await _(this.pollMs);i=await this.health(e,s)}return i}async createTemplateWork(e){if(!["slim","stock"].includes(e.profile)||!Number.isInteger(e.metroPort)||e.metroPort<1||e.metroPort>65535){throw new c("BAD_REQUEST","Use slim or stock and a port from 1 to 65535.")}const s=e.seedDir==null?null:ue(e.seedDir);if(s)throw new c("NO_SEED",s,"Run: pnpm e2e:pool seed");if(!De(O.join(e.appPath,"Info.plist"))){throw new c("BAD_REQUEST",`No app build at ${e.appPath}.`,"Build the app first.")}const t=await Mt(e.appPath);const i=`t-${Z(3).toString("hex")}`;const a=e.name??`${me}Template ${i.slice(2)}`;return this.withReservation({ramGB:this.state.settings.stockSimGB,diskGB:4},async()=>{const n=this.store.intent("create-template",{id:i,name:a});const o=await this.host.create(a,e.deviceType,e.runtime);const r={id:i,udid:o,name:a,bundleId:t,appPath:e.appPath,appHash:await bt(e.appPath),seedDir:e.seedDir??null,seedHash:e.seedDir?await St(e.seedDir):null,launchUrl:e.launchUrl,deviceType:e.deviceType,runtime:e.runtime,xcodeBuild:await this.host.xcodeBuild(),profile:e.profile,profileHash:Oe(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(n,"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 ${a} 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,s){const t=H(e.profile);if(t.length)await this.files(()=>Re(e.udid,t,this.host.disabledRoot));await this.host.boot(e.udid);const i=await this.waitHealthy(e.udid,t);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 a=await this.host.appContainer(e.udid,e.bundleId);if(!a)throw new Error("app installed but has no data container");if(e.seedDir)await this.files(()=>Ce(a,e.bundleId,e.seedDir,this.checkActive));else await this.files(()=>te(a,this.checkActive));await this.setPrefs(e.udid,e);if(s){await this.launchApp(e.udid,e);await _(Math.min(15e3,this.pollMs*15));await this.host.terminate(e.udid,e.bundleId)}await this.host.shutdown(e.udid);await this.files(()=>te(a,this.checkActive))}async launchApp(e,s){await this.host.launch(e,s.bundleId);if(s.launchUrl)await this.host.openUrl(e,s.launchUrl)}async setPrefs(e,s){const t=["defaults","write","com.apple.keyboard.preferences"];await this.host.spawn(e,[...t,"AutomaticMinimizationEnabled","-bool","true"]);await this.host.spawn(e,[...t,"DidShowContinuousPathIntroduction","-bool","true"]);await this.host.spawn(e,["defaults","write",s.bundleId,"RCT_jsLocation",`localhost:${s.metroPort}`])}async nextName(){const e=await this.host.listDevices();const s=new Set([...e.map(i=>i.name),...Object.values(this.state.sims).map(i=>i.name)]);let t=this.state.nextIndex;while(s.has(`${me}${String(t).padStart(2,"0")}`))t++;this.state.nextIndex=t+1;return`${me}${String(t).padStart(2,"0")}`}template(e){const s=this.state.templates[e];if(!s)throw new c("NO_TEMPLATE",`No template ${e}.`,"Run sim_farm_setup to make one.");if(!s.usable)throw new c("NO_TEMPLATE",`Template ${s.name} can't be used: ${s.note??"not ready"}.`);return s}async cloneSimWork(e,s,t){s??=await this.nextName();const i=s;const a=this.template(e);return this.withReservation({ramGB:0,diskGB:this.state.settings.cloneDiskGB},()=>this.simLock.run(`template:${a.id}`,async()=>{await this.assertOwned(a.udid);const n=this.store.intent("clone",{templateId:e,name:i});const o=await this.host.clone(a.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(n,"clone",{udid:o});await this.writeMarker(o,{template:a.id});const l=await ee(await this.dataPath(o),a.bundleId);if(l)await this.files(()=>te(l,this.checkActive));const d=t?this.state.leases[t.leaseId]:void 0;if(d&&!this.leaseOverdue(d)){r.leaseId=d.id;d.sims=d.sims.map(p=>p===t.udid?o:p)}r.power="cold";this.store.event({kind:"clone",message:`Made ${s}.`,udid:o});this.store.save();return r}))}async bootSimWork(e,s){await this.assertOwned(e);const t=this.sim(e);if(t.power==="frozen"||t.power==="thawing"){throw new c("BAD_REQUEST",`${t.name} is frozen. Thaw it first.`)}const i=this.state.templates[t.templateId];const a=this.state.settings;const n={ramGB:i?.profile==="slim"?a.slimSimGB:a.stockSimGB,diskGB:0};return this.withReservation(n,()=>this.bootGate.use(()=>this.simLock.run(e,async()=>{this.assertLeaseState(t,s);if(t.power==="awake")return t;const o=i?H(i.profile):[];if(o.length&&!await kt(e,o,this.host.disabledRoot)){await this.files(()=>Re(e,o,this.host.disabledRoot))}t.power="booting";t.bootEpoch++;this.store.save();await this.host.boot(e);const r=await this.waitHealthy(e,i?.profile==="slim"?o:[]);t.lastCheckAt=new Date().toISOString();t.profile=r.profile;t.power="awake";if(!r.ok){t.failures++;t.health=t.failures>=2?"broken":"suspect";t.lastError=r.faults.join("; ");this.store.event({kind:"unhealthy",message:`${t.name}: ${t.lastError}`,udid:e});this.store.save();throw new c("HOST_ERROR",`${t.name} did not come up: ${t.lastError}`,"The farm will rebuild it.",true)}t.failures=0;if(t.health==="suspect")t.health="ok";t.idleSince=new Date().toISOString();if(i?.profile==="slim"&&r.profile==="unknown"){t.health="suspect";t.lastError="can't tell if slim applied";this.store.event({kind:"unknown-profile",message:`${t.name}: can't tell if slim worked. Held for now.`,udid:e});this.store.save();throw new c("HOST_ERROR",`${t.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:`${t.name}: slim didn't work here (${r.profile}). It uses full RAM.`,udid:e});const l=await this.budget({ramGB:a.stockSimGB-a.slimSimGB,diskGB:0});if(!l.ok){t.health="suspect";t.lastError="slim did not apply and there is no RAM for a full sim";await this.host.shutdown(e).catch(()=>{});t.power="parked";this.store.save();throw new c("RAM_LOW",`${t.name}: slim didn't apply and there's no room for a full sim.`,void 0,true)}}this.store.save();return t})))}assertLeaseState(e,s){if(!this.state.sims[e.udid])throw new c("NOT_FOUND",`${e.name} is gone.`);if(s){const t=this.state.leases[s];if(e.leaseId!==s||!t||this.leaseOverdue(t)){throw new c("LEASE_EXPIRED",`${e.name} is no longer in lease ${s}.`)}}else if(e.leaseId){throw new c("BAD_REQUEST",`${e.name} is in use. End its lease first.`)}}async parkSimWork(e,s="idle"){await this.assertOwned(e);const t=this.sim(e);await this.simLock.run(e,async()=>{this.assertLeaseState(t);if(this.busy.has(e))throw new c("BAD_REQUEST",`${t.name} is being reset.`);if(t.power==="frozen"||t.power==="thawing")throw new c("BAD_REQUEST",`${t.name} is frozen. Wake it before parking.`);if(t.power!=="awake")return;this.busy.add(e);try{await this.host.shutdown(e);t.power="parked"}finally{this.busy.delete(e)}this.store.event({kind:"park",message:`Parked ${t.name} (${s}).`,udid:e});this.store.save()})}sim(e){const s=this.state.sims[e];if(!s)throw new c("NOT_OWNED",`Sim ${e} is not a farm sim.`);return s}async deleteSimWork(e,s){const t=this.state.orphans[e];if(t){await this.simLock.run(e,async()=>{const a=(await this.host.listDevices()).find(r=>r.udid===e);if(!a)throw new c("NOT_FOUND","This sim is gone.");let n=false;try{const r=JSON.parse(await R.readFile(O.join(a.dataPath,fe),"utf8"));n=r.farmId===this.state.farmId}catch(r){n=r.code==="ENOENT"&&a.name===t.cloneIntentName}if(!n)throw new c("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(()=>R.rm(O.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`),{recursive:true,force:true}));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,s);if(i.power==="frozen"||i.power==="thawing")throw new c("BAD_REQUEST",`${i.name} is frozen. Wake it first.`);i.power="deleting";const a=this.store.intent("delete",{udid:e});await this.host.shutdown(e).catch(()=>{});await this.host.delete(e);await this.files(()=>R.rm(O.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`),{recursive:true,force:true}));delete this.state.sims[e];this.store.result(a,"delete",{});this.store.event({kind:"delete",message:`Deleted ${i.name}.`,udid:e})})}async rebuildSimWork(e,s){const t=this.sim(e);const{name:i,templateId:a}=t;await this.assertOwned(e);await this.simLock.run(e,async()=>{this.assertLeaseState(t,s);this.busy.add(e)});const n=this.store.intent("rebuild",{udid:e,name:i,templateId:a});try{await this.deleteSim(e,s);const o=await this.cloneSimWork(a,i,s?{udid:e,leaseId:s}:void 0);this.store.result(n,"rebuild",{udid:o.udid});return o}finally{this.busy.delete(e)}}async fastResetWork(e,s){await this.assertOwned(e);const t=this.sim(e);const i=this.template(t.templateId);await this.simLock.run(e,async()=>{this.assertLeaseState(t,s);if(t.power==="frozen"||t.power==="thawing")throw new c("BAD_REQUEST",`${t.name} is frozen. Wake it first.`);this.busy.add(e);try{if(t.power==="awake")await this.host.terminate(e,i.bundleId);const a=await ee(await this.dataPath(e),i.bundleId);if(!a)throw new Error("app container not found");if(i.seedDir)await this.files(()=>Ce(a,i.bundleId,i.seedDir,this.checkActive));else await this.files(()=>te(a,this.checkActive));t.health="ok";this.store.save()}finally{this.busy.delete(e)}})}async grow(e,s){if(!Number.isInteger(s)||s<1||s>64)throw new c("BAD_REQUEST","count must be 1 to 64.");const t=[];for(let i=0;i<s;i++)t.push(await this.cloneSim(e));return t}async shrink(e,s){if(!Number.isInteger(s)||s<1||s>64)throw new c("BAD_REQUEST","count must be 1 to 64.");const t=["cold","parked","awake"];const i=Object.values(this.state.sims).filter(n=>n.templateId===e&&!n.leaseId&&t.includes(n.power)&&!this.busy.has(n.udid)).sort((n,o)=>t.indexOf(n.power)-t.indexOf(o.power));const a=[];for(const n of i.slice(0,s)){try{await this.deleteSim(n.udid);a.push(n.name)}catch(o){if(!(o instanceof c))throw o}}return a}async removeTemplate(e){return this.work(void 0,async()=>{const s=this.state.templates[e];if(!s)throw new c("NO_TEMPLATE",`No template ${e}.`);const t=Object.values(this.state.sims).filter(o=>o.templateId===e);const i=t.filter(o=>o.leaseId||this.busy.has(o.udid));if(i.length){throw new c("HOST_BUSY",`${i.map(o=>o.name).join(", ")} ${i.length===1?"is":"are"} in use.`,"End those uses first.",true)}const a=[];for(const o of t){await this.deleteSim(o.udid);a.push(o.name)}const n=(await this.host.listDevices()).some(o=>o.udid===s.udid);if(n){await this.assertOwned(s.udid);await this.simLock.run(s.udid,async()=>{const o=this.store.intent("delete",{udid:s.udid});await this.host.shutdown(s.udid).catch(()=>{});await this.host.delete(s.udid);await this.files(()=>R.rm(O.join(this.host.disabledRoot,`com.apple.CoreSimulator.SimDevice.${s.udid}`),{recursive:true,force:true}));this.store.result(o,"delete",{})})}delete this.state.templates[e];a.push(s.name);this.store.event({kind:"delete",message:`Removed template ${s.name} and its ${t.length} sim(s).`});return{removed:a}})}leaseOverdue(e){const s=this.clock.now();if(s>(this.hardDeadlines.get(e.id)??Infinity))return true;return!this.acquiring.has(e.id)&&s>(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 c("BAD_REQUEST","ttlMs must be a finite number above zero.")}const s=this.template(e.templateId);if(!Number.isInteger(e.count)||e.count<1||e.count>64)throw new c("BAD_REQUEST","count must be 1 to 64.");const t=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,s.id,t);const a=Date.parse(i.deadlineAt)-Date.now();if(a<3e4)throw new c("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 n={id:`l-${Z(4).toString("hex")}`,epoch:1,owner:e.owner,purpose:e.purpose,runId:i.id,templateId:s.id,sims:[],createdAt:new Date().toISOString(),ttlMs:Math.min(t,a)};i.leases.push(n.id);i.lastActivityAt=n.createdAt;if(e.labels?.length)this.labels.set(n.id,e.labels);this.state.leases[n.id]=n;this.hardDeadlines.set(n.id,this.clock.now()+n.ttlMs);this.acquiring.add(n.id);const o=()=>this.deadlines.set(n.id,this.clock.now()+Math.min(n.ttlMs,we));o();const r=performance.now()+(e.waitMs??0);const l=()=>{if(!this.state.leases[n.id])throw new c("LEASE_EXPIRED","The lease ended while sims were being prepared.");if(this.leaseOverdue(n))throw new c("LEASE_EXPIRED","The lease ran out of time while sims were being prepared.")};try{while(n.sims.length<e.count){l();o();const d=await this.claimOne(n,s);l();if(d)continue;if(performance.now()>=r)break;if(!n.sims.length&&i.state==="active"&&!Object.keys(i.lanes).length)i.state="waiting";await _(Math.max(this.pollMs,2e3))}if(n.sims.length<e.count&&!e.allowPartial){const d=await this.budget({ramGB:this.state.settings.slimSimGB,diskGB:0});throw new c(d.code??"HOST_BUSY",`Got ${n.sims.length} of ${e.count} sims. ${d.reason??"Others are in use."}`,"Try again later or ask for fewer.",true)}if(n.sims.length===0)throw new c("HOST_BUSY","No sim is free right now.","Try again later.",true);if(e.launch)await this.work(n.id,async()=>{l();await Promise.all(n.sims.map(d=>this.launchApp(d,s)));o();await this.checkIdentities(n,6e4);l()});o();this.store.event({kind:"lease",message:`${e.owner.label} took ${n.sims.length} sim(s) for ${i.goal}.`,leaseId:n.id,runId:i.id});this.note(i,"farm",`Got ${n.sims.length} sim${n.sims.length===1?"":"s"}.`);this.store.save();return{lease:n,run:i,sims:n.sims.map(d=>this.state.sims[d])}}catch(d){if(this.state.leases[n.id])this.note(i,"farm",`Could not get sims: ${d.message}`);await this.endLease(n.id,"acquire failed","fast","acquire-failed").catch(()=>{});throw d}finally{this.acquiring.delete(n.id);this.labels.delete(n.id)}}async claimOneWork(e,s){const t=Object.values(this.state.sims).filter(o=>o.templateId===s.id&&!o.leaseId&&o.health==="ok"&&!this.busy.has(o.udid));const i=t.find(o=>o.power==="awake")??t.find(o=>o.power==="parked"||o.power==="cold");const a=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 a(i);let n=null;try{if(i){n=await a(i)}else{const o=await this.budget({ramGB:s.profile==="slim"?this.state.settings.slimSimGB:this.state.settings.stockSimGB,diskGB:this.state.settings.cloneDiskGB});if(!o.ok)return null;n=await a(await this.cloneSim(s.id))}if(!n)return null;await this.bootSim(n.udid,e.id);return n}catch(o){if(n)this.unclaim(e,n.udid);if(o instanceof c&&(o.code==="RAM_LOW"||o.code==="DISK_LOW"||o.code==="HOST_BUSY"||o.code==="LEASE_EXPIRED")){return null}if(n&&this.state.sims[n.udid]?.health!=="ok"){const r=n.udid;this.background(`rebuild ${r}`,()=>this.rebuildSim(r))}throw o}}unclaim(e,s){e.sims=e.sims.filter(a=>a!==s);const t=e.runId?this.state.runs[e.runId]:void 0;if(t?.lanes[s])delete t.lanes[s];const i=this.state.sims[s];if(i&&i.leaseId===e.id)i.leaseId=void 0;this.store.save()}async checkIdentities(e,s=6e4){const t=this.template(e.templateId);const i=performance.now()+s;const a=new Map;for(;;){for(const o of e.sims){if(a.has(o))continue;const r=await ee(await this.dataPath(o),t.bundleId);const l=r?await Le(r):null;if(!l)continue;const d=[...a.entries()].find(([,u])=>u===l);if(d)throw new c("ID_CLASH",`${this.state.sims[o].name} and ${this.state.sims[d[0]].name} share one Buoy ID.`);const p=Object.values(this.state.sims).find(u=>u.udid!==o&&!e.sims.includes(u.udid)&&u.buoyDeviceId===l);if(p)throw new c("ID_CLASH",`${this.state.sims[o].name} and ${p.name} share one Buoy ID.`);a.set(o,l);this.state.sims[o].buoyDeviceId=l}if(a.size===e.sims.length||performance.now()>=i)break;await _(Math.max(this.pollMs,1e3))}const n=e.sims.filter(o=>!a.has(o));if(n.length){this.store.event({kind:"no-id",message:`No Buoy ID yet on ${n.map(o=>this.state.sims[o].name).join(", ")}. Is the app loading? Check Metro.`,leaseId:e.id})}}liveLease(e,s,t){const i=this.state.leases[e];if(!i){const a=this.runOfLease(e);if(a&&(a.endedBy==="user"||a.endedBy==="deadline"))this.assertRunOpen(a);throw new c("LEASE_EXPIRED",`Lease ${e} has ended.`,"Acquire new sims.")}if(i.epoch!==s)throw new c("STALE_EPOCH",`Lease ${e} is at epoch ${i.epoch}, not ${s}.`);if(t&&i.owner.clientId!==t)throw new c("NOT_OWNED",`Lease ${e} belongs to ${i.owner.label}.`);if(this.leaseOverdue(i)){const a=this.overdueEnd(i);this.background(`expire ${i.id}`,()=>this.endLease(i.id,"expired","full",a));if(a==="deadline")throw new c("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 c("LEASE_EXPIRED",`Lease ${e} has expired.`,"Acquire new sims.")}return i}renew(e,s,t){const i=this.liveLease(e,s,t);const a=Math.min(this.clock.now()+Math.min(i.ttlMs,we),this.hardDeadlines.get(i.id)??Infinity);this.deadlines.set(i.id,a);return{expiresInMs:Math.round(a-this.clock.now())}}async release(e,s,t="fast",i,a=false){if(t!=="fast"&&t!=="full")throw new c("BAD_REQUEST","Use fast or full reset.");this.liveLease(e,s,i);return this.endLease(e,"released",t,a?null:"released")}async endLease(e,s,t,i="released"){const a=this.state.leases[e];if(!a)return{};a.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 n=new Set(a.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 a.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"}this.store.event({kind:"lease-end",message:`${a.owner.label}'s ${a.purpose} ended (${s}).`,leaseId:e,runId:a.runId});this.settleRun(a,i);this.store.save();this.background(`reset after ${e}`,()=>this.work(void 0,async()=>{for(const r of a.sims){if(!this.state.sims[r])continue;try{const l=Date.parse(a.createdAt)-2e3;for(const d of await this.host.clientsOf(r)){if(Date.parse(d.start)>=l)await this.host.kill(d.pid,d.start)}if(t==="full"&&!n.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,s){const t=this.state.leases[e];if(t)t.sims=t.sims.filter(i=>i!==s)}async actionWork(e,s,t,i,a,n){const o=this.liveLease(e,s,n);const r={};for(const l of t){if(!o.sims.includes(l))throw new c("NOT_OWNED",`${l} is not in lease ${e}.`);await this.assertOwned(l);this.liveLease(e,s,n);const d=this.template(o.templateId);switch(i){case"screenshot":{const p=this.state.locks["perf-slot"];if(p&&p.leaseId!==o.id)throw new c("HOST_BUSY","A speed test is running. No screenshots right now.",void 0,true);const u=O.join(this.store.shotsPath,`${l}.png`);r[l]=await this.host.screenshot(l,u)?u:null;break}case"open-url":if(!a)throw new c("BAD_REQUEST","open-url needs a url.");await this.host.openUrl(l,a);r[l]="opened";break;case"launch":await this.launchApp(l,d);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 c("LEASE_EXPIRED",`Lease ${e} ended during the rebuild.`);p.leaseId=o.id;o.sims=o.sims.map(k=>k===l?p.udid:k);const u=o.runId?this.state.runs[o.runId]:void 0;const y=u?.lanes[l];if(u&&y){delete u.lanes[l];u.lanes[p.udid]={...y,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 c("BAD_REQUEST",`Unknown action ${i}.`)}}this.store.save();return r}async lock(e,s,t,i=0,a){if(typeof e!=="string"||!/^[a-z0-9-]{1,40}$/.test(e))throw new c("BAD_REQUEST","Lock names are short lowercase words.");this.touch(this.liveLease(s,t,a));if(this.state.locks[e]?.leaseId===s)return{held:true};const n=this.lockQueues.get(e)??[];this.lockQueues.set(e,n);const o={};n.push(o);const r=performance.now()+i;try{for(;;){this.store.assertActive();this.liveLease(s,t,a);const l=this.state.locks[e];const d=e==="perf-slot"&&(this.activeWork>0||this.busy.size>0||this.pendingBg>0);if(n[0]===o&&!l&&!d){this.state.locks[e]={name:e,leaseId:s,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 _(Math.max(1,Math.min(this.pollMs,r-performance.now())))}}finally{n.splice(n.indexOf(o),1);if(!n.length)this.lockQueues.delete(e)}}fairness(e,s,t,i){this.liveLease(e,s,i);const a=this.state.settings;const n=Pt(a,this.meter.cpuCount);const o=this.meter.verdicts(e,t,n);return{rules:n,fair:o.filter(r=>r.fair).length,unfair:o.filter(r=>!r.fair).length,runs:o}}unlock(e,s,t,i){this.liveLease(s,t,i);if(this.state.locks[e]?.leaseId===s)delete this.state.locks[e];this.store.save();return{held:false}}async shot(e){if(this.state.locks["perf-slot"])throw new c("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(this.shotsInFlight>=Bt)throw new c("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 s=this.sim(e);if(this.state.locks["perf-slot"])throw new c("HOST_BUSY","A speed test is running. No stills right now.",void 0,true);if(s.power!=="awake")throw new c("BAD_REQUEST",`${s.name} is ${s.power}.`);const t=O.join(this.store.shotsPath,`${e}.png`);await this.files(()=>R.mkdir(O.dirname(t),{recursive:true}));if(!await this.host.screenshot(e,t,8e3))throw new c("HOST_ERROR",`No still from ${s.name} in time.`,void 0,true);return{file:t,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 s=this.sim(e);if(s.power==="frozen"||s.power==="thawing")throw new c("BAD_REQUEST",`${s.name} is paused. Wake it first.`);if(s.power!=="awake")await this.bootSim(e,s.leaseId);await this.host.showWindow(e);return"shown"}async tick(){if(this.ticking)return;this.ticking=true;try{for(const t of Object.values(this.state.leases)){const i=!await P(t.owner.pid,t.owner.pidStart);if(i)await this.endLease(t.id,"its owner exited","full","owner-exited");else if(this.leaseOverdue(t))await this.endLease(t.id,"no renewal","full",this.overdueEnd(t))}await this.tickRuns();const e=this.state.settings.parkAfterMinutes;if(e>0){for(const t of Object.values(this.state.sims)){if(t.power!=="awake"||t.leaseId||this.busy.has(t.udid)||!t.idleSince)continue;if(Date.now()-Date.parse(t.idleSince)>e*6e4){await this.parkSim(t.udid).catch(i=>this.log(`park ${t.name}: ${i.message}`))}}}const s=Object.keys(this.state.requests);if(s.length>We)for(const t of s.slice(0,s.length-We))delete this.state.requests[t]}finally{this.ticking=false}}async drain(e=10*6e4){const s=performance.now()+e;while((this.busy.size>0||this.pendingBg>0||this.activeWork>0)&&performance.now()<s)await _(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,s){this.pendingBg++;this.workContext.exit(s).catch(t=>{this.log(`${e}: ${t.message}`);try{this.store.event({kind:"error",message:`${e}: ${t.message}`})}catch{}}).finally(()=>this.pendingBg--)}async once(e,s,t,i){if(!s)return i();const a=`${e}:${s}`;const n=pt("sha256").update(JSON.stringify(t)).digest("hex").slice(0,16);const o=this.state.requests[a];if(o){if(o.hash!==n)throw new c("REQUEST_REUSED",`Request ${s} was already used for a different call.`);return o.result}const r=this.inflight.get(a);if(r){if(r.hash!==n)throw new c("REQUEST_REUSED",`Request ${s} was already used for a different call.`);return r.promise}const l=i();this.inflight.set(a,{hash:n,promise:l});try{const d=await l;this.state.requests[a]={hash:n,result:d,at:new Date().toISOString()};this.store.save();return d}finally{this.inflight.delete(a)}}newRun(e,s,t){const i=Date.now();const a=e.owner.agent?this.state.settings.runCapMinutes*6e4:24*36e5;const n=(e.goal??"").trim()||e.purpose;const o={id:`r-${Z(4).toString("hex")}`,goal:n.slice(0,200),kind:e.kind?.slice(0,40),owner:e.owner,templateId:s,state:"active",createdAt:new Date(i).toISOString(),deadlineAt:new Date(i+Math.min(t,a)).toISOString(),capAt:new Date(i+a).toISOString(),leases:[],lanes:{},notes:[],lastActivityAt:new Date(i).toISOString(),auto:!e.goal};this.state.runs[o.id]=o;return o}touch(e){const s=e.runId?this.state.runs[e.runId]:void 0;if(s)s.lastActivityAt=new Date().toISOString()}runOfLease(e){return Object.values(this.state.runs).find(s=>s.leases.includes(e))}assertRunOpen(e){if(e.state==="stopping"||e.state==="stopped"){if(e.endedBy==="user")throw new c("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 c("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(j.includes(e.state)||e.state==="stopping")throw new c("RUN_ENDED",`This run has ended (${e.state}).`,"Start a new run.")}ownRun(e,s){const t=this.state.runs[e];if(!t)throw new c("NOT_FOUND",`No run ${e}.`,"Use the runId that sim_farm_acquire returned.");if(s&&t.owner.clientId!==s)throw new c("NOT_OWNED",`Run ${e} belongs to ${t.owner.label}.`);this.assertRunOpen(t);return t}note(e,s,t,i){e.notes.push({at:new Date().toISOString(),by:s,text:t.slice(0,500),udid:i});if(e.notes.length>qe)e.notes.splice(0,e.notes.length-qe)}addLane(e,s){const t=e.runId?this.state.runs[e.runId]:void 0;if(!t)return;const i=this.labels.get(e.id)?.[e.sims.length-1];t.lanes[s.udid]={udid:s.udid,name:s.name,label:i?.slice(0,80),state:"setting-up",at:new Date().toISOString()};if(t.state==="waiting")t.state="active"}overdueEnd(e){return this.clock.now()>=(this.hardDeadlines.get(e.id)??Infinity)?"deadline":"lost"}settleRun(e,s){const t=e.runId?this.state.runs[e.runId]:void 0;if(!t)return;for(const a of e.sims)if(t.lanes[a])t.lanes[a].gone=true;if(j.includes(t.state)||t.state==="stopping"||s===null)return;const i=t.leases.some(a=>this.state.leases[a]);switch(s){case"released":if(!i)this.endRun(t,this.outcomeOf(t)==="failed"?"failed":"done","released");return;case"acquire-failed":if(!i&&!Object.keys(t.lanes).length)this.endRun(t,"failed","acquire-failed");return;case"user":return this.endRun(t,"stopped","user","You stopped this run.");case"deadline":return this.endRun(t,"stopped","deadline","Ran out of time. The sims were taken back.");case"owner-exited":return this.endRun(t,"interrupted","owner-exited","The chat closed. Cleaning up.");case"lost":return this.endRun(t,"interrupted","lost","Lost touch with the chat. Cleaning up.");case"finished":return this.endRun(t,"done","finished")}}endRun(e,s,t,i,a="full"){if(j.includes(e.state))return;e.state="stopping";e.endedBy=t;if(i)this.note(e,t==="user"?"person":"farm",i);for(const n of e.leases){if(this.state.leases[n])void this.endLease(n,i??t,t==="finished"||t==="released"?a:"full",null)}for(const n of Object.values(e.lanes))n.gone=true;e.state=s;e.endedAt=new Date().toISOString();e.outcome=e.outcome??this.outcomeOf(e);this.store.event({kind:"run-end",message:`${e.owner.label}: "${e.goal}" ${s==="done"?"finished":s}.`,runId:e.id})}outcomeOf(e){const s=Object.values(e.lanes);const t=s.filter(a=>a.state==="passed").length;const i=s.filter(a=>a.state==="failed").length;if(!t&&!i)return"unknown";if(i&&t)return"mixed";return i?"failed":"passed"}report(e,s){const t=this.ownRun(e.runId,s);const i=new Date().toISOString();if(e.sequence!==void 0){if(!Number.isInteger(e.sequence)||e.sequence<0)throw new c("BAD_REQUEST","sequence must be a whole number.");if(t.lastSeq!==void 0&&e.sequence<=t.lastSeq)return{applied:false,reason:"already have this report",...this.timeLeft(t)};t.lastSeq=e.sequence}if(e.state!==void 0&&!He.includes(e.state))throw new c("BAD_REQUEST",`state must be one of ${He.join(", ")}.`);if(e.udid!==void 0){const a=t.lanes[e.udid];if(!a)throw new c("NOT_FOUND",`That sim is not in run ${t.id}.`,"Use a UDID from this run's sims.");if(e.state)a.state=e.state;if(e.state==="passed"||e.state==="failed")a.resultSource=e.resultSource==="test"?"test":"agent";if(e.note!==void 0)a.note=String(e.note).slice(0,200);a.at=i}if(e.counts!==void 0){const a=e.counts;const n=o=>o===void 0||Number.isInteger(o)&&o>=0;if(!a||typeof a!=="object"||!n(a.done)||!n(a.total)||!n(a.passed)||!n(a.failed)||a.done>a.total){throw new c("BAD_REQUEST","counts needs whole numbers, with done no more than total.")}t.counts={done:a.done,total:a.total,passed:a.passed,failed:a.failed}}if(e.note)this.note(t,"agent",String(e.note),e.udid);t.lastReportAt=t.lastActivityAt=i;this.store.save();return{applied:true,...this.timeLeft(t)}}extend(e,s,t){const i=this.ownRun(e,t);if(!Number.isFinite(s)||s<=0)throw new c("BAD_REQUEST","minutes must be above zero.");const a=Date.parse(i.deadlineAt);const n=Date.now()+s*6e4;const o=Math.min(n,Date.parse(i.capAt));if(o<=a){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<n,...this.timeLeft(i)}}finish(e,s,t,i){const a=this.state.runs[e];if(a?.endedBy==="finished"&&a.owner.clientId===i)return{state:a.state,outcome:a.outcome};const n=this.ownRun(e,i);if(s&&!["passed","failed","mixed","unknown"].includes(s))throw new c("BAD_REQUEST","outcome must be passed, failed, mixed or unknown.");n.outcome=s??this.outcomeOf(n);if(t)n.summary=t.slice(0,600);this.endRun(n,n.outcome==="failed"?"failed":"done","finished",void 0,"fast");this.store.save();return{state:n.state,outcome:n.outcome}}stopRun(e,s="you"){const t=this.state.runs[e];if(!t)throw new c("NOT_FOUND",`No run ${e}.`);if(j.includes(t.state))return{state:t.state};this.endRun(t,"stopped","user",s==="you"?"You stopped this run.":`Stopped by ${s}.`);this.store.save();return{state:t.state}}stopAll(e="you"){const s=Object.values(this.state.runs).filter(t=>!j.includes(t.state));for(const t of s)this.stopRun(t.id,e);return{stopped:s.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 a of Object.values(this.state.runs)){if(j.includes(a.state))continue;if(e>=Date.parse(a.deadlineAt)){this.endRun(a,"stopped","deadline","Ran out of time. The sims were taken back.");this.store.save()}else if(!a.leases.some(n=>this.state.leases[n])&&!await P(a.owner.pid,a.owner.pidStart)){this.endRun(a,"interrupted","owner-exited","The chat closed.");this.store.save()}}const s=this.state.settings.keepRunsHours*36e5;const t=Object.values(this.state.runs).filter(a=>j.includes(a.state)).sort((a,n)=>Date.parse(n.endedAt??n.createdAt)-Date.parse(a.endedAt??a.createdAt));let i=false;t.forEach((a,n)=>{if(n>=200||e-Date.parse(a.endedAt??a.createdAt)>s){delete this.state.runs[a.id];i=true}});if(i)this.store.save()}async status(e={}){const s=await this.budget();const t=Object.values(this.state.sims).map(a=>{const n=a.leaseId?this.state.leases[a.leaseId]:void 0;const o=n?.runId?this.state.runs[n.runId]:void 0;return{...a,owner:n?.owner.label,purpose:o?.goal??n?.purpose,runId:o?.id,lane:o?.lanes[a.udid]?.label,busy:this.busy.has(a.udid)}});const i={farmId:this.state.farmId,counts:{pool:t.length,awake:t.filter(a=>a.power==="awake").length,leased:t.filter(a=>a.leaseId).length},budget:{roomForMoreSims:s.roomFor,freeDiskGB:Math.floor(s.stats.freeDiskBytes/W),availableMemGB:Math.floor(s.stats.availableMemBytes/W),memPressure:s.stats.memPressure,loadAvg1:Number(s.stats.loadAvg1.toFixed(1)),cpuCount:s.stats.cpuCount},templates:Object.values(this.state.templates),sims:t,orphans:Object.values(this.state.orphans),leases:Object.values(this.state.leases).map(a=>({...a,expiresInMs:Math.max(0,Math.round(this.leaseDeadline(a)-this.clock.now()))})),runs:Object.values(this.state.runs).sort((a,n)=>Date.parse(n.createdAt)-Date.parse(a.createdAt)).map(a=>({...a,notes:a.notes.slice(-50),secondsLeft:j.includes(a.state)?0:Math.max(0,Math.round((Date.parse(a.deadlineAt)-Date.now())/1e3))})),locks:Object.values(this.state.locks),recentEvents:this.state.events.slice(-25),settings:this.state.settings};if(e.outside){const a=new Set([...Object.keys(this.state.sims),...Object.keys(this.state.orphans),...Object.values(this.state.templates).map(n=>n.udid)]);i.outsideSims=(await this.host.listDevices()).filter(n=>!a.has(n.udid)&&n.available).map(n=>({udid:n.udid,name:n.name,state:n.state,runtime:n.runtime}))}return i}setSettings(e){const s=this.state.settings;const t={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]};if(!e||typeof e!=="object")throw new c("BAD_REQUEST","settings must be an object.");for(const[i,a]of Object.entries(e)){if(!(i in s)||typeof a!==typeof s[i])throw new c("BAD_REQUEST",`Bad setting ${i}.`);const n=t[i];if(n&&(typeof a!=="number"||!Number.isFinite(a)||a<n[0]||a>n[1])){throw new c("BAD_REQUEST",`${i} must be ${n[0]} to ${n[1]}.`)}}for(const[i,a]of Object.entries(e))s[i]=a;this.store.save();return s}};function Nt(e,s){return Math.min(s,Math.max(4,Math.round(e*.375)))}function jt(e){return Math.min(4,Math.max(1.5,e*.1))}function Pt(e,s){const t=i=>Math.round(i*10)/10;return{minFreeCores:t(Math.max(2,e.fairMinFreeShare*s)),maxSwingCores:t(Math.max(1,e.fairMaxSwingShare*s)),maxThermalLevel:e.fairMaxThermalLevel,maxSwapGrowthMB:e.fairMaxSwapGrowthMB}}function Je(e,s=Date.now()-3*6e4){const t=new Map;for(const i of e.split("\n")){const a=/^(\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(!a||a[3]==="SIGKILL"||a[3]==="SIGTERM")continue;const n=Date.parse(a[1].replace(" ","T"));if(!(n>=s))continue;const o=t.get(a[2])??[];o.push(n);t.set(a[2],o)}for(const[i,a]of t){a.sort((n,o)=>n-o);for(let n=2;n<a.length;n++)if(a[n]-a[n-2]<=6e4)return i.replace(/^com\.apple\./,"")}return null}import{promises as Ct}from"fs";import q from"os";import ge from"path";var Ke=6e4;var Ye=class{constructor(){this.disabledRoot=F;this.devicesDir=ge.join(q.homedir(),"Library/Developer/CoreSimulator/Devices");this.graphicsPids=[];this.loadCalls=0}simctl(e,s=Ke){return D("xcrun",["simctl",...e],{timeoutMs:s})}async listDevices(){const e=JSON.parse(await this.simctl(["list","devices","--json"],3e4));const s=[];for(const[t,i]of Object.entries(e.devices??{})){for(const a of i){s.push({udid:a.udid,name:a.name,state:a.state,runtime:t,deviceType:a.deviceTypeIdentifier,dataPath:a.dataPath??ge.join(this.devicesDir,a.udid,"data"),available:a.isAvailable!==false})}}return s}async create(e,s,t){return(await this.simctl(["create",e,s,t])).trim()}async clone(e,s){return(await this.simctl(["clone",e,s])).trim()}async boot(e){const s=await b("xcrun",["simctl","boot",e],{timeoutMs:Ke});if(s.code!==0&&!/current state: Booted/i.test(s.stderr)){throw new Error(`simctl boot ${e} failed: ${s.stderr.trim().slice(0,300)}`)}}async shutdown(e){const s=await b("xcrun",["simctl","shutdown",e],{timeoutMs:9e4});if(s.code!==0&&!/current state: Shutdown/i.test(s.stderr)){throw new Error(`simctl shutdown ${e} failed: ${s.stderr.trim().slice(0,300)}`)}}async delete(e){await this.simctl(["delete",e],9e4)}async install(e,s){await this.simctl(["install",e,s],18e4)}async launch(e,s){await this.simctl(["launch","--terminate-running-process",e,s],6e4)}async terminate(e,s){await b("xcrun",["simctl","terminate",e,s],{timeoutMs:2e4})}async openUrl(e,s){await this.simctl(["openurl",e,s],2e4)}async screenshot(e,s,t=1e4){const i=await b("xcrun",["simctl","io",e,"screenshot",s],{timeoutMs:t});return i.code===0&&!i.timedOut}spawn(e,s,t=2e4){return b("xcrun",["simctl","spawn",e,...s],{timeoutMs:t})}async appContainer(e,s){const t=await b("xcrun",["simctl","get_app_container",e,s,"data"],{timeoutMs:2e4});return t.code===0?t.stdout.trim()||null:null}async simTree(e){const s=await Ae();const t=s.find(i=>/launchd_sim/.test(i.command)&&i.command.includes(e));return t?Te(s,t.pid):[]}async signal(e,s){for(const t of e){try{process.kill(t,s)}catch{}}}async launchdLogTail(e,s=256*1024){const t=ge.join(this.disabledRoot,`com.apple.CoreSimulator.SimDevice.${e}`,"launchd.log");try{const i=await Ct.open(t,"r");try{const{size:a}=await i.stat();const n=Math.max(0,a-s);const o=Buffer.alloc(a-n);await i.read(o,0,o.length,n);return o.toString("utf8")}finally{await i.close()}}catch{return""}}async hostStats(){const[e,s,t,i]=await Promise.all([D("vm_stat",[],{timeoutMs:1e4}),b("sysctl",["-n","kern.memorystatus_vm_pressure_level"],{timeoutMs:5e3}),b("sysctl",["-n","vm.swapusage"],{timeoutMs:5e3}),D("df",["-k",q.homedir()],{timeoutMs:1e4})]);const a=Number(/page size of (\d+)/.exec(e)?.[1]??16384);const n=u=>Number(new RegExp(`${u}:\\s+(\\d+)`).exec(e)?.[1]??0);const o=n("Pages free")+n("Pages inactive")+n("Pages speculative")+n("Pages purgeable");const r=Number(s.stdout.trim());const l=/used = ([\d.]+)M/.exec(t.stdout)?.[1];const d=i.trim().split("\n").pop()??"";const p=Number(d.split(/\s+/)[3]??0);return{totalMemBytes:q.totalmem(),availableMemBytes:o*a,memPressure:r>=4?"critical":r>=2?"warn":"normal",swapUsedBytes:l?Number(l)*1024*1024:0,freeDiskBytes:p*1024,cpuCount:q.cpus().length,loadAvg1:q.loadavg()[0]}}async showWindow(e){const s=await b("open",["-b","com.apple.iphonesimulator","--args","-CurrentDeviceUDID",e],{timeoutMs:2e4});if(s.code===0)return;await D("open",["-b","com.apple.dt.Devices"],{timeoutMs:2e4})}async loadCounters(e,s=true){if(s&&this.loadCalls++%10===0){const T=await b("pgrep",["-x","SimMetalHost"],{timeoutMs:5e3});this.graphicsPids=T.stdout.split("\n").map(Number).filter(f=>f>0)}const t=q.cpus();let i=0;let a=0;for(const T of t){const f=T.times;const N=f.user+f.nice+f.sys+f.idle+f.irq;a+=N;i+=N-f.idle}const n=[...e,...this.graphicsPids];const[o,r,l,d]=await Promise.all([s&&n.length?b("ps",["-o","pid=,time=","-p",n.join(",")],{timeoutMs:5e3}):null,s?b("ioreg",["-r","-c","IOAccelerator","-d","1"],{timeoutMs:5e3}):null,b("sysctl",["-n","kern.memorystatus_vm_pressure_level","vm.swapusage"],{timeoutMs:5e3}),b("notifyutil",["-g","com.apple.system.thermalpressurelevel"],{timeoutMs:5e3})]);const p=new Set(this.graphicsPids);let u=0;let y=0;for(const T of o?.stdout.split("\n")??[]){const f=/^\s*(\d+)\s+(\S+)/.exec(T);if(!f)continue;const N=Fe(f[2]);if(p.has(Number(f[1])))y+=N;else u+=N}const k=r&&/"Device Utilization %"=(\d+)/.exec(r.stdout);const M=/thermalpressurelevel\s+(\d+)/.exec(d.stdout);return{hostBusyMs:i,hostTotalMs:a,cpuCount:t.length,pidsCpuMs:s?u:null,graphicsCpuMs:s?y:null,gpuPct:k?Number(k[1]):null,memPressureLevel:Number(l.stdout.trim().split("\n")[0])||1,swapUsedMB:Number(/used = ([\d.]+)M/.exec(l.stdout)?.[1]??0),thermalLevel:M?Number(M[1]):null}}async xcodeBuild(){const e=await b("xcodebuild",["-version"],{timeoutMs:2e4});return e.stdout.trim().replace(/\s+/g," ")||"unknown"}async clientsOf(e){const s=await Ae();const t=new Set((await this.simTree(e)).map(a=>a.pid));const i=new Set(Te(s,process.pid).map(a=>a.pid));return s.filter(a=>!i.has(a.pid)&&!t.has(a.pid)&&a.command.includes(e)&&!/launchd_sim|CoreSimulator/.test(a.command))}async kill(e,s){if(!await P(e,s))return;try{process.kill(e,"SIGTERM")}catch{return}for(let t=0;t<20;t++){await new Promise(i=>setTimeout(i,100));if(!await P(e,s))return}if(await P(e,s)){try{process.kill(e,"SIGKILL")}catch{}}}};import Lt from"net";import{chmodSync as Ut,rmSync as Gt,appendFileSync as Ft}from"fs";import{timingSafeEqual as Wt}from"crypto";import{promises as qt,existsSync as ye,mkdirSync as ke,readFileSync as U,renameSync as ve,writeFileSync as se,rmSync as J,openSync as Ht,fsyncSync as Qt,closeSync as zt,chmodSync as Jt,readdirSync as Kt,statSync as Xe}from"fs";import Yt from"os";import I from"path";import{createHash as Xt,randomBytes as K}from"crypto";function B(){return process.env.BUOY_FARM_HOME||I.join(Yt.homedir(),".buoy","farm")}var x=(e=B())=>({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 Se(e=B()){ke(e,{recursive:true,mode:448});try{Jt(e,448)}catch{}}function Ve(e=B()){const s=x(e).token;try{const t=U(s,"utf8").trim();if(t)return t}catch{}Se(e);try{se(s,K(24).toString("hex")+"\n",{mode:384,flag:"wx"})}catch(t){if(t.code!=="EEXIST")throw t}for(let t=0;t<20;t++){const i=U(s,"utf8").trim();if(i)return i;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,25)}throw new Error(`farm token at ${s} is empty`)}async function ie(e=B()){try{return JSON.parse(await qt.readFile(x(e).lockOwner,"utf8"))}catch{return null}}async function ae(e){return!!e&&await P(e.pid,e.start)}async function Vt(e,s){const t=I.join(e,"owner.claim");for(let i=0;i<20;i++){try{ke(t,{mode:448});se(I.join(t,"pid"),`${process.pid}
|
|
5
|
-
${await z(process.pid)??""}`);try{return await s()}finally{J(t,{recursive:true,force:true})}}catch(a){if(a.code!=="EEXIST")throw a;const n=Xe(t,{throwIfNoEntry:false});if(n&&Date.now()-n.mtimeMs>3e4){let o=true;try{const[r,l]=U(I.join(t,"pid"),"utf8").split("\n");o=!await P(Number(r),l||void 0)}catch{}if(o){try{ve(t,I.join(e,`owner.claim-dead-${K(4).toString("hex")}`))}catch{}}}await new Promise(o=>setTimeout(o,100))}}return null}async function Zt(e,s,t=B()){Se(t);const i=x(t);const a=await z(process.pid)??"";for(let n=0;n<3;n++){try{ke(i.lock,{mode:448})}catch(r){if(r.code!=="EEXIST")throw r;const l=await ie(t);if(l&&await ae(l))return null;if(!l){await new Promise(u=>setTimeout(u,300));const p=await ie(t);if(p&&await ae(p))return null}const d=await Vt(t,async()=>{const p=await ie(t);if(p&&await ae(p))return false;if(!ye(i.lock))return true;ve(i.lock,I.join(t,`owner.stale-${K(4).toString("hex")}`));return true});if(d===false)return null;continue}const o={pid:process.pid,start:a,version:e,host:s,since:new Date().toISOString()};se(i.lockOwner,JSON.stringify(o),{mode:384});es(t);return{info:o,release(){try{const r=JSON.parse(U(i.lockOwner,"utf8"));if(r.pid!==process.pid)return}catch{return}try{J(i.socket,{force:true})}catch{}J(i.lock,{recursive:true,force:true})}}}return null}function es(e){try{for(const s of Kt(e)){if(s.startsWith("owner.stale-")||s.startsWith("owner.claim-dead-"))J(I.join(e,s),{recursive:true,force:true})}}catch{}}function Ze(){return{version:V,seq:0,farmId:K(6).toString("hex"),nextIndex:1,templates:{},sims:{},orphans:{},leases:{},runs:{},locks:{},requests:{},events:[],settings:{...le}}}var et=e=>Xt("sha256").update(JSON.stringify(e)).digest("hex").slice(0,12);function tt(e,s,t){const i=Ht(e,t,384);try{se(i,s);Qt(i)}finally{zt(i)}}var st=class{constructor(e=B()){this.fenced=false;this.pending=[];this.open=new Map;this.p=x(e);Se(e);this.state=this.load()}get shotsPath(){return this.p.shots}fence(){this.fenced=true}assertActive(){if(this.fenced)throw new Error("This farm has stopped.")}load(){let e=Ze();if(ye(this.p.state)){const a=JSON.parse(U(this.p.state,"utf8"));if(a.version>V){throw new Error(`farm state is version ${a.version}; this farmd understands ${V}. Update Buoy.`)}e={...Ze(),...a,settings:{...le,...a.settings}}}delete e.settings.activeSlots;const s=new Map;const t=new Set;let i=e.seq;if(ye(this.p.journal)){for(const a of U(this.p.journal,"utf8").split("\n")){if(!a.trim())continue;let n;try{n=JSON.parse(a)}catch{continue}const{sum:o,...r}=n;if(et(r)!==o||n.seq<=e.seq)continue;i=Math.max(i,n.seq);if(n.type==="intent")s.set(n.seq,n);else{s.delete(n.seq);t.add(n.seq)}}}for(const a of e.openIntents??[]){if(!t.has(a.seq)&&!s.has(a.seq))s.set(a.seq,{...a,type:"intent",sum:""})}this.pending=[...s.values()].sort((a,n)=>a.seq-n.seq);for(const a of this.pending)this.open.set(a.seq,a);e.seq=i;e.openIntents=[];return e}intent(e,s){this.assertActive();const t=++this.state.seq;const i={seq:t,at:new Date().toISOString(),type:"intent",op:e,data:s};this.append(i);this.open.set(t,{...i,sum:""});return t}result(e,s,t){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:s,data:t})}append(e){this.assertActive();tt(this.p.journal,JSON.stringify({...e,sum:et(e)})+"\n","a")}save(){this.assertActive();const e=`${this.p.state}.${K(4).toString("hex")}.tmp`;this.state.openIntents=[...this.open.values()].map(({seq:s,at:t,op:i,data:a})=>({seq:s,at:t,op:i,data:a}));tt(e,JSON.stringify(this.state,null,1),"w");ve(e,this.p.state);try{if(Xe(this.p.journal).size>2*1024*1024)J(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 ts(e=B()){try{const s=JSON.parse(U(x(e).state,"utf8"));return new Set([...Object.keys(s.sims??{}),...Object.keys(s.orphans??{}),...Object.values(s.templates??{}).map(t=>t.udid)])}catch{return new Set}}var it=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 ss(e={}){const s=e.home??B();const t=x(s);const i=await Zt($,e.hostLabel??"node",s);if(!i)return null;const a=n=>{try{Ft(t.log,`${new Date().toISOString()} ${n}
|
|
6
|
-
`)}catch{}e.log?.(n)};try{return await is(e,s,i,a)}catch(n){i.release();throw n}}async function is(e,s,t,i){const a=x(s);const n=Ve(s);const o=new st(s);const r=new ze({host:e.host??new Ye,store:o,log:i,pollMs:e.pollMs,readyTimeoutMs:e.readyTimeoutMs});await r.recover();Gt(a.socket,{force:true});const l=new Set;let d=Date.now();let p=false;let u=0;const y=Lt.createServer(w=>{l.add(w);w.on("close",()=>l.delete(w));w.on("error",()=>{});let h="";w.on("data",m=>{h+=m.toString("utf8");if(h.length>1024*1024)return w.destroy();let g;while((g=h.indexOf("\n"))>=0){const S=h.slice(0,g);h=h.slice(g+1);if(S.trim())void M(w,S)}})});const k=(w,h,m)=>{if(!w.destroyed)w.write(JSON.stringify({id:h,...m})+"\n")};async function M(w,h){d=Date.now();let m;try{m=JSON.parse(h)}catch{return k(w,-1,{ok:false,error:{code:"BAD_REQUEST",message:"Not JSON."}})}u++;try{if(!m||typeof m!=="object")throw new c("BAD_REQUEST","Use a request object.");if(!as(m.token,n))throw new c("UNAUTHORIZED","Bad farm token.");if(!m.client||typeof m.client.clientId!=="string"||!m.client.clientId.trim())throw new c("BAD_REQUEST","Each call needs a clientId.");if(p&&!["hello","status"].includes(m.method)){throw new c("HOST_BUSY","The farm is stopping.","Try again in a few seconds.",true)}if(m.v>$&&it.has(m.method)){throw new c("FARMD_VERSION",`This farm runs protocol ${$}; the client speaks ${m.v}.`,"Ask it to stop when idle (method shutdown), then start a newer farm.")}if(m.v<$&&it.has(m.method)){throw new c("READ_ONLY",`This client speaks farm protocol ${m.v}; the farm runs ${$}.`,"Update Buoy to change the farm.")}ns(m);const g=await T(m);k(w,m.id,{ok:true,result:g})}catch(g){const S=g instanceof c?g.toJSON():{code:"HOST_ERROR",message:g.message,retryable:false};k(w,m?.id??-1,{ok:false,error:S})}finally{u--}}async function T(w){const h=w.params??{};const m=w.client;const g=S=>r.once(m.clientId,w.requestId,{m:w.method,q:h},S);switch(w.method){case"hello":return{protocol:$,pid:process.pid,host:t.info.host,farmId:r.state.farmId};case"status":return r.status({outside:h.outside});case"budget":return r.budget({ramGB:h.ramGB??0,diskGB:h.diskGB??0});case"setup":return g(()=>r.createTemplate(h));case"grow":return g(async()=>(await r.grow(h.templateId,h.count)).map(S=>S.name));case"shrink":return g(()=>r.shrink(h.templateId,h.count));case"removeTemplate":return g(()=>r.removeTemplate(h.templateId));case"acquire":return g(async()=>{const{lease:S,run:A,sims:re}=await r.acquire({...h,owner:m});return{runId:A.id,leaseId:S.id,epoch:S.epoch,deadlineAt:A.deadlineAt,capAt:A.capAt,sims:re.map(X=>({udid:X.udid,name:X.name,buoyDeviceId:X.buoyDeviceId,profile:X.profile}))}});case"renew":return r.renew(h.leaseId,h.epoch,m.clientId);case"release":return g(()=>r.release(h.leaseId,h.epoch,h.reset,m.clientId,h.keepRun===true));case"action":return g(()=>r.action(h.leaseId,h.epoch,h.udids,h.action,h.arg,m.clientId));case"lock":return r.lock(h.name,h.leaseId,h.epoch,h.waitMs??0,m.clientId);case"unlock":return r.unlock(h.name,h.leaseId,h.epoch,m.clientId);case"fairness":return r.fairness(h.leaseId,h.epoch,h.windows,m.clientId);case"park":return g(async()=>(await r.parkSim(h.udid,"asked"),"parked"));case"wake":return g(async()=>(await r.bootSim(h.udid)).power);case"reset":return g(async()=>(await r.fastReset(h.udid),"reset"));case"rebuild":return g(async()=>(await r.rebuildSim(h.udid)).udid);case"delete":return g(async()=>(await r.deleteSim(h.udid),"deleted"));case"endLease":{const S=r.state.leases[h.leaseId];if(!S)return{};if(S.owner.clientId!==m.clientId&&h.force!==true){throw new c("NOT_OWNED",`Lease ${h.leaseId} belongs to ${S.owner.label}.`,"Pass force: true only after a person confirms.")}return g(()=>r.endLease(h.leaseId,`ended by ${m.label}`,"full",S.owner.clientId===m.clientId?"released":"user"))}case"report":return g(async()=>r.report(h,m.clientId));case"extend":return g(async()=>r.extend(h.runId,h.minutes,m.clientId));case"finish":return g(async()=>r.finish(h.runId,h.outcome,h.summary,m.clientId));case"stopRun":{const S=r.state.runs[h.runId];if(S&&S.owner.clientId!==m.clientId&&h.force!==true){throw new c("NOT_OWNED",`Run ${h.runId} belongs to ${S.owner.label}.`,"Pass force: true only after a person confirms.")}return g(async()=>r.stopRun(h.runId))}case"stopAll":if(h.force!==true)throw new c("BAD_REQUEST","stopAll needs force: true, after a person confirms.");return g(async()=>r.stopAll());case"settings":return r.setSettings(h);case"shot":return r.shot(h.udid);case"show":return r.show(h.udid);case"shutdown":if(h.ifIdle!==false&&(!r.idle()||u>1))return{stopping:false,reason:"busy"};void oe("asked to stop");return{stopping:true};default:throw new c("BAD_REQUEST",`Unknown method ${w.method}.`)}}await new Promise((w,h)=>{y.once("error",h);y.listen(a.socket,()=>w())});Ut(a.socket,384);i(`farmd ${$} up, pid ${process.pid}, host ${e.hostLabel??"node"}`);const f=setInterval(()=>{void r.tick().catch(h=>i(`tick: ${h.message}`));const w=Date.now()-d;if(e.idleExitMs&&l.size===0&&w>e.idleExitMs){void(async()=>{await r.parkAllIdle();if(r.idle()&&l.size===0&&u===0&&Date.now()-d>e.idleExitMs)await oe("idle")})()}},e.tickMs??5e3);f.unref?.();const N=setInterval(()=>void r.meter.tick().catch(w=>i(`meter: ${w.message}`)),1e3);N.unref?.();let ne=null;function oe(w,h=10*6e4){if(!ne)ne=ct(w,h);return ne}async function ct(w,h){if(p)return;p=true;clearInterval(f);clearInterval(N);i(`farmd stopping: ${w}`);const m=Date.now()+h;while(u>0&&Date.now()<m)await new Promise(A=>setTimeout(A,200));const g=await r.drain(Math.max(0,m-Date.now()));let S=false;if(u>0||!g||he()>0){o.save();r.fence();S=true;i(`farmd stopping with ${u} call(s) and ${he()} child process(es) still running; killing them`);wt();const A=Date.now()+5e3;while(he()>0&&Date.now()<A)await new Promise(re=>setTimeout(re,100))}await r.settleFiles();if(!S&&u===0){try{await r.parkAllIdle()}catch{}}for(const A of l)A.destroy();await new Promise(A=>y.close(()=>A()));if(!S){o.save();r.fence()}t.release();e.onExit?.(w)}return{engine:r,owner:t,stop:oe}}function as(e,s){if(typeof e!=="string"||e.length!==s.length)return false;return Wt(Buffer.from(e),Buffer.from(s))}function ns(e){const s=o=>{throw new c("BAD_REQUEST",o)};const t=e.params??{};if(typeof t!=="object"||Array.isArray(t))s("Params must be an object.");const i=(o,r=false)=>{if(r&&t[o]===void 0)return;if(typeof t[o]!=="string"||!t[o].trim())s(`${o} must be text.`)};const a=o=>{if(t[o]!==void 0&&typeof t[o]!=="boolean")s(`${o} must be true or false.`)};const n=(o,r,l,d=false,p=false)=>{const u=t[o];if(p&&u===void 0)return;if(typeof u!=="number"||!Number.isFinite(u)||u<r||u>l||d&&!Number.isInteger(u))s(`${o} must be a number from ${r} to ${l}.`)};switch(e.method){case"status":a("outside");break;case"budget":n("ramGB",0,Number.MAX_VALUE,false,true);n("diskGB",0,Number.MAX_VALUE,false,true);break;case"setup":for(const o of["appPath","deviceType","runtime"])i(o);if(t.seedDir!=null)i("seedDir");i("name",true);i("launchUrl",true);a("warmLaunch");if(!["slim","stock"].includes(t.profile))s("Use slim or stock.");n("metroPort",1,65535,true);break;case"removeTemplate":i("templateId");break;case"grow":case"shrink":case"acquire":i("templateId");n("count",1,64,true);if(e.method==="acquire"){i("purpose");n("ttlMs",1,Number.MAX_VALUE,false,true);n("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);a("launch");a("allowPartial");i("goal",true);i("runId",true);i("kind",true);if(t.labels!==void 0&&(!Array.isArray(t.labels)||t.labels.some(o=>typeof o!=="string")))s("labels must be a list of text.");if(!Number.isInteger(e.client.pid)||e.client.pid<=0)s("The client needs a valid pid.")}break;case"release":if(t.reset!==void 0&&!["fast","full"].includes(t.reset))s("Use fast or full reset.");break;case"action":if(!Array.isArray(t.udids)||t.udids.some(o=>typeof o!=="string"||!o))s("udids must be a list of sim ids.");if(!["reset","rebuild","open-url","screenshot","launch"].includes(t.action))s("Unknown sim action.");i("arg",t.action!=="open-url");break;case"lock":case"unlock":i("name");if(!/^[a-z0-9-]{1,40}$/.test(t.name))s("Lock names are short lowercase words.");n("waitMs",0,Number.MAX_SAFE_INTEGER,false,true);break;case"fairness":if(!Array.isArray(t.windows)||t.windows.length>500||t.windows.some(o=>!o||typeof o.id!=="string"||!Number.isFinite(o.from)||!Number.isFinite(o.to)||o.to<o.from)){s("windows must be a list of { id, from, to } times in ms.")}break;case"endLease":i("leaseId");break;case"report":i("runId");i("udid",true);i("note",true);n("sequence",0,Number.MAX_SAFE_INTEGER,true,true);if(t.state!==void 0&&typeof t.state!=="string")s("state must be text.");break;case"extend":i("runId");n("minutes",1,24*60);break;case"finish":i("runId");i("summary",true);i("outcome",true);break;case"stopRun":i("runId");a("force");break;case"stopAll":a("force");break;case"shutdown":a("ifIdle");break}if(["renew","release","action","lock","unlock","fairness"].includes(e.method))i("leaseId")}import os from"net";import{existsSync as rs}from"fs";import{spawn as ls}from"child_process";import{randomBytes as at}from"crypto";function cs(e,s=["farmd"]){return()=>{const t=ls(process.execPath,[...process.execArgv,e,...s],{detached:true,stdio:"ignore",env:{...process.env}});t.unref()}}var nt=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??B();this.clientIdValue=`c-${at(5).toString("hex")}`}get clientId(){return this.owner?.clientId}async connect(){this.token=Ve(this.home);this.owner={clientId:this.clientIdValue,pid:process.pid,pidStart:await z(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 ie(this.home);if(!await ae(e)){if(!this.opts.launcher)throw new c("HOST_ERROR","The farm is not running.","Start it from Buoy Desktop or `pnpm farm status`.");this.opts.launcher()}const s=Date.now()+(this.opts.startTimeoutMs??2e4);while(Date.now()<s){await _(250);if(await this.tryOpen())return this.checkVersion()}throw new c("HOST_ERROR","The farm did not start in time.",`Check ${x(this.home).log}.`)}async checkVersion(){const e=await this.send("hello",void 0,{});if(e.protocol<$&&this.opts.launcher){const s=await this.send("shutdown",{ifIdle:true},{});if(s.stopping){this.close();await _(500);return this.connect()}}return this}tryOpen(){const e=x(this.home).socket;if(!rs(e))return Promise.resolve(false);return new Promise(s=>{const t=os.createConnection(e);t.once("connect",()=>{this.sock=t;t.on("data",i=>this.onData(i));t.on("close",()=>this.onClose());t.on("error",()=>{});s(true)});t.once("error",()=>s(false))})}onData(e){this.buf+=e.toString("utf8");let s;while((s=this.buf.indexOf("\n"))>=0){const t=this.buf.slice(0,s);this.buf=this.buf.slice(s+1);let i;try{i=JSON.parse(t)}catch{continue}const a=this.pending.get(i.id);if(!a)continue;this.pending.delete(i.id);clearTimeout(a.timer);if(i.ok)a.resolve(i.result);else{const n=i.error??{};a.reject(new c(n.code??"HOST_ERROR",n.message??"farm error",n.next,n.retryable))}}}onClose(){this.sock=null;for(const[,e]of this.pending){clearTimeout(e.timer);e.reject(new c("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,s,t={}){if(!this.sock&&!this.closed)await this.reconnect();return this.send(e,s,t)}send(e,s,t){if(!this.sock)return Promise.reject(new c("HOST_ERROR","Not connected to the farm.",void 0,true));const i=this.nextId++;const a={id:i,v:$,token:this.token,client:this.owner,method:e,params:s,requestId:t.requestId};return new Promise((n,o)=>{const r=setTimeout(()=>{this.pending.delete(i);o(new c("HOST_ERROR",`Farm call ${e} timed out.`,void 0,true))},t.timeoutMs??3e4);this.pending.set(i,{resolve:n,reject:o,timer:r});this.sock.write(JSON.stringify(a)+"\n")})}async acquire(e){const{requestId:s,...t}=e;const i=s??`acq-${at(4).toString("hex")}`;const a=await this.call("acquire",t,{timeoutMs:15*6e4+(e.waitMs??0),requestId:i});if(this.renewals.has(a.leaseId))return a;const n=setInterval(()=>{this.call("renew",{leaseId:a.leaseId,epoch:a.epoch}).catch(o=>{if(["LEASE_EXPIRED","STALE_EPOCH","RUN_STOPPED","RUN_ENDED"].includes(o.code))this.stopRenew(a.leaseId)})},15e3);n.unref?.();this.renewals.set(a.leaseId,n);return a}stopRenew(e){const s=this.renewals.get(e);if(s)clearInterval(s);this.renewals.delete(e)}async release(e,s,t="fast",i=false){this.stopRenew(e);return this.call("release",{leaseId:e,epoch:s,reset:t,keepRun:i},{timeoutMs:6e4,requestId:`rel-${e}-${s}`})}close(){this.closed=true;for(const e of[...this.renewals.keys()])this.stopRenew(e);this.sock?.end();this.sock=null}};async function ds(e){return new nt(e).connect()}import Y from"path";import hs from"os";import{existsSync as be,readdirSync as us,statSync as ps}from"fs";function Me(e=process.cwd()){let s=e;for(;;){if(be(Y.join(s,"buoy.tools.json")))return s;const t=Y.dirname(s);if(t===s)return null;s=t}}function ot(){const e=Y.join(hs.homedir(),"Library/Developer/Xcode/DerivedData");let s=null;try{for(const t of us(e)){if(!t.startsWith("exampledevbuild-"))continue;const i=Y.join(e,t,"Build/Products/Debug-iphonesimulator/exampledevbuild.app");if(!be(i))continue;const a=ps(i).mtimeMs;if(!s||a>s.t)s={p:i,t:a}}}catch{}return s?.p??null}function rt(e=Me()){return e?Y.join(e,"example-dev-build/artifacts/pool-seed"):null}async function ms(e=Me()){const s=rt(e);let t=[];let i=[];try{const a=JSON.parse(await D("xcrun",["simctl","list","runtimes","--json"],{timeoutMs:3e4}));t=(a.runtimes??[]).filter(o=>o.isAvailable!==false&&/^iOS/.test(o.name??"")).map(o=>({id:o.identifier,name:o.name}));const n=JSON.parse(await D("xcrun",["simctl","list","devicetypes","--json"],{timeoutMs:3e4}));i=(n.devicetypes??[]).filter(o=>/iPhone|iPad/.test(o.name??"")).map(o=>({id:o.identifier,name:o.name}))}catch{}return{appPath:ot(),seedDir:s&&be(s)?s:null,seedProblem:s?ue(s):"Could not find the repo's seed folder.",runtimes:t,deviceTypes:i,slimTurnsOff:ce.map(a=>({name:a.name,downside:a.downside}))}}import fs from"path";var lt=[{app:"claude",name:"Claude",match:/(^|\/)claude$/i},{app:"codex",name:"Codex",match:/(^|\/)codex$/i},{app:"cursor",name:"Cursor",match:/cursor/i}];async function ws(e){const s=await b("ps",["-o","ppid=,comm=","-p",String(e)],{timeoutMs:3e3});const t=/^\s*(\d+)\s+(.+?)\s*$/.exec(s.stdout);return s.code===0&&t?{ppid:Number(t[1]),comm:t[2]}:null}async function gs(e){const s=await b("lsof",["-a","-p",String(e),"-d","cwd","-Fn"],{timeoutMs:3e3});const t=s.stdout.split("\n").find(i=>i.startsWith("n"));return t?t.slice(1):null}function ys(e){if(!e)return void 0;return lt.find(s=>e.toLowerCase().includes(s.app))}async function ks(e={}){const s=e.env??process.env;let t=e.pid??process.pid;let i=null;for(let p=0;p<5&&t>1;p++){const u=await ws(t).catch(()=>null);if(!u)break;const y=lt.find(k=>k.match.test(u.comm));if(p>0&&y){i={app:y,pid:t};break}t=u.ppid}const a=i?.app??ys(e.clientName);const n=(i?await gs(i.pid).catch(()=>null):null)??process.cwd();const o=(await b("git",["-C",n,"rev-parse","--abbrev-ref","HEAD"],{timeoutMs:3e3}).catch(()=>null))?.stdout.trim();const r=s.CLAUDE_CODE_SESSION_ID?.trim();let l;let d="none";if(r&&(a?.app??"claude")==="claude"){l=`claude:${r}`;d="claude-session"}else if(i){l=`${i.app.app}:${i.pid}:${await z(i.pid)??""}`;d="parent-process"}return{app:a?.app??"other",appName:a?.name??(e.clientName||"Agent"),chatKey:l,chatKeySource:d,project:fs.basename(n)||void 0,projectPath:n,branch:o&&o!=="HEAD"?o:void 0,clientName:e.clientName}}function vs(e){return e.project?`${e.appName} \xB7 ${e.project}`:e.appName}export{$ as PROTOCOL_VERSION,V as STATE_VERSION,le as DEFAULT_SETTINGS,c as FarmError,Ie as ALWAYS_ENABLED,ce as SLIM_CATEGORIES,Ee as slimLabels,H as profileLabels,Oe as profileHash,ee as findDataContainer,Le as readBuoyInstallId,Ge as LoadMeter,Fe as parseCpuTime,ze as Engine,Je as crashLoop,Ye as SimctlHost,B as farmHome,x as paths,st as Store,ts as farmOwnedUdids,ss as startFarmd,cs as nodeLauncher,nt as FarmClient,ds as connectFarm,Me as findRepoRoot,ot as findExampleBuild,rt as defaultSeedDir,ms as farmDefaults,ks as detectAgent,vs as agentLabel};
|
package/dist/chunk-OPHPYPJD.mjs
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import{createRequire as Ze}from"module";const vt=Ze(import.meta.url);import{createRequire as et}from"module";import{execFileSync as Se}from"child_process";import{join as de}from"path";import{closeSync as ke,linkSync as ve,mkdirSync as tt,openSync as xe,readFileSync as nt,renameSync as st,statSync as Ee,unlinkSync as Y,writeFileSync as Te,writeSync as rt}from"fs";import{tmpdir as at}from"os";import{pathToFileURL as ot}from"url";var it=`(() => {
|
|
2
|
-
const supported = (t) => {
|
|
3
|
-
try { return (PerformanceObserver.supportedEntryTypes || []).includes(t); } catch { return false; }
|
|
4
|
-
};
|
|
5
|
-
const frames = [], longTasks = [], loaf = [], shifts = [], events = [], observers = [];
|
|
6
|
-
const watch = (type, cb, extra) => {
|
|
7
|
-
if (!supported(type)) return false;
|
|
8
|
-
const o = new PerformanceObserver((l) => l.getEntries().forEach(cb));
|
|
9
|
-
o.observe(Object.assign({ type, buffered: false }, extra || {}));
|
|
10
|
-
observers.push([o, cb]);
|
|
11
|
-
return true;
|
|
12
|
-
};
|
|
13
|
-
const hasLong = watch("longtask", (e) => longTasks.push({ start: e.startTime, end: e.startTime + e.duration }));
|
|
14
|
-
const hasLoaf = watch("long-animation-frame", (e) => loaf.push({
|
|
15
|
-
startTime: e.startTime,
|
|
16
|
-
duration: e.duration,
|
|
17
|
-
blockingDuration: e.blockingDuration || 0,
|
|
18
|
-
styleAndLayoutStart: e.styleAndLayoutStart || 0,
|
|
19
|
-
scripts: (e.scripts || []).map((s) => {
|
|
20
|
-
const file = s.sourceURL ? s.sourceURL.split("?")[0].split("/").pop() : "";
|
|
21
|
-
const where = s.sourceFunctionName ? s.sourceFunctionName + (file ? " @ " + file : "") : file;
|
|
22
|
-
// Invokers like "FrameRequestCallback" say how a script ran, not which
|
|
23
|
-
// one; add the function and file when the browser gives them.
|
|
24
|
-
const name = s.invoker ? (where ? s.invoker + " \u2192 " + where : s.invoker) : where || "(anonymous)";
|
|
25
|
-
return { duration: s.duration, name, forced: s.forcedStyleAndLayoutDuration || 0 };
|
|
26
|
-
}),
|
|
27
|
-
}));
|
|
28
|
-
// 16 ms is Event Timing's smallest threshold; the default (104) hides most taps.
|
|
29
|
-
const hasEvents = watch("event", (e) => {
|
|
30
|
-
if (e.interactionId) events.push({
|
|
31
|
-
name: e.name, startTime: e.startTime, duration: e.duration,
|
|
32
|
-
processingStart: e.processingStart, processingEnd: e.processingEnd, interactionId: e.interactionId,
|
|
33
|
-
});
|
|
34
|
-
}, { durationThreshold: 16 });
|
|
35
|
-
// Click proof: which step selectors each real click landed inside, read
|
|
36
|
-
// at the moment of the click (capture phase, before the page's handlers).
|
|
37
|
-
const clickTargets = Array.isArray(window.__buoyBenchClickTargets) ? window.__buoyBenchClickTargets : [];
|
|
38
|
-
const clicks = [], keys = [];
|
|
39
|
-
// Only real (trusted) input counts as proof; a click the page fakes in
|
|
40
|
-
// script doesn't. Input after the run's end doesn't count either.
|
|
41
|
-
const onClick = (e) => {
|
|
42
|
-
if (!e.isTrusted || ended) return;
|
|
43
|
-
const t = e.target;
|
|
44
|
-
const hit = clickTargets.filter((sel) => { try { return t && t.closest && t.closest(sel); } catch { return false; } });
|
|
45
|
-
clicks.push({ t: performance.now(), hit });
|
|
46
|
-
};
|
|
47
|
-
const onKey = (e) => {
|
|
48
|
-
if (!e.isTrusted || ended) return;
|
|
49
|
-
keys.push({ t: performance.now(), key: e.key, ctrl: e.ctrlKey, shift: e.shiftKey, alt: e.altKey, meta: e.metaKey });
|
|
50
|
-
};
|
|
51
|
-
// Expect steps, judged inside end() so no page work can slip in between
|
|
52
|
-
// the end of the run and the check.
|
|
53
|
-
const expects = Array.isArray(window.__buoyBenchExpects) ? window.__buoyBenchExpects : [];
|
|
54
|
-
let expectResults = null;
|
|
55
|
-
document.addEventListener("click", onClick, true);
|
|
56
|
-
document.addEventListener("keydown", onKey, true);
|
|
57
|
-
const hasShift = watch("layout-shift", (e) => shifts.push({ startTime: e.startTime, value: e.value, hadRecentInput: e.hadRecentInput }));
|
|
58
|
-
let hidden = document.visibilityState === "hidden";
|
|
59
|
-
const onVis = () => { if (document.visibilityState === "hidden") hidden = true; };
|
|
60
|
-
document.addEventListener("visibilitychange", onVis);
|
|
61
|
-
// Latched: a resize or zoom that's undone before stop still changed the work.
|
|
62
|
-
let resized = false;
|
|
63
|
-
const onResize = () => { resized = true; };
|
|
64
|
-
window.addEventListener("resize", onResize);
|
|
65
|
-
// A pixel-ratio change (moving to another screen) may not fire resize.
|
|
66
|
-
let dprQuery = null;
|
|
67
|
-
try {
|
|
68
|
-
dprQuery = matchMedia("(resolution: " + window.devicePixelRatio + "dppx)");
|
|
69
|
-
dprQuery.addEventListener("change", onResize);
|
|
70
|
-
} catch {}
|
|
71
|
-
let raf = 0;
|
|
72
|
-
const tick = (t) => { frames.push(t); raf = requestAnimationFrame(tick); };
|
|
73
|
-
raf = requestAnimationFrame(tick);
|
|
74
|
-
const start = performance.now();
|
|
75
|
-
// Trace markers: the driver counts Chrome's frames between these two.
|
|
76
|
-
try { performance.mark("__buoy_bench_start"); } catch {}
|
|
77
|
-
const domStart = document.getElementsByTagName("*").length;
|
|
78
|
-
let ended = 0;
|
|
79
|
-
window.__buoyBench = {
|
|
80
|
-
// Two steps so the driver can read Chrome's counters between them:
|
|
81
|
-
// end() freezes the end time at once; collect() then waits up to two
|
|
82
|
-
// frames (capped at 300 ms) so an input's entry, which only arrives after
|
|
83
|
-
// its paint, isn't lost. Entries after the end are filtered out later.
|
|
84
|
-
end: () => {
|
|
85
|
-
if (ended) return ended;
|
|
86
|
-
ended = performance.now();
|
|
87
|
-
try { performance.mark("__buoy_bench_end"); } catch {}
|
|
88
|
-
cancelAnimationFrame(raf);
|
|
89
|
-
expectResults = expects.map((x) => {
|
|
90
|
-
const n = document.querySelector(x.selector);
|
|
91
|
-
const full = n ? (n.textContent || "") : null;
|
|
92
|
-
// Match against the full text here; only a short snippet goes back.
|
|
93
|
-
return { selector: x.selector, text: x.text == null ? null : x.text, found: !!n, matched: full != null && (x.text == null || full.includes(x.text)), content: full == null ? null : full.slice(0, 200) };
|
|
94
|
-
});
|
|
95
|
-
return ended;
|
|
96
|
-
},
|
|
97
|
-
collect: () => new Promise((resolve) => {
|
|
98
|
-
const end = window.__buoyBench.end();
|
|
99
|
-
let done = false;
|
|
100
|
-
const finish = () => {
|
|
101
|
-
if (done) return;
|
|
102
|
-
done = true;
|
|
103
|
-
document.removeEventListener("visibilitychange", onVis);
|
|
104
|
-
window.removeEventListener("resize", onResize);
|
|
105
|
-
document.removeEventListener("click", onClick, true);
|
|
106
|
-
document.removeEventListener("keydown", onKey, true);
|
|
107
|
-
try { dprQuery && dprQuery.removeEventListener("change", onResize); } catch {}
|
|
108
|
-
for (const [o, cb] of observers) { o.takeRecords().forEach(cb); o.disconnect(); }
|
|
109
|
-
resolve({
|
|
110
|
-
start, end, frames,
|
|
111
|
-
longTasks: hasLong ? longTasks : null,
|
|
112
|
-
loaf: hasLoaf ? loaf : null,
|
|
113
|
-
shifts: hasShift ? shifts : null,
|
|
114
|
-
events: hasEvents ? events : null,
|
|
115
|
-
clicks,
|
|
116
|
-
keys,
|
|
117
|
-
expectResults,
|
|
118
|
-
domStart,
|
|
119
|
-
domEnd: document.getElementsByTagName("*").length,
|
|
120
|
-
hidden,
|
|
121
|
-
resized,
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
requestAnimationFrame(() => requestAnimationFrame(finish));
|
|
125
|
-
setTimeout(finish, 300);
|
|
126
|
-
}),
|
|
127
|
-
};
|
|
128
|
-
return true;
|
|
129
|
-
})()`;var lt=`(() => (window.__buoyBench ? window.__buoyBench.end() : null))()`;var ct=`(() => (window.__buoyBench ? window.__buoyBench.collect() : null))()`;var ut=`new Promise((resolve) => {
|
|
130
|
-
let n = 0, t0 = 0;
|
|
131
|
-
const tick = (t) => {
|
|
132
|
-
if (!t0) t0 = t;
|
|
133
|
-
n++;
|
|
134
|
-
if (t - t0 >= 1000) resolve(((n - 1) / (t - t0)) * 1000);
|
|
135
|
-
else requestAnimationFrame(tick);
|
|
136
|
-
};
|
|
137
|
-
requestAnimationFrame(tick);
|
|
138
|
-
})`;function Pe(e,r,t=[]){const a=e.median;const s=[];const n=[];const i=(u,l,d)=>{if(l==null)return;if(d==null)n.push(u);else if(d>l)s.push(`${u} ${d} > ${l}`)};i("droppedPct",r.droppedPct,e.droppedMin!=null?a?.droppedPct:null);const o=e.runs.filter(u=>!u.failed&&u.fps!=null);const c=t.some(u=>"click"in u||"press"in u||"type"in u);const p=o.map(u=>u.interactions==null||!u.inputCaptureComplete?null:u.worstInteractionMs??16);i("inputMs",r.inputMs,c&&p.length&&p.every(u=>u!=null)?Math.max(...p):null);const h=o.map(u=>Math.max(u.frameGapMaxMs??0,u.edgeSilenceMs??0));i("freezeMs",r.freezeMs,h.length?Math.max(...h):null);i("mainThreadPct",r.mainThreadPct,a?.mainThreadPct);i("jsHeapMb",r.jsHeapMb,a?.heapMb);return{meets:Boolean(a)&&s.length===0&&n.length===0,over:s,unknown:n}}var x=e=>Math.round(e*10)/10;var Re=`(() => {
|
|
139
|
-
try {
|
|
140
|
-
const gl = document.createElement("canvas").getContext("webgl2") || document.createElement("canvas").getContext("webgl");
|
|
141
|
-
if (!gl) return null;
|
|
142
|
-
const ext = gl.getExtension("WEBGL_debug_renderer_info");
|
|
143
|
-
return String(gl.getParameter(ext ? ext.UNMASKED_RENDERER_WEBGL : gl.RENDERER));
|
|
144
|
-
} catch { return null; }
|
|
145
|
-
})()`;function fe(e){return/swiftshader|llvmpipe|softpipe|software|microsoft basic render/i.test(e)}function _e(e,r){if(e.length===0)return null;const t=[...e].sort((s,n)=>s-n);const a=Math.ceil(r/100*t.length)-1;return t[Math.min(t.length-1,Math.max(0,a))]}var dt=[30,60,75,90,120,144,165,240];function Ce(e){for(const r of dt)if(Math.abs(e-r)/r<=.08)return r;return Math.max(1,Math.round(e))}function he(e,r){let t=r>>>0;const a=()=>{t=t+1831565813>>>0;let n=t;n=Math.imul(n^n>>>15,n|1);n^=n+Math.imul(n^n>>>7,n|61);return((n^n>>>14)>>>0)/4294967296};const s=[...e];for(let n=s.length-1;n>0;n--){const i=Math.floor(a()*(n+1));[s[n],s[i]]=[s[i],s[n]]}return s}function Ae(e,r,t){const a=new URL(e);const s=r.path??t;if(s)a.pathname=s.startsWith("/")?s:`${a.pathname.replace(/\/$/,"")}/${s}`;for(const[n,i]of Object.entries(r.params??{}))a.searchParams.set(n,i);return a.toString()}var ft=["STATE_PRESENTED_ALL","STATE_PRESENTED_PARTIAL","STATE_DROPPED"];function Le(e){const r=d=>e.find(M=>M.name===d&&M.ts!=null);const t=r("__buoy_bench_start");const a=r("__buoy_bench_end");if(!t||!a||a.ts<=t.ts)return null;if(t.pid!==a.pid)return null;const s=t.pid;const n={STATE_PRESENTED_ALL:0,STATE_PRESENTED_PARTIAL:1,STATE_DROPPED:2};const i=new Map;for(const d of e){if(d.name!=="PipelineReporter"||d.ph!=="b")continue;if(s!=null&&d.pid!==s)continue;if(d.ts==null||d.ts<t.ts||d.ts>a.ts)continue;const M=d.args?.frame_reporter;if(!M||M.frame_sequence==null)continue;const S=M.state;if(!ft.includes(S))continue;const T=`${M.layer_tree_host_id??""}:${M.frame_source??""}:${M.frame_sequence}`;const b=Boolean(M.affects_smoothness)&&S!=="STATE_PRESENTED_ALL";const A=i.get(T);if(!A)i.set(T,{state:S,fault:b});else{if(n[S]>n[A.state])A.state=S;A.fault=A.fault||b}}let o=0;let c=0;let p=0;let h=0;for(const d of i.values()){if(d.state==="STATE_PRESENTED_ALL")o++;else if(d.state==="STATE_PRESENTED_PARTIAL")c++;else p++;if(d.fault)h++}const u=o+c+p;if(u===0)return null;const l=(a.ts-t.ts)/1e6;return{full:o,partial:c,dropped:p,faults:h,shownFps:x((o+c)/l),droppedPct:x(h/u*100)}}function Fe(e,r){const t=e.split("+");const a=t[t.length-1];const s=new Set(t.slice(0,-1).map(o=>o.toLowerCase()));if(r.key!==a)return false;const n=a.toLowerCase();const i={ctrl:s.has("control")||n==="control",alt:s.has("alt")||n==="alt",meta:s.has("meta")||s.has("command")||n==="meta",shift:s.has("shift")||n==="shift"};if(Boolean(r.ctrl)!==i.ctrl||Boolean(r.alt)!==i.alt||Boolean(r.meta)!==i.meta)return false;if(!i.shift&&r.shift&&a.length!==1)return false;if(i.shift&&!r.shift)return false;return true}function Ne(e){const r=[];for(const t of e){const a=t.startTime+t.duration;const s=r.find(i=>i.interactionId===t.interactionId&&Math.abs(i.startTime+i.duration-a)<=8);if(!s){r.push({...t});continue}const n=s.startTime+s.duration;s.startTime=Math.min(s.startTime,t.startTime);s.processingStart=Math.min(s.processingStart,t.processingStart);s.processingEnd=Math.max(s.processingEnd,t.processingEnd);s.duration=Math.max(n,a)-s.startTime;if(t.processingEnd-t.processingStart>0)s.name=t.name}return r}function De(e,r){const{start:t,end:a}=e;const s=Math.max(1,a-t);const n=[];for(let m=1;m<e.frames.length;m++)n.push(e.frames[m]-e.frames[m-1]);const i=1e3/r;const o=_e(n,95);let c=null;if(e.longTasks){let m=0;for(const k of e.longTasks){const q=Math.min(k.end,a)-Math.max(k.start,t);if(q>0)m+=q}c=x(Math.min(100,m/s*100))}let p=null;let h=null;let u=null;let l=[];let d=0;if(e.loaf){let m=0;let k=0;let q=0;const te=new Map;for(const E of e.loaf){if(E.startTime>=t&&E.startTime<=a&&E.startTime+E.duration>a)d++;if(E.startTime<t||E.startTime+E.duration>a)continue;m+=E.blockingDuration;if(E.styleAndLayoutStart>E.startTime)k+=Math.max(0,E.startTime+E.duration-E.styleAndLayoutStart);for(const L of E.scripts){q+=L.forced;const f=te.get(L.name)??{ms:0,count:0};f.ms+=L.duration;f.count++;te.set(L.name,f)}}p=x(m);h=x(k);u=x(q);l=[...te.entries()].map(([E,L])=>({name:E,ms:x(L.ms),count:L.count})).sort((E,L)=>L.ms-E.ms).slice(0,5)}let M=null;if(e.shifts){let m=0;for(const k of e.shifts)if(!k.hadRecentInput&&k.startTime>=t&&k.startTime<=a)m+=k.value;M=Math.round(m*1e3)/1e3}let S=null;let T=null;if(e.events){const m=Ne(e.events.filter(k=>k.startTime>=t&&k.startTime<=a));S=new Set(m.map(k=>k.interactionId)).size;for(const k of m)if(!T||k.duration>T.duration)T=k}const b=T;const A=e.frames;const ee=[...(e.clicks??[]).map(m=>m.t),...(e.keys??[]).map(m=>m.t)];const I=ee.length?Math.max(...ee):null;const se=I==null||A.filter(m=>m>I).length>=2;const ae=A.length===0?s:Math.max(0,A[0]-t,a-A[A.length-1]);return{edgeSilenceMs:x(ae),frameGapCount:n.length,inputCaptureComplete:se,resized:Boolean(e.resized),interactions:S,worstInteractionMs:b?x(b.duration):null,worstInputDelayMs:b?x(Math.max(0,b.processingStart-b.startTime)):null,worstProcessingMs:b?x(Math.max(0,b.processingEnd-b.processingStart)):null,worstPresentationMs:b?x(Math.max(0,b.startTime+b.duration-b.processingEnd)):null,fps:x(e.frames.length/s*1e3),frameGapP95Ms:o==null?null:x(o),frameGapMaxMs:n.length?x(Math.max(...n)):null,longFrames:n.filter(m=>m>i*1.5).length,longTaskPct:c,loafBlockingMs:p,styleLayoutMs:h,forcedLayoutMs:u,topScripts:l,loafCutOff:d,layoutShift:M,domNodesStart:e.domStart,domNodesEnd:e.domEnd}}function Be(e,r,t){const a=o=>o in e&&o in r?r[o]-e[o]:null;const s=o=>{const c=a(o);return c==null?null:x(c*1e3)};const n=s("TaskDuration");const i=s("Timestamp")??t;return{mainThreadPct:n==null?null:x(n/Math.max(1,i)*100),scriptMs:s("ScriptDuration"),styleMs:s("RecalcStyleDuration"),layoutMs:s("LayoutDuration"),layoutCount:a("LayoutCount"),styleCount:a("RecalcStyleCount"),heapMb:"JSHeapUsedSize"in r?x(r.JSHeapUsedSize/(1024*1024)):null}}function Ie(e,r){const t=[];if(e.droppedPct!=null&&e.droppedPct>=5)t.push(`${e.droppedPct}% of frames dropped or partial`);if(e.fps!=null&&e.fps<r*.95)t.push("Low main-thread frame rate");if(e.frameGapCount&&e.longFrames/e.frameGapCount>=.02)t.push("Frequent long frames");const a=Math.max(e.frameGapMaxMs??0,e.edgeSilenceMs??0);if(a>=100)t.push(`Freeze of ${Math.round(a)} ms`);if(e.mainThreadPct!=null&&e.mainThreadPct>=80)t.push("Main thread near full");if(e.longTaskPct!=null&&e.longTaskPct>=20)t.push("Long tasks block the main thread");if(e.forcedLayoutMs!=null&&e.forcedLayoutMs>=50)t.push(`Forced layout in scripts (${Math.round(e.forcedLayoutMs)} ms)`);if(e.layoutShift!=null&&e.layoutShift>=.1)t.push(`Layout shifts (${e.layoutShift})`);if(e.worstInteractionMs!=null&&e.worstInteractionMs>=200){t.push(`Slow response to input (${Math.round(e.worstInteractionMs)} ms)`)}return t}function pe(e,r){const t=e.filter(c=>!c.failed&&c.fps!=null);const a=[...t].sort((c,p)=>(c.fps??0)-(p.fps??0));const s=t.filter(c=>c.droppedPct!=null).length;const n=t.length>0&&s===t.length;const i=n?[...t].sort((c,p)=>c.droppedPct-p.droppedPct):a;const o=i.length?i[Math.floor((i.length-1)/2)]:null;return{caseName:e[0]?.caseName??"",cpu:e[0]?.cpu??1,validRuns:t.length,plannedRuns:e.length,isBaseline:false,deltaDroppedPct:null,tracedRuns:s,...(()=>{const c=n?t.map(h=>h.droppedPct):[];const p=t.map(h=>h.worstInteractionMs).filter(h=>h!=null);return{droppedMin:c.length?Math.min(...c):null,droppedMax:c.length?Math.max(...c):null,inputMin:p.length?Math.min(...p):null,inputMax:p.length?Math.max(...p):null}})(),runs:e,median:o,fpsMin:a.length?a[0].fps:null,fpsMax:a.length?a[a.length-1].fps:null,flags:o?Ie(n?o:{...o,droppedPct:null},r):[]}}var ze="Playwright isn't installed in this project. Install it, then run again:\n npm i -D playwright && npx playwright install chromium";async function Oe(e){const r=et(de(e,"noop.js"));for(const t of["playwright","playwright-core"]){let a;try{a=r.resolve(t)}catch{continue}const s=await import(ot(a).href);const n=s.chromium??s.default?.chromium;if(n)return n}throw new Error(`${ze}
|
|
146
|
-
(Searched from ${e}. Pass projectRoot to look elsewhere.)`)}async function G(e,r,t){let a;const s=new Promise(n=>{a=setTimeout(()=>n("timeout"),r)});try{const n=await Promise.race([e,s]);if(n==="timeout"){e.catch(()=>{});return await t()}return n}finally{clearTimeout(a)}}function ht(e){try{return Se("git",["rev-parse","HEAD"],{cwd:e,stdio:["ignore","pipe","ignore"],timeout:3e3}).toString().trim()}catch{return null}}function pt(e){try{const r=Se("git",["status","--porcelain"],{cwd:e,stdio:["ignore","pipe","ignore"],timeout:5e3});return r.toString().trim().length>0}catch{return null}}async function U(e,r){if(!e)return true;return G(e.close().then(()=>true,()=>false),r,async()=>false)}function je(e){if(!e.cases?.length)throw new Error("Give at least one case.");const r=new Set;for(const i of e.cases){if(!i.name?.trim())throw new Error("Every case needs a name.");if(r.has(i.name))throw new Error(`Two cases are named "${i.name}". Give each case its own name.`);r.add(i.name)}const t=(i,o,c,p)=>{if(i==null)return;if(!Number.isInteger(i)||i<c||i>p)throw new Error(`${o} must be a whole number from ${c} to ${p}.`)};const a=(i,o,c)=>{if(i==null)return;if(!Number.isFinite(i)||i<c)throw new Error(`${o} must be a number of at least ${c}.`)};const s=(i,o,c,p)=>{if(i==null)return;if(!Number.isFinite(i)||i<c||i>p)throw new Error(`${o} must be from ${c} to ${p}.`)};t(e.runsPerCase,"runsPerCase",1,10);s(e.durationMs,"durationMs",500,12e4);s(e.settleMs,"settleMs",0,6e4);s(e.refreshHz,"refreshHz",1,500);const n=e.cpuSlowdown??[];for(const i of n)s(i,"each cpuSlowdown rate",1,20);if(new Set(n).size!==n.length)throw new Error("cpuSlowdown has the same rate twice.");if(e.viewport){t(e.viewport.width,"viewport.width",200,8e3);t(e.viewport.height,"viewport.height",200,8e3);s(e.viewport.deviceScaleFactor,"viewport.deviceScaleFactor",.5,4)}t(e.extraRoundsWhenClose,"extraRoundsWhenClose",0,10);s(e.maxBatchMs,"maxBatchMs",1e4,6*60*6e4);for(const[i,o]of Object.entries(e.budgets??{}))s(o,`budgets.${i}`,0,1e9);for(const i of e.steps??[]){if("wait"in i)s(i.wait,"a wait step",0,6e4);if("scroll"in i)s(i.scroll,"a scroll step",-1e5,1e5)}new URL(e.url)}function re(e){return e.replace(/\u001b\[[0-9;]*m/g,"").split("\n")[0].trim().slice(0,160)}var mt=["disabled-by-default-devtools.timeline.frame","blink.user_timing"];var wt=["devtools.timeline","disabled-by-default-devtools.timeline","disabled-by-default-devtools.timeline.frame","blink.user_timing","loading","latencyInfo","v8.execute"];var me=15e7;var we=e=>new Promise(r=>setTimeout(r,e));async function Ge(e,r){const t=new Map;for(const a of r){if("expect"in a)continue;const s="click"in a?a.click:"type"in a?a.type.selector:null;if(!s||t.has(s))continue;const n=await e.$(s);const i=n?await n.boundingBox():null;if(!i)throw new Error(`step target ${s} isn't on the page (or isn't visible)`);t.set(s,{x:i.x+i.width/2,y:i.y+i.height/2})}return t}async function gt(e,r,t){for(const a of r){if("click"in a){const s=t.get(a.click);await e.mouse.click(s.x,s.y)}else if("press"in a)await e.keyboard.press(a.press);else if("type"in a){const s=t.get(a.type.selector);await e.mouse.click(s.x,s.y);await e.keyboard.type(a.type.text)}else if("scroll"in a)await e.mouse.wheel(0,a.scroll);else if("wait"in a)await we(a.wait)}}function Ue(e,r,t=[]){const a=[...r].sort((h,u)=>h.t-u.t);const s=[...t].sort((h,u)=>h.t-u.t);let n=0;let i=0;let o=-Infinity;const c=h=>{while(n<a.length){const u=a[n++];if(u.t>=o&&u.hit.includes(h)){o=u.t;return true}}return false};const p=h=>{while(i<s.length){const u=s[i++];if(u.t>=o&&Fe(h,u)){o=u.t;return true}}return false};for(const[h,u]of e.entries()){if("click"in u&&!c(u.click)){return`step ${h+1} (click ${u.click}) has no real click on its target, in order (moved, covered, or ignored)`}if("type"in u){if(!c(u.type.selector))return`step ${h+1} (type into ${u.type.selector}) has no real click on its target`;for(const l of u.type.text){if(!p(l))return`step ${h+1} (type "${u.type.text}") didn't deliver the key "${l}"`}}if("press"in u&&!p(u.press))return`step ${h+1} (press ${u.press}) didn't deliver that key`}return null}async function yt(e,r){for(const t of r){if(!("expect"in t))continue;const{selector:a,text:s}=t.expect;const n=await e.evaluate(`(() => { const n = document.querySelector(${JSON.stringify(a)}); if (!n) return false; ${s==null?"return true;":`return (n.textContent || "").includes(${JSON.stringify(s)});`} })()`);if(n)return s==null?a:`${a} contains "${s}"`}return null}async function Mt(e,r){for(const[t,a]of r){const s=await e.$(t);const n=s?await s.boundingBox():null;if(!n)return`step target ${t} was gone after the run`;const i=n.x+n.width/2;const o=n.y+n.height/2;if(Math.abs(i-a.x)>4||Math.abs(o-a.y)>4){return`step target ${t} moved during the run, so a click may have missed it`}}return null}function He(e){for(const r of e){if(!r.found)return`expected ${r.selector} at the end of the run, but it isn't there`;const t=r.matched??(r.text==null||(r.content??"").includes(r.text));if(!t){return`expected ${r.selector} to contain "${r.text}" at the end of the run, got "${(r.content??"").slice(0,60)}"`}}return null}async function qe(e){const{metrics:r}=await e.send("Performance.getMetrics");return Object.fromEntries(r.map(t=>[t.name,t.value]))}async function bt(e,r=()=>{}){je(e);const t=await We(e.lockWaitMs??20*6e4,r);const a={browserLeftRunning:false};try{return await $t(e,r,a)}finally{t(a.browserLeftRunning)}}async function We(e,r=()=>{},t=2e3){const a=Date.now()+Math.max(0,e);let s=false;for(;;){try{return Je()}catch(n){const i=n?.message??String(n);const o=/Another web benchmark|already running/.test(i);if(!o||Date.now()+t>a)throw n;if(!s){r("another web benchmark is running; waiting for it to finish");s=true}await new Promise(c=>setTimeout(c,t))}}}var Z=de(at(),"buoy-web-bench.lock");var ge=false;function Je(){if(ge){throw new Error("A web benchmark is already running in this process (or left a stuck browser). Wait, or restart the MCP server.")}const e=`${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`;const r=JSON.stringify({pid:process.pid,token:e,startedAt:new Date().toISOString()});const t=()=>{const n=`${Z}.${e.replace(/[^\w]/g,"")}.tmp`;try{Te(n,r);ve(n,Z);return true}catch{return false}finally{try{Y(n)}catch{}}};const a=()=>{try{return JSON.parse(nt(Z,"utf8"))}catch{return null}};const s=n=>{if(!n)return false;try{process.kill(n,0);return true}catch{return false}};if(!t()){const n=a();if(!n)throw new Error("Another web benchmark is starting. Wait for it to finish.");if(s(n.pid)){throw new Error(`Another web benchmark is running (pid ${n.pid}, since ${n.startedAt}). Two at once would skew both; wait for it to finish.`)}const i=`${Z}.cleanup`;let o=null;try{if(Date.now()-Ee(i).mtimeMs>3e4){const c=`${i}.${e.replace(/[^\w]/g,"")}.old`;st(i,c);if(Date.now()-Ee(c).mtimeMs>3e4)Y(c);else{try{ve(c,i)}catch{}Y(c)}}}catch{}try{o=xe(i,"wx")}catch{throw new Error("Another web benchmark is clearing an old lock. Try again in a moment.")}try{const c=a();if(c?.token===n.token)Y(Z);if(!t())throw new Error("Another web benchmark just started. Wait for it to finish.")}finally{ke(o);try{Y(i)}catch{}}}ge=true;return(n=false)=>{if(n)return;ge=false;if(a()?.token===e){try{Y(Z)}catch{}}}}async function $t(e,r,t){const a=e.runsPerCase??3;const s=Math.max(500,e.durationMs??5e3);const n=Math.max(0,e.settleMs??1e3);const i=e.frameTrace!==false;const o=(e.cpuSlowdown?.length?e.cpuSlowdown:[1]).map(f=>Math.max(1,f));let c=e.refreshHz??60;const p={width:e.viewport?.width??1280,height:e.viewport?.height??800,deviceScaleFactor:e.viewport?.deviceScaleFactor??1};const h=[];const u=[];let l=null;if(a<2)h.push("runsPerCase=1 gives no spread; use 3 or more before choosing.");const d=await Oe(e.projectRoot??process.cwd());const M=await d.launch({headless:!e.headed,args:["--enable-gpu"]});const S=[];const T=[];const b=Date.now();const A=new Date(b).toISOString();const ee=e.maxBatchMs??30*6e4;let I=false;let se=false;const ae={commit:ht(e.projectRoot??process.cwd()),dirty:pt(e.projectRoot??process.cwd())};try{const f={viewport:{width:p.width,height:p.height},deviceScaleFactor:p.deviceScaleFactor};const $=await G(M.newContext(f),3e4,async()=>{t.browserLeftRunning=true;throw new Error("Chromium didn't open a page in 30 s. Restart the MCP server and try again.")});const R=3e4+15e3+n+s+6e4;const V=async(g,w,v,_)=>{const C={};return G(F(C,g,w,v,_),R,async()=>{C.cancelled=true;if(!C.ctx)I=true;else if(!await U(C.ctx,1e4))I=true;return{...W(g.name,w,v),failed:`run took over ${Math.round(R/1e3)} s (the page may be stuck in a loop)`}})};const W=(g,w,v)=>({caseName:g,cpu:w,round:v,fps:null,frameGapP95Ms:null,frameGapMaxMs:null,longFrames:0,longTaskPct:null,loafBlockingMs:null,styleLayoutMs:null,forcedLayoutMs:null,topScripts:[],layoutShift:null,domNodesStart:null,domNodesEnd:null,mainThreadPct:null,scriptMs:null,styleMs:null,layoutMs:null,layoutCount:null,styleCount:null,heapMb:null,pageErrors:[]});const F=async(g,w,v,_,C)=>{const J=await M.newContext(f);g.ctx=J;if(g.cancelled){await U(J,1e4);throw new Error("run was cancelled during setup")}const y=await J.newPage();const z=[];y.on("pageerror",P=>z.push(String(P?.message??P)));const N={...W(w.name,v,_),pageErrors:z};try{const P=await J.newCDPSession(y);await P.send("Performance.enable",{timeDomain:"timeTicks"});if(v>1)await P.send("Emulation.setCPUThrottlingRate",{rate:v});const oe=await y.goto(Ae(e.url,w,e.path),{waitUntil:"load",timeout:3e4});if(oe&&oe.status()>=400)return{...N,failed:`HTTP ${oe.status()}`};if(e.readySelector)await y.waitForSelector(e.readySelector,{state:"attached",timeout:15e3});await we(n);const Me=C||i;if(Me){await P.send("Tracing.start",{transferMode:"ReturnAsStream",categories:(C?wt:mt).join(",")})}const ie=e.steps?.length?await Ge(y,e.steps):new Map;if(e.steps?.length){const K=await yt(y,e.steps);if(K)return{...N,failed:`expect ${K} already held before the steps, so it can't prove they worked`};const Q=[...ie.keys()];const j=e.steps.flatMap(X=>"expect"in X?[X.expect]:[]);await y.evaluate(`window.__buoyBenchClickTargets = ${JSON.stringify(Q)}; window.__buoyBenchExpects = ${JSON.stringify(j)}; true`)}const le=await qe(P);await y.evaluate(it);const be=Date.now();if(e.steps?.length)await gt(y,e.steps,ie);const $e=s-(Date.now()-be);if($e>0)await we($e);await y.evaluate(lt);const ce=await qe(P);const Xe=Date.now()-be;const O=await y.evaluate(ct);let D=null;if(Me){const K=new Promise(B=>P.on("Tracing.tracingComplete",ne=>B(ne.stream)));await P.send("Tracing.end");const Q=await K;const j=C?xe(C,"w"):null;const X=[];let ue=0;try{for(;;){const B=await P.send("IO.read",{handle:Q});const ne=B.base64Encoded?Buffer.from(B.data,"base64").toString("utf8"):B.data;ue+=ne.length;if(j!=null)rt(j,ne);if(ue<=me)X.push(ne);else X.length=0;if(B.eof)break}}finally{await P.send("IO.close",{handle:Q}).catch(()=>{});if(j!=null)ke(j)}if(C)u.push(C);if(ue>me){h.push(`Trace for ${w.name} was over ${Math.round(me/1e6)} MB, so its frames weren't counted. A page that busy shows its problem in the table already.`)}else{try{const B=JSON.parse(X.join(""));D=Le(Array.isArray(B)?B:B.traceEvents??[])}catch{D=null}}}if(!O)return{...N,failed:"probe returned nothing (page navigated away?)"};if(O.hidden)return{...N,failed:"page was hidden during the run"};if(O.resized)return{...N,failed:"the window size or zoom changed during the run"};if(z.length&&!e.allowPageErrors){return{...N,failed:`page error: ${re(z[0])}`}}if(e.steps?.length){const K=Ue(e.steps,O.clicks??[],O.keys??[]);if(K)return{...N,failed:K};const Q=He(O.expectResults??[]);if(Q)return{...N,failed:Q};const j=await Mt(y,ie);if(j)return{...N,failed:j}}const Ye="Timestamp"in le&&"Timestamp"in ce?x((ce.Timestamp-le.Timestamp)*1e3):null;return{...N,...De(O,c),...Be(le,ce,Xe),probeSpanMs:x(O.end-O.start),cdpSpanMs:Ye,shownFps:D?.shownFps??null,droppedFrames:D?.faults??null,frameCounts:D?{full:D.full,partial:D.partial,dropped:D.dropped,faults:D.faults}:null,droppedPct:D?.droppedPct??null}}catch(P){return{...N,failed:re(P?.message??String(P))}}finally{await U(y,5e3);if(!await U(J,1e4))I=true}};{const g=await G($.newPage(),15e3,async()=>{throw new Error("Chromium didn't open the GPU check page in 15 s.")});try{l=await G(g.evaluate(Re),5e3,async()=>null)??null}catch{l=null}finally{await U(g,5e3)}if(l)r(`GPU ${l}`);if(l&&fe(l)){h.push(`Chrome is drawing with a software GPU (${l}). Canvas, WebGL and Skia pages run several times slower than on a real screen, so their numbers mean little. Try headed: true, or a machine with a GPU.`)}}if(e.refreshHz==null){const g=await G($.newPage(),15e3,async()=>{throw new Error("Chromium didn't open the calibration page in 15 s.")});try{await G(g.goto("about:blank"),15e3,async()=>null);const w=await G(g.evaluate(ut),5e3,async()=>{throw new Error("idle rate check timed out")});c=Ce(w);r(`idle frame rate ${w.toFixed(1)} \u2192 ${c} Hz`)}catch{h.push("Couldn't measure the idle frame rate; frame budgets assume 60 Hz.")}finally{await U(g,5e3)}}r("warmup load");await V(e.cases[0],1,-1);const H=async(g,w,v,_)=>{if(I){S.push({...W(g.name,w,v),failed:"an earlier run left the browser stuck; batch stopped"});return}if(Date.now()-b>ee){S.push({...W(g.name,w,v),failed:"batch time limit reached; run skipped"});return}r(_);T.push({cpu:w,round:v,caseName:g.name});S.push(await V(g,w,v))};for(const g of o){for(let w=0;w<a;w++){const v=he(e.cases,2654435769^w*7919+g);for(const _ of v)await H(_,g,w,`cpu ${g}x \xB7 round ${w+1}/${a} \xB7 ${_.name}`)}for(let w=0;w<(e.extraRoundsWhenClose??0);w++){const v=e.cases.map(y=>pe(S.filter(z=>z.cpu===g&&z.caseName===y.name),c));for(const y of v)y.control=e.cases.find(z=>z.name===y.caseName)?.control===true;const _=new Set([...ye(v).flat(),...v.filter(y=>!y.control&&y.validRuns<2&&y.plannedRuns>0).map(y=>y.caseName)]);if(_.size===0)break;const C=a+w;const J=he(e.cases.filter(y=>_.has(y.name)),20973^C*7919+g);for(const y of J)await H(y,g,C,`cpu ${g}x \xB7 extra round ${w+1} (close call) \xB7 ${y.name}`)}}if(e.traceDir){tt(e.traceDir,{recursive:true});for(const[g,w]of e.cases.entries()){const v=de(e.traceDir,`${String(g+1).padStart(2,"0")}-${w.name.replace(/[^\w.-]+/g,"_")}.json`);if(I||Date.now()-b>ee){h.push(`Trace run for ${w.name} skipped (batch time limit or a stuck browser).`);continue}r(`trace \xB7 ${w.name}`);const _=await V(w,o[0],-1,v);if(_.failed)h.push(`Trace run for ${w.name} failed: ${_.failed}`)}}await U($,1e4)}finally{const f=await U(M,15e3);t.browserLeftRunning=!f;if(!f){se=true;h.push("Chromium didn't close in 15 s and may still be running. The benchmark lock stays held; restart the MCP server before the next batch.")}}const m=[];for(const f of o){for(const $ of e.cases){m.push(pe(S.filter(R=>R.cpu===f&&R.caseName===$.name),c))}}for(const f of m)f.control=e.cases.find($=>$.name===f.caseName)?.control===true;for(const f of o){const $=m.filter(F=>F.cpu===f);const R=$.find(F=>!F.control);if(!R)continue;R.isBaseline=true;const V=F=>F.droppedMin!=null?F.median?.droppedPct??null:null;const W=V(R);for(const F of $.filter(H=>H!==R&&!H.control)){const H=V(F);F.deltaDroppedPct=W!=null&&H!=null?x(H-W):null}}if(e.allowPageErrors&&S.some(f=>f.pageErrors.length)){h.push("Some runs threw page errors (kept because allowPageErrors is on).")}if(I)h.push("A browser context wouldn't close, so the batch stopped early. Restart and run again.");if(e.budgets)for(const f of m)f.budget=Pe(f,e.budgets,e.steps??[]);const k=m.filter(f=>f.median).length;const q=m.every(f=>f.validRuns===f.plannedRuns);const te=k===0?"failed":k<m.length||!q?"partial":"ok";const E=m.filter(f=>f.median&&f.validRuns>0&&f.validRuns<f.plannedRuns);for(const f of E){const $=Array.from(new Set(f.runs.filter(R=>R.failed).map(R=>R.failed)));h.push(`${f.caseName} (CPU ${f.cpu}x): ${f.plannedRuns-f.validRuns} of ${f.plannedRuns} runs failed: ${$.join("; ")}`)}for(const f of m){const $=f.median;if($?.probeSpanMs&&$.cdpSpanMs&&Math.abs($.cdpSpanMs-$.probeSpanMs)/$.probeSpanMs>.15){h.push(`${f.caseName} (CPU ${f.cpu}x): Chrome's counters cover ${Math.round($.cdpSpanMs)} ms but frames cover ${Math.round($.probeSpanMs)} ms, so its cost columns include extra time.`)}if($?.probeSpanMs!=null&&$.probeSpanMs<s*.8){h.push(`${f.caseName} (CPU ${f.cpu}x) recorded ${Math.round($.probeSpanMs)} ms, not the ${s} ms asked for.`)}}const L={status:te,hasSteps:Boolean(e.steps?.length),browserLeftRunning:se,replay:{startedAt:A,...ae,runOrder:T},url:e.url,browser:`Chromium ${M.version()}${e.headed?"":" (headless)"}`,gpu:l,viewport:p,refreshHz:c,durationMs:s,runsPerCase:a,results:m,traces:u,warnings:h};if(e.outFile){try{Te(e.outFile,JSON.stringify({options:e,result:L},null,2))}catch(f){L.warnings.push(`Couldn't write outFile ${e.outFile}: ${re(f?.message??String(f))}`)}}return L}var Ke=1.5;var Qe=2;function ye(e){const r=e.filter(n=>!n.control&&n.median&&n.validRuns>1);const t=r.filter(n=>n.droppedMin!=null);const a=[];if(t.length>=2&&t.length===r.length){const n=[...t].sort((i,o)=>i.median.droppedPct-o.median.droppedPct);for(let i=1;i<n.length;i++){const[o,c]=[n[i-1],n[i]];const p=c.median.droppedPct-o.median.droppedPct<=Ke;if(c.droppedMin<=o.droppedMax||p)a.push([o.caseName,c.caseName])}return a}if(t.length>0)return a;const s=r.filter(n=>n.median.fps!=null).sort((n,i)=>i.median.fps-n.median.fps);for(let n=1;n<s.length;n++){const[i,o]=[s[n-1],s[n]];const c=i.median.fps-o.median.fps<=Qe;if((o.fpsMax??0)>=(i.fpsMin??0)||c)a.push([i.caseName,o.caseName])}return a}function Ve(e){const r=e.filter(o=>!o.control&&o.median);const t=[];const a=r.filter(o=>o.validRuns<2);if(a.length)t.push(`**Too few runs to rank:** ${a.map(o=>o.caseName).join(", ")} (need 2 or more valid runs).`);const s=r.filter(o=>o.validRuns>1);const n=s.filter(o=>o.droppedMin!=null);if(n.length>0&&n.length<s.length){t.push(`**Not enough traced runs to compare smoothness:** ${s.filter(o=>!n.includes(o)).map(o=>`${o.caseName} (${o.tracedRuns}/${o.validRuns} traced)`).join(", ")}.`);return t.join("\n")}const i=new Map(s.map(o=>[o.caseName,o]));for(const[o,c]of ye(e)){const p=i.get(o);const h=i.get(c);t.push(n.length?`**Too close to call:** ${o} (${p.droppedMin}\u2013${p.droppedMax}% dropped) and ${c} (${h.droppedMin}\u2013${h.droppedMax}%) overlap or sit within ${Ke} pp. No clear winner between them; more runs may separate them.`:`**Too close to call:** ${o} and ${c} overlap or sit within ${Qe} FPS. No clear winner between them; more runs may separate them.`)}return t.length?t.join("\n"):null}function St(e){const r=(o,c=1)=>o==null?"\u2014":o.toFixed(c);const t=[];t.push(`### Web benchmark \xB7 ${e.url}`);t.push(`${e.browser}${e.gpu?` \xB7 GPU: ${e.gpu}${fe(e.gpu)?" \u26A0\uFE0F software":""}`:""} \xB7 ${e.viewport.width}\xD7${e.viewport.height} @${e.viewport.deviceScaleFactor}x \xB7 ${e.runsPerCase} runs${e.results.some(o=>o.plannedRuns>e.runsPerCase)?" (more for close calls)":""} \xD7 ${e.durationMs/1e3}s per case, fresh browser context each run, case order shuffled each round.`);const a=e.results.map(o=>o.median?.probeSpanMs).filter(o=>o!=null);const s=e.results.map(o=>o.median?.cdpSpanMs).filter(o=>o!=null);const n=o=>`${(Math.min(...o)/1e3).toFixed(2)}\u2013${(Math.max(...o)/1e3).toFixed(2)} s`;if(a.length){t.push(`Recorded: frames over ${n(a)}${s.length?`; cost columns (Chrome's counters) over ${n(s)}`:""}, not the requested time.`)}if(e.replay?.dirty)t.push(`Note: the project had uncommitted changes, so commit ${e.replay.commit?.slice(0,10)??"?"} alone doesn't describe the code that ran.`);t.push("");t.push(`**How to read this.** Dropped % is the smoothness number: frames Chrome dropped or only partly produced that it says hurt smoothness, as a share of the frames it wanted, from its own verdicts in a trace. Rank on it first, and only across variants doing the same work. Shown FPS counts frames Chrome produced (headless Chrome displays nothing), so an animation that changes less often needs fewer; don't compare it across variants that update at different rates. Main-thread FPS and frame gaps come from requestAnimationFrame. Main thread % is Chrome's task time for the run. Desktop numbers, even with CPU slowdown, don't stand in for a real phone's GPU, memory or heat.`);const i=Array.from(new Set(e.results.map(o=>o.cpu)));for(const o of i){t.push("");t.push(o===1?`#### No CPU slowdown`:`#### CPU slowed ${o}x`);t.push("");t.push(`| Case | Runs | Shown FPS | Dropped % | Main-thread FPS (range) | p95 gap ms | Worst gap ms | Main thread % | Script ms | Style ms | Layout ms | Layouts | DOM nodes | JS heap MB | Flags |`);t.push(`|------|-----:|----------:|----------:|------------------------:|-----------:|-------------:|--------------:|----------:|---------:|----------:|--------:|----------:|--------:|-------|`);for(const l of e.results.filter(d=>d.cpu===o)){const d=l.median;if(!d){const T=l.runs.find(b=>b.failed)?.failed??"no valid runs";t.push(`| ${l.caseName} | 0/${l.plannedRuns} | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u274C FAILED (${T}) |`);continue}const M=l.fpsMin!=null&&l.fpsMax!=null&&l.validRuns>1?` (${r(l.fpsMin)}\u2013${r(l.fpsMax)})`:"";const S=l.flags.length?`\u26A0\uFE0F ${l.flags.join("; ")}`:"no flags";t.push(`| ${l.isBaseline?`${l.caseName} (baseline)`:l.control?`${l.caseName} (control)`:l.caseName} | ${l.validRuns}/${l.plannedRuns} | ${r(d.shownFps)} | ${l.droppedMin==null&&l.tracedRuns>0?`\u2014 (${l.tracedRuns}/${l.validRuns} traced)`:r(l.droppedMin==null?null:d.droppedPct)}${l.droppedMin!=null&&l.validRuns>1?` [${l.droppedMin}\u2013${l.droppedMax}]`:""}${l.deltaDroppedPct!=null?` (${l.deltaDroppedPct>0?"+":""}${l.deltaDroppedPct} pp)`:""} | ${r(d.fps)}${M} | ${r(d.frameGapP95Ms)} | ${r(d.frameGapMaxMs)} | ${r(d.mainThreadPct)} | ${r(d.scriptMs,0)} | ${r(d.styleMs,0)} | ${r(d.layoutMs,0)} | ${r(d.layoutCount,0)} | ${r(d.domNodesEnd,0)} | ${r(d.heapMb)} | ${S} |`)}const c=Ve(e.results.filter(l=>l.cpu===o));if(c){t.push("");t.push(c)}const p=e.results.filter(l=>l.cpu===o&&l.budget);if(p.length){t.push("");t.push(`Budget:`);for(const l of p){const d=l.budget;const M=[...d.over,...d.unknown.map(T=>`${T} not measured`)].join("; ");const S=l.control?`${l.caseName} (control)`:l.caseName;t.push(`- **${S}**: ${d.meets?"meets every limit":`misses (${M||"no result"})`}`)}}const h=e.results.filter(l=>l.cpu===o&&l.median?.interactions!=null&&(e.hasSteps||l.median.interactions>0)).map(l=>{const d=l.median;const M=l.inputMin!=null&&l.inputMax!=null?` \xB7 slowest across all runs ${l.inputMax} ms (range ${l.inputMin}\u2013${l.inputMax})`:"";const S=l.runs.filter(b=>!b.failed&&b.fps!=null&&b.inputCaptureComplete===false).length;const T=S?` \xB7 \u26A0\uFE0F timing incomplete in ${S} run(s): the last input's paint came after the run ended, so its time is unknown (leave more time after the last step)`:"";if(!d.interactions)return`- **${l.caseName}**: middle run had no input entry (inputs under 16 ms aren't reported)${M}${T}`;return`- **${l.caseName}**: middle run: ${d.interactions} reported (16 ms or more), slowest ${d.worstInteractionMs} ms (waiting ${d.worstInputDelayMs} \xB7 handlers ${d.worstProcessingMs} \xB7 to paint ${d.worstPresentationMs})${M}${T}`});if(h.length){t.push("");t.push(`Input response (scripted steps, Event Timing). Inputs under 16 ms aren't reported. Under 200 ms is good.`);t.push(...h)}const u=e.results.filter(l=>l.cpu===o&&l.median&&l.median.topScripts.length).map(l=>`- **${l.caseName}**: ${l.median.topScripts.slice(0,3).map(d=>`${d.name} ${d.ms} ms`).join(", ")}`);if(u.length){t.push("");t.push(`Scripts in slow frames (Long Animation Frames; frames that ran past the end of a run are left out):`);t.push(...u)}}if(e.traces.length){t.push("");t.push(`Chrome traces (open in DevTools \u2192 Performance \u2192 Load profile): ${e.traces.join(", ")}`)}if(e.warnings.length){t.push("");t.push(...e.warnings.map(o=>`\u26A0\uFE0F ${o}`))}return t.join("\n")}export{Pe as checkBudgets,Re as GPU_RENDERER_JS,fe as isSoftwareGpu,_e as percentile,Ce as snapRefreshRate,he as shuffled,Ae as caseUrl,Le as summarizeFrameTrace,Fe as keyMatches,Ne as mergeInteractions,De as summarizeProbe,Be as cdpDeltas,Ie as webBenchFlags,pe as aggregateCase,ze as PLAYWRIGHT_MISSING,Oe as loadChromium,G as withDeadline,U as closeWithin,je as validateWebBenchOptions,re as oneLine,Ge as locateStepTargets,Ue as missedInput,He as expectFailureFrom,bt as runWebBench,We as waitForHostLock,Je as takeHostLock,ye as tiedPairs,Ve as closeCall,St as formatWebBenchMarkdown};
|
package/dist/chunk-VKWX4XAY.mjs
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{createRequire as p}from"module";const B=p(import.meta.url);import{execFile as d}from"child_process";import{promisify as h}from"util";import{existsSync as c,readFileSync as f}from"fs";import u from"os";import i from"path";var y=h(d);function g(){const n=process.env.BUOY_SIMCAM_DIR;const e=[...n?[i.join(n,"buoycam")]:[],"/Applications/Buoy.app/Contents/Resources/simcam/buoycam",i.join(u.homedir(),"Applications/Buoy.app/Contents/Resources/simcam/buoycam")];let t=__dirname;for(let o=0;o<8;o++){e.push(i.join(t,"apps/desktop/resources/simcam/buoycam"));const a=i.dirname(t);if(a===t)break;t=a}return e}var s=null;function m(){if(s)return s;s=g().find(n=>c(n))??null;return s}async function b(n){const e=m();if(!e){return{ok:false,error:"The simulator camera components were not found. They ship with the Buoy desktop app \u2014 install it, or set BUOY_SIMCAM_DIR to the directory holding `buoycam`."}}try{const{stdout:t}=await y(e,[...n,"--json"],{timeout:3e4});return{ok:true,data:JSON.parse(t)}}catch(t){const o=t.stdout??"";try{return{ok:false,data:JSON.parse(o)}}catch{return{ok:false,error:t.message||"the simulator camera CLI failed"}}}}function _(n){const e=n.filter(r=>r.level==="fail");const t=n.filter(r=>r.level==="warn");if(e.length===0&&t.length===0){return"The simulator camera is healthy \u2014 every check passed."}const o=[];for(const r of[...e,...t]){o.push(`- **${r.name}** (${r.level}): ${r.detail}`);if(r.fix)o.push(` ${r.fix}`)}const a=e.length>0?`${e.length} problem${e.length===1?"":"s"} found.`:`No hard failures, but ${t.length} thing${t.length===1?"":"s"} worth knowing.`;return[a,"",...o].join("\n")}function $(n,e){if(n.length===0){return"No booted simulators.\n\n_Boot one first \u2014 `xcrun simctl boot <udid>`, or open Simulator.app. The camera attaches to a simulator, so nothing else here works until one is running._"}const t=[n.length===1?"1 booted simulator:":`${n.length} booted simulators:`,...n.map(o=>`- **${o.name}** (${o.runtime}) \u2014 \`${o.udid}\``)];if(n.length>1&&e===null){t.push("","_More than one is booted, so pass `udid` to list a specific one's apps \u2014 and to every other camera tool that takes one._");return t.join("\n")}if(e===null){t.push("","_Could not read the installed apps._");return t.join("\n")}if(e.length===0){t.push("","No apps installed on it.");return t.join("\n")}t.push("",`${e.length} installed app${e.length===1?"":"s"}:`,...e.map(o=>`- ${o.name} \u2014 \`${o.bundleId}\``),"","_Pass one of these bundle ids to `camera_launch` to relaunch it with the camera attached._");return t.join("\n")}function k(n,e){const t=[];t.push(n.length?'**Mac cameras** (for `kind: "camera"`):':"**Mac cameras**: none found.");if(n.length){t.push(...n.map(o=>`- ${o.name} \u2014 \`${o.uniqueId}\``))}else{t.push("_If this Mac has a webcam, Buoy has not been granted Camera permission (System Settings \u2192 Privacy & Security \u2192 Camera)._")}t.push("");t.push(e.length?'**Capturable windows** (for `kind: "screen"`, `value: "window:<id>"`):':"**Capturable windows**: none found.");if(e.length){t.push(...e.map(o=>`- ${o.app}${o.title?` \u2014 ${o.title}`:""} \u2014 \`window:${o.id}\``))}else{t.push("_Screen Recording permission is almost certainly missing (System Settings \u2192 Privacy & Security \u2192 Screen & System Audio Recording)._")}return t.join("\n")}var l=null;function S(n){l=n}function v(){if(l)return[l];return[i.join(u.homedir(),"Library/Application Support/Buoy/entitlement.json"),i.join(u.homedir(),"Library/Application Support/buoy-desktop/entitlement.json")]}function w(){for(const n of v()){try{if(!c(n))continue;const e=JSON.parse(f(n,"utf8"));return{tier:e.tier==="pro"||e.tier==="free"?e.tier:"locked",isPro:e.isPro===true&&e.hasDesktopAccess===true&&typeof e.validUntil==="number"&&Number.isFinite(e.validUntil)&&Date.now()<e.validUntil&&typeof e.proValidUntil==="number"&&Number.isFinite(e.proValidUntil)&&Date.now()<e.proValidUntil}}catch{}}return{tier:"locked",isPro:false}}function j(n){return`\`${n}\` is a Buoy Pro feature.
|
|
2
|
-
|
|
3
|
-
The simulator camera itself is free \u2014 the Mac camera, image, video, test-pattern and QR sources all work in the Buoy desktop panel with a Free account. Pro adds the screen region, the non-QR symbologies (PDF417, Aztec, DataMatrix, Code 128), and driving any of it from an agent.
|
|
4
|
-
|
|
5
|
-
_Tell the user to enter a licence in Buoy Desktop, or see https://buoy.gg/pricing. Don't try to work around it \u2014 use the desktop panel by hand instead._`}export{m as cliPath,b as run,_ as describeDiagnosis,$ as describeDevices,k as describeInputs,S as setEntitlementFileForTests,w as readEntitlement,j as proRequired};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{createRequire as v}from"module";const E=v(import.meta.url);import{SyncClient as y,createMcpServer as R}from"./chunk-LDULNQCZ.mjs";import"./chunk-2MSKDRYW.mjs";import"./chunk-VKWX4XAY.mjs";import"./chunk-AMU2EOGE.mjs";import"./chunk-OPHPYPJD.mjs";import"./chunk-MTTWR4T7.mjs";import{promises as S}from"fs";import $ from"path";import{Client as _}from"@modelcontextprotocol/sdk/client/index.js";import{InMemoryTransport as B}from"@modelcontextprotocol/sdk/inMemory.js";function I(e){const o={names:[]};for(let n=0;n<e.length;n++){const t=e[n];if(t==="--device")o.deviceId=e[++n];else if(t==="--update-snapshots")o.updateSnapshots=true;else if(t==="--json")o.json=true;else if(t==="--wait")o.waitMs=Number(e[++n])*1e3;else if(t==="--all")continue;else if(!t.startsWith("--"))o.names.push(t);else throw new Error(`Unknown option ${t}.`)}return o}async function P(){const e=$.join(process.cwd(),".buoy","flows");return(await S.readdir(e).catch(()=>[])).filter(o=>o.endsWith(".json")).map(o=>o.replace(/\.json$/,"")).sort()}async function g(e,o=n=>process.stderr.write(`${n}
|
|
2
|
-
`)){const n=e.names.length?e.names:await P();if(n.length===0)throw new Error('No saved flows. Save one with run_flow and save:"name", or pass a flow name.');const t=process.env.BUOY_BROKER_PORT;const s=new y({url:process.env.BUOY_BROKER_URL,port:t?Number(t):void 0,autoSpawnBroker:process.env.BUOY_NO_SPAWN!=="1"});const w=R(s);const[d,f]=B.createLinkedPair();const i=new _({name:"buoy-flow-run",version:"1"});try{await w.connect(f);await i.connect(d);await s.start();const a=Date.now()+(e.waitMs??6e4);while(Date.now()<a&&!s.connectedDevices().some(r=>(r.isPro||r.tier==="free")&&(!e.deviceId||r.deviceId===e.deviceId))){await new Promise(r=>setTimeout(r,500))}if(!s.connectedDevices().some(r=>r.isPro||r.tier==="free")){throw new Error("No app on a Buoy account is connected. Start the app (with Buoy) on a simulator or device, then run again.")}const u=[];for(const r of n){const m=Date.now();o(`\u25B6 ${r}`);const l=await i.callTool({name:"run_flow",arguments:{flow:r,include:"none",...e.deviceId?{deviceId:e.deviceId}:{},...e.updateSnapshots?{updateSnapshots:true}:{}}},void 0,{timeout:10*6e4});const h=(l.content??[]).filter(c=>c.type==="text").map(c=>c.text).join("\n");const p=l.isError!==true;u.push({name:r,passed:p,ms:Date.now()-m,report:h});o(`${p?"\u2713":"\u2717"} ${r} (${((Date.now()-m)/1e3).toFixed(1)}s)`)}return u}finally{await i.close().catch(()=>void 0);await Promise.race([s.stop().catch(()=>void 0),new Promise(a=>setTimeout(a,2e3))])}}function j(e){const o=e.filter(t=>!t.passed);const n=[`${e.length-o.length} of ${e.length} flows passed.`];for(const t of o){n.push("",`\u2717 ${t.name}`,t.report.trim(),` Rerun: npx @buoy-gg/mcp flow run ${t.name}`)}return n.join("\n")}export{j as formatFlowResults,I as parseFlowRunArgs,g as runFlows};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createRequire as r}from"module";const t=r(import.meta.url);import{farmFairness as e,registerSimFarmTools as m,runFarmd as o}from"./chunk-2MSKDRYW.mjs";import"./chunk-VKWX4XAY.mjs";import"./chunk-AMU2EOGE.mjs";import"./chunk-MTTWR4T7.mjs";export{e as farmFairness,m as registerSimFarmTools,o as runFarmd};
|
package/dist/src-DPWEFWQY.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createRequire as e}from"module";const B=e(import.meta.url);import{ALWAYS_ENABLED as r,DEFAULT_SETTINGS as a,Engine as t,FarmClient as o,FarmError as i,LoadMeter as m,PROTOCOL_VERSION as n,SLIM_CATEGORIES as d,STATE_VERSION as l,SimctlHost as s,Store as p,agentLabel as f,connectFarm as E,crashLoop as u,defaultSeedDir as L,detectAgent as S,farmDefaults as c,farmHome as R,farmOwnedUdids as T,findDataContainer as A,findExampleBuild as I,findRepoRoot as O,nodeLauncher as _,parseCpuTime as C,paths as D,profileHash as F,profileLabels as b,readBuoyInstallId as h,slimLabels as N,startFarmd as g}from"./chunk-AMU2EOGE.mjs";import"./chunk-MTTWR4T7.mjs";export{r as ALWAYS_ENABLED,a as DEFAULT_SETTINGS,t as Engine,o as FarmClient,i as FarmError,m as LoadMeter,n as PROTOCOL_VERSION,d as SLIM_CATEGORIES,l as STATE_VERSION,s as SimctlHost,p as Store,f as agentLabel,E as connectFarm,u as crashLoop,L as defaultSeedDir,S as detectAgent,c as farmDefaults,R as farmHome,T as farmOwnedUdids,A as findDataContainer,I as findExampleBuild,O as findRepoRoot,_ as nodeLauncher,C as parseCpuTime,D as paths,F as profileHash,b as profileLabels,h as readBuoyInstallId,N as slimLabels,g as startFarmd};
|