stim 1.16.0 → 1.17.1

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.
Files changed (108) hide show
  1. package/dist/{activity-BxwdD81S.mjs → activity-IRvJY_ru.mjs} +23 -3
  2. package/dist/{agent-device-usage-output-Dpih1Tfa.mjs → agent-device-usage-output-BK41vnzN.mjs} +3 -3
  3. package/dist/{android-Ift4kVvt.mjs → android-B6rDWrTm.mjs} +681 -319
  4. package/dist/{android-B_NumVyQ.mjs → android-B_YNZy61.mjs} +7 -43
  5. package/dist/{android-r2rBL8kV.mjs → android-DE4v59Mb.mjs} +12 -3
  6. package/dist/{android-cas-8U4DPqeR.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
  7. package/dist/{app-install-CKWdbXIX.mjs → app-install-VPkI5WEg.mjs} +6 -5
  8. package/dist/{budget-DjrURfQn.mjs → budget-CUDz870h.mjs} +62 -25
  9. package/dist/{build-plan-DjeSb2Xl.mjs → build-plan-BJSXzsCL.mjs} +345 -86
  10. package/dist/{idle-DYVymG92.mjs → build-progress-D0WkjCPd.mjs} +170 -233
  11. package/dist/{build-slots-rUk77xqh.mjs → build-slots-41v_d2x9.mjs} +35 -25
  12. package/dist/{cli-BkYZ_6hy.mjs → cli-BLQqDGlX.mjs} +17 -17
  13. package/dist/cli.mjs +1 -1
  14. package/dist/{client-BTY686R6.mjs → client-CxNotugx.mjs} +313 -44
  15. package/dist/collector-run.mjs +3 -3
  16. package/dist/{command-output-DnJMdEY-.mjs → command-output-CDAIFD-8.mjs} +3 -3
  17. package/dist/{created-devices-D5owjN4C.mjs → created-devices-CpvzBL25.mjs} +1 -1
  18. package/dist/{dependency-state-B6cMXZab.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
  19. package/dist/{deps-urbmfXig.mjs → deps-CVxlsRFn.mjs} +1 -1
  20. package/dist/{dev-client-CroAIjjj.mjs → dev-client-B-XC4uRH.mjs} +2 -2
  21. package/dist/{device-DZQjF7H5.mjs → device-C8IOoJPv.mjs} +7 -7
  22. package/dist/device-capacity-ZC4XSZ4R.mjs +580 -0
  23. package/dist/device-host-worker.mjs +436 -154
  24. package/dist/{device-ios-BadmeL2V.mjs → device-ios-C2Um-XUR.mjs} +75 -44
  25. package/dist/{device-lease-D-hLqx1b.mjs → device-lease-DpoS36_u.mjs} +2 -2
  26. package/dist/{device-lease-run-CqjShqWW.mjs → device-lease-run-B8YltIH_.mjs} +3 -3
  27. package/dist/{device-pool-CUaEWMMm.mjs → device-pool-3o7s5lo4.mjs} +2 -2
  28. package/dist/{device-remote-D5WVWqOq.mjs → device-remote-hFSkpfQu.mjs} +12 -12
  29. package/dist/{doctor-Cimf-gGB.mjs → doctor-BF_435OC.mjs} +40 -58
  30. package/dist/{error-diagnostics-B9L4Ae0x.mjs → error-diagnostics-C-NMnhrk.mjs} +7 -7
  31. package/dist/{gc-Bqy4bjEu.mjs → gc-BsWKa_-d.mjs} +313 -30
  32. package/dist/{gradle-BsO1MdsH.mjs → gradle-bsBRy6i9.mjs} +3 -3
  33. package/dist/{guide-B_uyKTxW.mjs → guide-DC3n6-NH.mjs} +1776 -655
  34. package/dist/{guide-status-CjEZMAD4.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
  35. package/dist/hosted-android-DgU31wY3.mjs +32 -0
  36. package/dist/{hosted-client-a-WmqE2L.mjs → hosted-client-CdSoH-G6.mjs} +5 -6
  37. package/dist/hosted-ios-BTaVj4Jb.mjs +15 -0
  38. package/dist/{hosted-logs-D5g041Q3.mjs → hosted-logs-SxfBvSvc.mjs} +16 -16
  39. package/dist/{hosted-macos-BSZyIb3y.mjs → hosted-macos-CT9E3meL.mjs} +4 -4
  40. package/dist/{hosted-ios-CIWelIqf.mjs → hosted-native-CZLOrMrN.mjs} +77 -42
  41. package/dist/idle-BXG__1BA.mjs +176 -0
  42. package/dist/{idle-shutdown-CTsDnAde.mjs → idle-shutdown-IGyNV8Cr.mjs} +25 -17
  43. package/dist/{in-use-DvBw0Kxo.mjs → in-use-CvGOUn-x.mjs} +11 -11
  44. package/dist/{ios-aq3QoGI3.mjs → ios-C7JuT1bI.mjs} +173 -91
  45. package/dist/{ios-BaInn3sW.mjs → ios-DSfnPfse.mjs} +4 -4
  46. package/dist/{ios-device-FfCN_C8p.mjs → ios-device-pAN4t54-.mjs} +1 -1
  47. package/dist/ios-state-8L3lgum6.mjs +89 -0
  48. package/dist/{launch-verify-2DBRizjo.mjs → launch-verify-lTnWZikR.mjs} +2 -2
  49. package/dist/{logs-CHEVK8zD.mjs → logs-DNxKcok9.mjs} +36 -23
  50. package/dist/{macos-CSYEAFmF.mjs → macos-BEs8cwKx.mjs} +75 -34
  51. package/dist/macos-run.mjs +1 -1
  52. package/dist/maintenance-run.mjs +2 -2
  53. package/dist/{metro-BeK1ZPz1.mjs → metro-g8NqiJp-.mjs} +1 -1
  54. package/dist/{metro-gateway-BJ-rxQAB.mjs → metro-gateway-CHqK0p-a.mjs} +22 -3
  55. package/dist/{named-ports-Bk_YXEmE.mjs → named-ports-C8m8EQs2.mjs} +2 -2
  56. package/dist/{native-run-NaBrRFtQ.mjs → native-run-D1Q56rAD.mjs} +17 -6
  57. package/dist/{native-runtime-DBzRnEKS.mjs → native-runtime-YhaSxP4v.mjs} +6 -6
  58. package/dist/offload-worker.mjs +9 -9
  59. package/dist/{teardown-CL6vDs2Y.mjs → ownership-BMLqyBgJ.mjs} +603 -26
  60. package/dist/{ownership-DiA16AsN.mjs → ownership-DyRQru0q.mjs} +2 -2
  61. package/dist/{ports-R2k4Knz3.mjs → ports-4nYqxPWC.mjs} +2 -2
  62. package/dist/{prebuild-D2fsca_Z.mjs → prebuild-D7RUMkwN.mjs} +3 -3
  63. package/dist/{preview-DhVbvPAZ.mjs → preview-C_JjkEy4.mjs} +7 -7
  64. package/dist/{project-Du91Ilvc.mjs → project-D47Rh0ly.mjs} +7 -2
  65. package/dist/{recordings-BIXOzVnr.mjs → recordings-BNgsD1Tr.mjs} +1 -1
  66. package/dist/{reload-rM9jV1fz.mjs → reload-byOjE3P4.mjs} +62 -35
  67. package/dist/{remote-cache-C8-V13XN.mjs → remote-cache-Ci4DQYYj.mjs} +3 -3
  68. package/dist/{run-f4vI6EnK.mjs → run-CkE9y0nc.mjs} +1 -1
  69. package/dist/{server-bare-CpPGmnmO.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
  70. package/dist/{server-expo-MC0rHlOC.mjs → server-expo-CHTyXD0G.mjs} +2 -2
  71. package/dist/server-expo-CeakhI8P.mjs +2 -0
  72. package/dist/{settings-oTSkkNrM.mjs → settings-Cfd-0r2d.mjs} +10 -9
  73. package/dist/{settings-ZkDzkzvB.mjs → settings-SR9OmW9h.mjs} +3 -3
  74. package/dist/settings.schema.json +123 -76
  75. package/dist/{simslim-B8-7AoTI.mjs → simslim-BWNrc3M3.mjs} +3 -3
  76. package/dist/{slot-launch-B8VrHxn1.mjs → slot-launch-DmjBmpQV.mjs} +8 -8
  77. package/dist/{start-CX8Ig-U2.mjs → start-BEP-XmEA.mjs} +1 -1
  78. package/dist/{start-ClDfwXa4.mjs → start-BHIersSm.mjs} +11 -11
  79. package/dist/{state-DcW6vOd_.mjs → state-BKa8wNN5.mjs} +1 -1
  80. package/dist/{state-3DwSQIBK.mjs → state-DyaNTgfR.mjs} +1 -1
  81. package/dist/{state-C1jB7Q8q.mjs → state-PeQiJuIu.mjs} +2 -2
  82. package/dist/{stats-CEZ_i5je.mjs → stats-Bq4fJOdB.mjs} +7 -4
  83. package/dist/{status-Cs7VLg9D.mjs → status-CqeWbIWW.mjs} +128 -38
  84. package/dist/{status-BuvBFeII.mjs → status-CzS4bPO9.mjs} +4 -4
  85. package/dist/{stop-Bffozsac.mjs → stop-Cj01je8D.mjs} +48 -27
  86. package/dist/{stop-B0Yxtmiy.mjs → stop-D03prQnW.mjs} +4 -4
  87. package/dist/{stop-Dt_dM16A.mjs → stop-DOxwkH4j.mjs} +2 -2
  88. package/dist/{stop-cause-CidxIqup.mjs → stop-cause-rhmGfwvt.mjs} +1 -1
  89. package/dist/supervisor-run.d.mts +7 -4
  90. package/dist/supervisor-run.mjs +26 -28
  91. package/dist/{support-D4aYJniO.mjs → support-BE-tbOP9.mjs} +45 -20
  92. package/dist/{support-CAP8qDUF.mjs → support-WvD__tSV.mjs} +7 -7
  93. package/dist/{toolchain-DkUflduf.mjs → toolchain-CdrxgUeA.mjs} +8 -8
  94. package/dist/{warm-progress-CpGv3Wb6.mjs → warm-progress-DF4JRvYI.mjs} +3 -3
  95. package/dist/watchman-APYjXSQa.mjs +48 -0
  96. package/dist/{web-DWUaMp8P.mjs → web-DddUdJzr.mjs} +12 -13
  97. package/dist/web-run.mjs +4 -4
  98. package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
  99. package/dist/{workspace-state-B8-aJ1_5.mjs → workspace-state-DqcCbpE7.mjs} +2 -2
  100. package/dist/{workspaces-BV4pdHFz.mjs → workspaces-Bcib97-6.mjs} +3 -3
  101. package/dist/{worktree-K-xj9Db0.mjs → worktree-AK_m-d_m.mjs} +141 -40
  102. package/dist/{worktree-jnG8Iph5.mjs → worktree-CsAjSmEo.mjs} +1 -1
  103. package/package.json +5 -5
  104. package/dist/device-capacity-BHwxNBlu.mjs +0 -136
  105. package/dist/ios-state-DS5UpYTH.mjs +0 -48
  106. package/dist/machines-BIBUdhpj.mjs +0 -265
  107. package/dist/ownership-hCkPzGmr.mjs +0 -571
  108. package/dist/server-expo-CVqO2c35.mjs +0 -2
@@ -1,28 +1,34 @@
1
1
  import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
- import { D as withConfigLock$1, H as projectDeviceSlots, _ as loadConfig$1, a as clearDevice, b as releaseAndroidConsolePort, i as clearAndroidBootPending, k as acquireAvdClaim, t as allConsolePortsAndSerials, w as setDevice, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
2
+ import { D as withConfigLock$1, H as projectDeviceSlots, _ as loadConfig$1, a as clearDevice, b as releaseAndroidConsolePort, i as clearAndroidBootPending, k as acquireAvdClaim, p as getProject, t as allConsolePortsAndSerials, w as setDevice, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
3
+ import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
3
4
  import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
- import { i as formatDuration, l as phaseLine, m as shortHash, u as plural } from "./command-output-DnJMdEY-.mjs";
5
- import { Tt as removeTemporaryEntry, at as gitCommonDirOnDisk, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, wt as makeTemporaryDirectory } from "./settings-oTSkkNrM.mjs";
6
- import { l as ownedDeviceLabel } from "./project-Du91Ilvc.mjs";
7
- import { o as pidExists } from "./metro-BeK1ZPz1.mjs";
8
- import { d as sharedBuildCache } from "./workspace-state-B8-aJ1_5.mjs";
9
- import { C as diffFingerprintSources, I as storedSources, w as entryDir, y as artifactIn } from "./device-lease-D-hLqx1b.mjs";
10
- import { b as readStats$1, p as estimateBuild } from "./idle-DYVymG92.mjs";
11
- import { A as listAvds, B as ownedAvdSystemImage, E as getAvdNameForSerial, F as ownedAvdDeviceProfile, J as resolveOwnedAvdSerial, L as ownedAvdMatchesConfiguration, O as listAdbDevices, P as nextConsolePort, R as ownedAvdName, W as pickDefaultSystemImage, b as createOwnedAvd, f as assertOwnedAvdStopped, ft as parkedMaxSetting, g as avdPoolConfiguration, j as listInstalledSystemImages, n as OWNED_AVD_CONFIG_DEFAULTS, pt as readParked, st as adoptParked, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, ut as isLegacyDeletionClaim, v as bootAndroidEmulator, y as configureNewOwnedAvd } from "./android-B_NumVyQ.mjs";
12
- import { j as readHostMemoryPressure, u as listAllIosSims } from "./ios-BaInn3sW.mjs";
13
- import { i as teardownOwnedAvd, s as teardownParkedAvd } from "./teardown-CL6vDs2Y.mjs";
14
- import { b as tailLines, u as HEARTBEAT_INTERVAL_MS, y as startBuildHeartbeat } from "./toolchain-DkUflduf.mjs";
15
- import { t as cleanLine } from "./server-expo-MC0rHlOC.mjs";
16
- import { t as clearDeviceIdleShutdown } from "./idle-shutdown-CTsDnAde.mjs";
17
- import { i as liveOwnedDeviceCount } from "./device-capacity-BHwxNBlu.mjs";
18
- import { i as staleNativeDirRefusal } from "./prebuild-D2fsca_Z.mjs";
19
- import { i as ensureOwnedIosDevice, r as ensureIosBooted } from "./device-ios-BadmeL2V.mjs";
20
- import { a as easAuthNote } from "./remote-cache-C8-V13XN.mjs";
21
- import { t as reconcileSimSlim } from "./simslim-B8-7AoTI.mjs";
5
+ import { i as formatDuration, l as phaseLine, m as shortHash, u as plural } from "./command-output-CDAIFD-8.mjs";
6
+ import { Tt as removeTemporaryEntry, a as androidAvdConfigSetting, at as gitCommonDirOnDisk, c as androidDataPartitionSizeGbSetting, wt as makeTemporaryDirectory } from "./settings-Cfd-0r2d.mjs";
7
+ import { u as ownedDeviceLabel } from "./project-D47Rh0ly.mjs";
8
+ import { o as pidExists } from "./metro-g8NqiJp-.mjs";
9
+ import { d as sharedBuildCache } from "./workspace-state-DqcCbpE7.mjs";
10
+ import { o as peekBudget } from "./budget-CUDz870h.mjs";
11
+ import { C as diffFingerprintSources, I as storedSources, w as entryDir, y as artifactIn } from "./device-lease-DpoS36_u.mjs";
12
+ import { A as listAvds, B as ownedAvdSystemImage, E as getAvdNameForSerial, F as ownedAvdDeviceProfile, J as resolveOwnedAvdSerial, L as ownedAvdMatchesConfiguration, O as listAdbDevices, P as nextConsolePort, R as ownedAvdName, W as pickDefaultSystemImage, b as createOwnedAvd, f as assertOwnedAvdStopped, ft as parkedMaxSetting, g as avdPoolConfiguration, j as listInstalledSystemImages, n as OWNED_AVD_CONFIG_DEFAULTS, pt as readParked, st as adoptParked, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, ut as isLegacyDeletionClaim, v as bootAndroidEmulator, y as configureNewOwnedAvd } from "./android-B_YNZy61.mjs";
13
+ import { j as readHostMemoryPressure, u as listAllIosSims } from "./ios-DSfnPfse.mjs";
14
+ import { _ as teardownParkedAvd, m as teardownOwnedAvd } from "./ownership-BMLqyBgJ.mjs";
15
+ import { o as estimateBuild, p as readStats$1 } from "./build-progress-D0WkjCPd.mjs";
16
+ import { b as tailLines, u as HEARTBEAT_INTERVAL_MS, y as startBuildHeartbeat } from "./toolchain-CdrxgUeA.mjs";
17
+ import { t as cleanLine } from "./server-expo-CHTyXD0G.mjs";
18
+ import { f as namedBuildMachine, m as resolveBuildMachine, v as hostingMachines } from "./client-CxNotugx.mjs";
19
+ import { a as call, o as connectHost } from "./hosted-client-CdSoH-G6.mjs";
20
+ import { t as clearDeviceIdleShutdown } from "./idle-shutdown-IGyNV8Cr.mjs";
21
+ import { i as countLiveOwnedDevices, n as DeviceAdmissionRefusal, p as withDeviceBootAdmission, s as peekDeviceSlots } from "./device-capacity-ZC4XSZ4R.mjs";
22
+ import { i as staleNativeDirRefusal } from "./prebuild-D7RUMkwN.mjs";
23
+ import { i as ensureOwnedIosDevice, r as ensureIosBooted } from "./device-ios-C2Um-XUR.mjs";
24
+ import { r as readHostedNative, s as writeHostedNative } from "./ios-state-8L3lgum6.mjs";
25
+ import { n as prepareHostedNative } from "./hosted-native-CZLOrMrN.mjs";
26
+ import { a as easAuthNote } from "./remote-cache-Ci4DQYYj.mjs";
27
+ import { t as reconcileSimSlim } from "./simslim-BWNrc3M3.mjs";
22
28
  import { randomUUID } from "node:crypto";
23
29
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
24
30
  import { basename, dirname, join, relative } from "node:path";
25
- import { LAST_BUILD_KEYS, loadConfig, readWorkspaceState } from "@stim-cli/core/state";
31
+ import { LAST_BUILD_KEYS, isJsonObject, loadConfig, machineCapacity, parseHostedChoice, parseHostedNativeOffer, readWorkspaceState } from "@stim-cli/core/state";
26
32
  import { claimRemoveCommand, clearClaimChild, markClaimChildPending, processGroupAlive, releaseClaim, setClaimChild } from "@stim-cli/core/ownership-claim";
27
33
  import { captureProcessIdentity } from "@stim-cli/core/process-identity";
28
34
  import chalk from "chalk";
@@ -30,6 +36,219 @@ import { isDeepStrictEqual } from "node:util";
30
36
  import * as expoFingerprint from "@expo/fingerprint";
31
37
  import { resolveTieredBuild } from "@stim-cli/cache";
32
38
  import { request } from "node:http";
39
+ //#region src/device-host/placement.ts
40
+ function local$1(reason, skipped = []) {
41
+ return {
42
+ kind: "local",
43
+ reason,
44
+ skipped
45
+ };
46
+ }
47
+ function decideDevicePlacement(inputs) {
48
+ if ("sticky" in inputs) {
49
+ if ("machine" in inputs.sticky) {
50
+ const reason = `recorded session on ${inputs.sticky.machine}`;
51
+ return {
52
+ kind: "host",
53
+ reason,
54
+ machines: [{
55
+ machine: inputs.sticky.machine,
56
+ reason
57
+ }],
58
+ skipped: []
59
+ };
60
+ }
61
+ return {
62
+ kind: "local",
63
+ reason: "this workspace's device runs here",
64
+ skipped: []
65
+ };
66
+ }
67
+ const { platform, here, offers, buildMachine, noWait } = inputs;
68
+ const { count, max, queued } = here.devices;
69
+ const cannotTake = !(max === 0 || count !== null && count < max) || queued > 0;
70
+ const usage = max > 0 ? `${count} of ${max} devices in use` : `${count} devices in use, no device cap`;
71
+ const localReason = count === null ? "cannot tell how many devices run here; local boot admission decides" : cannotTake ? `${usage}${queued ? `, ${queued} runs queued ahead` : ""}` : here.budgetRefusal ?? (here.memoryPressure !== "normal" ? `host memory pressure ${here.memoryPressure ?? "unknown"} here` : `load ${here.loadPerCore.toFixed(1)}/core here, ${usage}`);
72
+ if (!offers.length) return local$1("no remote Macs configured; " + localReason);
73
+ if (count === null) return local$1(localReason);
74
+ if (!cannotTake && here.memoryPressure === "normal" && !here.budgetRefusal && here.loadPerCore < here.maxLoadPerCore) return local$1(localReason);
75
+ const skipped = [];
76
+ const admitted = [];
77
+ offers.forEach((probe, index) => {
78
+ let reason = null;
79
+ if ("failure" in probe) reason = probe.failure;
80
+ else {
81
+ const { offer } = probe;
82
+ if (offer.declined !== null) reason = `declined: ${offer.declined}`;
83
+ else if (offer.platform !== platform || !offer.choice) reason = `no matching ${platform} device choice`;
84
+ else if (offer.resources.memoryPressure !== "normal") reason = "host memory pressure unknown or elevated";
85
+ else if (offer.capacity.available === 0) reason = "no hosted device capacity available";
86
+ else if (!cannotTake && (offer.resources.loadPerCore === null || offer.resources.loadPerCore >= here.loadPerCore)) reason = offer.resources.loadPerCore === null ? "host load unknown while this Mac has room" : "host is not less loaded than this Mac";
87
+ else admitted.push({
88
+ machine: probe.machine,
89
+ offer,
90
+ index
91
+ });
92
+ }
93
+ if (reason) skipped.push({
94
+ machine: probe.machine,
95
+ reason
96
+ });
97
+ });
98
+ admitted.sort((a, b) => Number(b.machine === buildMachine) - Number(a.machine === buildMachine) || (a.offer.resources.loadPerCore ?? Infinity) - (b.offer.resources.loadPerCore ?? Infinity) || b.offer.resources.memoryFreeBytes - a.offer.resources.memoryFreeBytes || a.index - b.index);
99
+ if (!admitted.length) return local$1(`${localReason}; no host admits${cannotTake && !noWait ? "; waiting locally if needed" : ""}`, skipped);
100
+ const machines = admitted.map(({ machine, offer }) => ({
101
+ machine,
102
+ reason: `${localReason}; ${machine}${machine === buildMachine ? " is the explicit remote Mac," : ""} load ${offer.resources.loadPerCore === null ? "unknown" : offer.resources.loadPerCore.toFixed(1) + "/core"}`
103
+ }));
104
+ return {
105
+ kind: "host",
106
+ machines,
107
+ reason: machines[0].reason,
108
+ skipped
109
+ };
110
+ }
111
+ //#endregion
112
+ //#region src/device-host/auto-placement.ts
113
+ async function probeHost(machine, platform, selectors) {
114
+ let host;
115
+ const deadline = Date.now() + 3e3;
116
+ try {
117
+ host = await connectHost(machine, 3e3, true);
118
+ const offer = await call(host, "device-host.offer", {
119
+ platform,
120
+ ...selectors
121
+ }, Math.max(1, deadline - Date.now()));
122
+ const resources = isJsonObject(offer.resources) ? offer.resources : {};
123
+ const parsed = parseHostedNativeOffer({
124
+ ...offer,
125
+ resources: {
126
+ ...resources,
127
+ loadPerCore: resources.loadPerCore ?? 0
128
+ }
129
+ });
130
+ let choice = platform === "ios" ? offer.platform === "ios" ? parseHostedChoice(offer.choice) : null : parsed?.platform === "android" ? parsed.choice : null;
131
+ if (choice && ("runtime" in choice ? selectors.deviceType && selectors.deviceType !== choice.deviceType || selectors.runtime && selectors.runtime.replace(/^iOS /, "") !== choice.runtime.replace(/^iOS /, "") : selectors.systemImage && selectors.systemImage !== choice.systemImage || selectors.deviceProfile && selectors.deviceProfile !== choice.deviceProfile)) choice = null;
132
+ if (!isJsonObject(offer.capacity) || offer.declined !== null && typeof offer.declined !== "string") throw new Error("unreadable device offer");
133
+ return {
134
+ probe: {
135
+ machine,
136
+ offer: {
137
+ platform,
138
+ choice,
139
+ declined: offer.declined,
140
+ capacity: { available: typeof offer.capacity.available === "number" ? offer.capacity.available : null },
141
+ resources: {
142
+ loadPerCore: typeof resources.loadPerCore === "number" && Number.isFinite(resources.loadPerCore) && resources.loadPerCore >= 0 ? resources.loadPerCore : null,
143
+ memoryFreeBytes: typeof resources.memoryFreeBytes === "number" ? resources.memoryFreeBytes : 0,
144
+ memoryPressure: typeof resources.memoryPressure === "string" ? resources.memoryPressure : null
145
+ }
146
+ }
147
+ },
148
+ ...choice ? { target: {
149
+ host,
150
+ choice,
151
+ session: null
152
+ } } : {}
153
+ };
154
+ } catch (error) {
155
+ return { probe: {
156
+ machine,
157
+ failure: error instanceof Error ? error.message : String(error)
158
+ } };
159
+ } finally {
160
+ host?.connection.close();
161
+ }
162
+ }
163
+ function hosted(target, reason, skipped = []) {
164
+ target.selection = {
165
+ selected: "auto",
166
+ reason
167
+ };
168
+ return {
169
+ target,
170
+ placement: {
171
+ decision: "hosted",
172
+ machine: target.host.machine,
173
+ reason
174
+ },
175
+ skipped
176
+ };
177
+ }
178
+ function local(reason, skipped = []) {
179
+ return {
180
+ target: null,
181
+ placement: {
182
+ decision: "local",
183
+ reason
184
+ },
185
+ skipped
186
+ };
187
+ }
188
+ async function automaticDevicePlacement({ root, slot, platform, selectors, buildMachine, noWait }, { machines = hostingMachines, read = readHostedNative, write = writeHostedNative, resume = prepareHostedNative, peek = () => peekDeviceSlots({
189
+ platform,
190
+ project: getProject(root),
191
+ slot
192
+ }), capacity = machineCapacity, memory = readHostMemoryPressure, probe = probeHost, budget = peekBudget } = {}) {
193
+ const recorded = read(root, platform, slot)[slot];
194
+ if (recorded) {
195
+ const target = await resume(recorded.machine, selectors, recorded, platform, true);
196
+ if (target) return {
197
+ target,
198
+ placement: {
199
+ decision: "hosted",
200
+ machine: recorded.machine,
201
+ reason: recorded.reason ?? decideDevicePlacement({ sticky: { machine: recorded.machine } }).reason
202
+ },
203
+ skipped: [],
204
+ sticky: true
205
+ };
206
+ write(root, slot, null, platform);
207
+ }
208
+ const devices = peek();
209
+ if (devices.localLive) return {
210
+ ...local(decideDevicePlacement({ sticky: { local: true } }).reason),
211
+ sticky: true
212
+ };
213
+ const entries = machines();
214
+ if (entries === null) throw Object.assign(/* @__PURE__ */ new Error("remote.machines is invalid. Run stim guide settings and correct it."), { code: "STIM_HOSTING_REFUSED" });
215
+ const preference = resolveBuildMachine(buildMachine, process.env.STIM_REMOTE_BUILD, loadConfig$1()?.remote?.build);
216
+ buildMachine = namedBuildMachine(preference) ? preference : void 0;
217
+ const load = capacity();
218
+ const here = {
219
+ loadPerCore: load.loadPerCore,
220
+ maxLoadPerCore: load.maxLoadPerCore,
221
+ memoryPressure: memory(),
222
+ devices,
223
+ budgetRefusal: await budget(root)
224
+ };
225
+ const early = decideDevicePlacement({
226
+ platform,
227
+ here,
228
+ offers: entries.map((machine) => ({
229
+ machine,
230
+ failure: "not probed"
231
+ })),
232
+ buildMachine,
233
+ noWait
234
+ });
235
+ if (early.kind === "local" && early.skipped.length === 0) return local(early.reason);
236
+ const probes = await Promise.all(entries.map((machine) => probe(machine, platform, selectors)));
237
+ const decision = decideDevicePlacement({
238
+ platform,
239
+ here,
240
+ offers: probes.map((each) => each.probe),
241
+ buildMachine,
242
+ noWait
243
+ });
244
+ if (decision.kind === "local") return local(decision.reason, decision.skipped);
245
+ const target = probes.find((each) => each.probe.machine === decision.machines[0].machine).target;
246
+ return hosted(target, decision.reason, decision.skipped);
247
+ }
248
+ function devicePlacementLine(placement, skipped) {
249
+ return `${placement.machine ?? "local"}, ${placement.reason}${placement.decision !== "hosted" && skipped.length ? "; " + skipped.map((each) => `${each.machine}: ${each.reason}`).join("; ") : ""}`;
250
+ }
251
+ //#endregion
33
252
  //#region src/engine/metro-warmup.ts
34
253
  const WARMUP_TIMEOUT_MS = 6e4;
35
254
  async function requestLocal(url, platform, prefetch) {
@@ -325,6 +544,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
325
544
  out(chalk.dim(`Recorded port for owned AVD ${record.avdName} is not currently ours; booting it on a freshly allocated port...`));
326
545
  return {
327
546
  ...await bootOwnedAvdOnFreshPort({
547
+ deviceSlotWait: flags.deviceSlotWait,
328
548
  avdName: record.avdName,
329
549
  metadata: record,
330
550
  projectPath,
@@ -397,6 +617,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
397
617
  }
398
618
  return {
399
619
  ...await bootOwnedAvdOnFreshPort({
620
+ deviceSlotWait: flags.deviceSlotWait,
400
621
  avdName: parked.name,
401
622
  projectPath,
402
623
  slot,
@@ -435,6 +656,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
435
656
  }
436
657
  return {
437
658
  ...await bootOwnedAvdOnFreshPort({
659
+ deviceSlotWait: flags.deviceSlotWait,
438
660
  avdName: created.avdName,
439
661
  metadata: created.created ? {
440
662
  poolConfiguration: configuration,
@@ -472,41 +694,50 @@ function liveAndroidConsolePorts() {
472
694
  const adbLive = listAdbDevices();
473
695
  return [...adbLive.emulators.map((e) => e.consolePort), ...adbLive.unhealthy.map((u) => u.consolePort).filter((p) => p != null)];
474
696
  }
475
- async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, slot, deviceName, out, logFile = null, alive = pidExists }) {
476
- const claim = claimAndroidConsolePort({
477
- projectPath,
478
- slot,
479
- avdName,
480
- deviceName,
481
- livePorts: liveAndroidConsolePorts(),
482
- metadata
483
- });
484
- const serial = `emulator-${claim.consolePort}`;
485
- try {
486
- reportAndroidMemoryPressure(out);
487
- const pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
488
- out(chalk.dim(phaseLine("device", `waiting for ${serial} to finish booting`)));
489
- const result = await waitForAndroidBoot({
490
- serial,
491
- timeoutMs: ANDROID_FRESH_BOOT_TIMEOUT_MS,
492
- pid,
493
- alive,
494
- out
697
+ async function bootOwnedAvdOnFreshPort({ avdName, deviceSlotWait, metadata, projectPath, slot, deviceName, out, logFile = null, alive = pidExists }) {
698
+ return withDeviceBootAdmission({
699
+ platform: "android",
700
+ key: avdName
701
+ }, async () => {
702
+ const claim = claimAndroidConsolePort({
703
+ projectPath,
704
+ slot,
705
+ avdName,
706
+ deviceName,
707
+ livePorts: liveAndroidConsolePorts(),
708
+ metadata
495
709
  });
496
- if (result.failed) throw new AvdBootError(result.reason, result.remedy);
497
- const running = getAvdNameForSerial(serial);
498
- if (running && running !== avdName) throw new Error(`${serial} is running AVD ${running}, not this workspace's owned AVD ${avdName}; refusing to use it.`);
499
- clearAndroidBootPending(projectPath, avdName, slot);
500
- const { bootPending: _booted, ...booted } = claim;
501
- return {
502
- ...booted,
503
- serial,
504
- setup: true
505
- };
506
- } catch (error) {
507
- releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
508
- throw error;
509
- }
710
+ const serial = `emulator-${claim.consolePort}`;
711
+ try {
712
+ reportAndroidMemoryPressure(out);
713
+ const pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
714
+ out(chalk.dim(phaseLine("device", `waiting for ${serial} to finish booting`)));
715
+ const result = await waitForAndroidBoot({
716
+ serial,
717
+ timeoutMs: ANDROID_FRESH_BOOT_TIMEOUT_MS,
718
+ pid,
719
+ alive,
720
+ out
721
+ });
722
+ if (result.failed) throw new AvdBootError(result.reason, result.remedy);
723
+ const running = getAvdNameForSerial(serial);
724
+ if (running && running !== avdName) throw new Error(`${serial} is running AVD ${running}, not this workspace's owned AVD ${avdName}; refusing to use it.`);
725
+ clearAndroidBootPending(projectPath, avdName, slot);
726
+ const { bootPending: _booted, ...booted } = claim;
727
+ return {
728
+ ...booted,
729
+ serial,
730
+ setup: true
731
+ };
732
+ } catch (error) {
733
+ releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
734
+ throw error;
735
+ }
736
+ }, {
737
+ ...deviceSlotWait,
738
+ root: projectPath,
739
+ out
740
+ });
510
741
  }
511
742
  function reportAndroidMemoryPressure(out) {
512
743
  const pressure = readHostMemoryPressure();
@@ -515,11 +746,15 @@ function reportAndroidMemoryPressure(out) {
515
746
  function androidBootRemedy(pressure) {
516
747
  let count = null;
517
748
  try {
518
- count = liveOwnedDeviceCount({
519
- sims: process.platform === "darwin" ? listAllIosSims({ timeoutMs: 2e3 }) : [],
520
- adbEmulators: listAdbDevices({ timeoutMs: 2e3 }).emulators,
521
- config: loadConfig$1()
749
+ const live = countLiveOwnedDevices({
750
+ sims: () => process.platform === "darwin" ? listAllIosSims({ timeoutMs: 2e3 }) : [],
751
+ adb: () => ({
752
+ ...listAdbDevices({ timeoutMs: 2e3 }),
753
+ unhealthy: []
754
+ }),
755
+ booting: []
522
756
  });
757
+ count = typeof live === "number" ? live : null;
523
758
  } catch {}
524
759
  const observation = pressure === "warning" || pressure === "critical" ? `macOS reports ${pressure} host memory pressure. ` : "";
525
760
  const devices = count ? `${count} Stim-owned ${count === 1 ? "device is" : "devices are"} running. ` : "";
@@ -557,7 +792,7 @@ async function waitForAndroidBoot({ serial, timeoutMs, pid = null, alive = pidEx
557
792
  remedy: exited ? "Inspect the emulator log for the process exit, fix what it reports, then run `stim android` again." : androidBootRemedy(pressure)
558
793
  };
559
794
  }
560
- async function ensureAndroidBooted({ device, projectPath, slot, timeoutMs, out, logFile = null, alive = pidExists }) {
795
+ async function ensureAndroidBooted({ device, deviceSlotWait, projectPath, slot, timeoutMs, out, logFile = null, alive = pidExists }) {
561
796
  if (!device?.avdName || !projectPath) return {
562
797
  failed: true,
563
798
  reason: "No owned Android emulator is recorded for this project."
@@ -595,36 +830,57 @@ async function ensureAndroidBooted({ device, projectPath, slot, timeoutMs, out,
595
830
  timeoutMs,
596
831
  out
597
832
  }));
598
- const claim = claimAndroidConsolePort({
599
- projectPath,
600
- slot,
601
- avdName: device.avdName,
602
- deviceName: device.deviceName,
603
- livePorts: liveAndroidConsolePorts(),
604
- metadata: device
605
- });
606
- const serial = `emulator-${claim.consolePort}`;
607
- reportAndroidMemoryPressure(out);
608
- out(chalk.dim(phaseLine("device", `booting ${device.avdName} as ${serial}`)));
609
- let pid = null;
833
+ const boot = async () => {
834
+ const claim = claimAndroidConsolePort({
835
+ projectPath,
836
+ slot,
837
+ avdName,
838
+ deviceName: device.deviceName,
839
+ livePorts: liveAndroidConsolePorts(),
840
+ metadata: device
841
+ });
842
+ const serial = `emulator-${claim.consolePort}`;
843
+ reportAndroidMemoryPressure(out);
844
+ out(chalk.dim(phaseLine("device", `booting ${avdName} as ${serial}`)));
845
+ let pid = null;
846
+ try {
847
+ pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
848
+ } catch (e) {
849
+ releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
850
+ return {
851
+ failed: true,
852
+ reason: `Could not start emulator for AVD ${avdName}: ${e?.message || e}`
853
+ };
854
+ }
855
+ const result = await waitForAndroidBoot({
856
+ serial,
857
+ timeoutMs,
858
+ pid,
859
+ alive,
860
+ out
861
+ });
862
+ if (result.failed) releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
863
+ return booted(result);
864
+ };
610
865
  try {
611
- pid = bootAndroidEmulator(device.avdName, claim.consolePort, { logFile });
612
- } catch (e) {
613
- releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
866
+ return await withDeviceBootAdmission({
867
+ platform: "android",
868
+ key: avdName
869
+ }, boot, {
870
+ ...deviceSlotWait,
871
+ root: projectPath,
872
+ out
873
+ });
874
+ } catch (error) {
875
+ const refusal = error instanceof DeviceAdmissionRefusal ? error : (0, ownership_claim_exports.claimFailure)(error, "stim android");
876
+ if (!refusal) throw error;
614
877
  return {
615
878
  failed: true,
616
- reason: `Could not start emulator for AVD ${device.avdName}: ${e?.message || e}`
879
+ code: refusal.code,
880
+ reason: refusal.message,
881
+ remedy: refusal.remedy
617
882
  };
618
883
  }
619
- const result = await waitForAndroidBoot({
620
- serial,
621
- timeoutMs,
622
- pid,
623
- alive,
624
- out
625
- });
626
- if (result.failed) releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
627
- return booted(result);
628
884
  }
629
885
  //#endregion
630
886
  //#region src/engine/device.ts
@@ -668,9 +924,11 @@ async function ensureOwnedDevice({ platform, project, projectPath, slot, setting
668
924
  });
669
925
  }
670
926
  const BOOT_POLL_MS = 500;
671
- async function ensureBooted({ platform, device, simulatorApp, timeoutMs, pollMs = BOOT_POLL_MS, out = () => {}, logFile = null, alive = pidExists, projectPath, slot } = {}) {
927
+ async function ensureBooted({ platform, device, simulatorApp, timeoutMs, pollMs = BOOT_POLL_MS, out = () => {}, logFile = null, alive = pidExists, projectPath, slot, deviceSlotWait }) {
672
928
  if (platform === "ios") return ensureIosBooted({
673
929
  device,
930
+ projectPath,
931
+ deviceSlotWait,
674
932
  simulatorApp,
675
933
  timeoutMs: timeoutMs ?? 6e5,
676
934
  pollMs,
@@ -678,6 +936,7 @@ async function ensureBooted({ platform, device, simulatorApp, timeoutMs, pollMs
678
936
  });
679
937
  if (platform === "android") return ensureAndroidBooted({
680
938
  device,
939
+ deviceSlotWait,
681
940
  projectPath,
682
941
  slot,
683
942
  timeoutMs: timeoutMs ?? ANDROID_BOOT_TIMEOUT_MS,
@@ -1523,4 +1782,4 @@ function planFlagRefusal(flag) {
1523
1782
  };
1524
1783
  }
1525
1784
  //#endregion
1526
- export { AvdRecoveryError as _, refusePlan as a, skippedMissReason as c, refreshUpdatesManifestFile as d, swapJsBundle as f, AvdBootError as g, ensureOwnedDevice as h, printPlan as i, UPDATES_MANIFEST_NAME as l, ensureBooted as m, planFlagRefusal as n, explainBuildMiss as o, didSetUpDevice as p, planPayload as r, fingerprintErrorMissReason as s, planCachedBuild as t, detectEntryFile as u, warmMetro as v };
1785
+ export { AvdRecoveryError as _, refusePlan as a, devicePlacementLine as b, skippedMissReason as c, refreshUpdatesManifestFile as d, swapJsBundle as f, AvdBootError as g, ensureOwnedDevice as h, printPlan as i, UPDATES_MANIFEST_NAME as l, ensureBooted as m, planFlagRefusal as n, explainBuildMiss as o, didSetUpDevice as p, planPayload as r, fingerprintErrorMissReason as s, planCachedBuild as t, detectEntryFile as u, warmMetro as v, automaticDevicePlacement as y };