stim 1.14.0 → 1.16.0

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 (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,11 +1,12 @@
1
- import { z as deviceSlotKey } from "./config-lvc7d3C2.mjs";
2
- import { a as formatElapsed, r as clockTime } from "./command-output-DVcpw6eE.mjs";
3
- import { a as parseLease, c as raiseLease, f as selectPoolDevice, l as releaseRunLease, n as fileLeaseIo, p as takeLease, r as leaseIsExpired, t as deviceLeasePath } from "./device-lease-5c3WrVRm.mjs";
4
- import { a as VERIFY_TIMEOUT_MS, m as APP_READINESS_TIMEOUT_MS } from "./launch-verify-Deg4tB3Z.mjs";
1
+ import { R as deviceSlotKey } from "./config-D0DbpZtM.mjs";
2
+ import { a as formatElapsed, r as clockTime } from "./command-output-DnJMdEY-.mjs";
3
+ import { a as parseLease, c as raiseLease, l as releaseRunLease, n as fileLeaseIo, p as takeLease, r as leaseIsExpired, t as deviceLeasePath } from "./device-lease-D-hLqx1b.mjs";
4
+ import { a as VERIFY_TIMEOUT_MS, m as APP_READINESS_TIMEOUT_MS } from "./launch-verify-2DBRizjo.mjs";
5
5
  //#region src/engine/device-lease-run.ts
6
6
  const LEASE_STEP_FLOOR_MS = 6e4;
7
7
  const DEBUG_VERIFY_STEP_MS = VERIFY_TIMEOUT_MS + APP_READINESS_TIMEOUT_MS;
8
8
  const DEVICE_WAIT_POLL_MS = 2e3;
9
+ const DEVICE_WAIT_LINE_MS = 3e4;
9
10
  const DEVICE_BUSY = "STIM_DEVICE_BUSY";
10
11
  const DEVICE_LOST = "STIM_DEVICE_LOST";
11
12
  function leaseStepMs(boundMs = 0) {
@@ -290,125 +291,4 @@ async function acquireRunLease({ root, platform, slot = "default", id, deviceNam
290
291
  }
291
292
  }
292
293
  //#endregion
293
- //#region src/engine/device-pool.ts
294
- function heldPoolId(root, platform, now, io = fileLeaseIo, slot = "default") {
295
- const record = io.readHolder(root)[deviceSlotKey(platform, slot)];
296
- if (!record) return null;
297
- const lease = parseLease(io.readLease(deviceLeasePath(platform, record.id)));
298
- if (!lease || lease.token !== record.token || leaseIsExpired(lease, now)) return null;
299
- return record.id;
300
- }
301
- function readPool(platform, candidates, io) {
302
- const reading = {
303
- usable: [],
304
- leases: [],
305
- unreadable: []
306
- };
307
- for (const candidate of candidates) {
308
- const path = deviceLeasePath(platform, candidate.id);
309
- const raw = io.readLease(path);
310
- const lease = parseLease(raw);
311
- if (raw !== null && !lease) {
312
- reading.unreadable.push(path);
313
- continue;
314
- }
315
- reading.usable.push(candidate);
316
- if (lease) reading.leases.push(lease);
317
- }
318
- return reading;
319
- }
320
- function disconnectedHeldRefusal(id, idLabel) {
321
- return {
322
- code: "STIM_NO_DEVICE",
323
- message: `This workspace leases ${id}, and it is not connected.`,
324
- remedy: `Run \`stim device unlock\` to give it up, then run this command again. Naming another \`--device <${idLabel}>\` refuses the same way while this lease is held.`,
325
- lease: null
326
- };
327
- }
328
- const NO_LEASE_FACTS = {
329
- platform: null,
330
- id: null,
331
- deviceName: null,
332
- holder: null,
333
- expiresAt: null
334
- };
335
- function unreadableSentence(unreadable) {
336
- return unreadable.length ? ` Unreadable lease file, so no run may take that device around it: ${unreadable.join("; ")}.` : "";
337
- }
338
- function poolBusyRefusal(holders, leases, unreadable, now, waitSeconds, spare = [], idLabel = "id") {
339
- const named = holders.map((holder) => `${holder.id} (${holder.holder}, until ${leaseExpiryText(holder.expiresAt, now)})`);
340
- const first = holders[0];
341
- const lease = first ? leases.find((entry) => entry.id === first.id) : void 0;
342
- if (!first) return {
343
- code: DEVICE_BUSY,
344
- message: `Every connected device has an unreadable lease file, so Stim cannot tell who holds them: ${unreadable.join("; ")}.`,
345
- remedy: "Read each file and, once you know no run depends on it, delete it. `stim gc` reports them on every run.",
346
- lease: NO_LEASE_FACTS
347
- };
348
- return {
349
- code: DEVICE_BUSY,
350
- message: `Every connected device is leased by another workspace${waitSeconds > 0 ? `, and this run waited ${waitSeconds}s for one` : ""}: ${named.join("; ")}.${unreadableSentence(unreadable)}`,
351
- remedy: (spare.length ? `Also connected and free: ${spare.join(", ")}. Name one with \`--device <${idLabel}>\` to use it now. ` : "") + "Wait longer with `--wait <seconds>`, connect another device, or pass `--no-wait` to install anyway -- which takes no lease and, when both workspaces build the same app id, terminates the holder's running app.",
352
- lease: {
353
- platform: lease?.platform ?? null,
354
- id: first.id,
355
- deviceName: lease?.deviceName ?? null,
356
- holder: first.holder,
357
- expiresAt: first.expiresAt
358
- }
359
- };
360
- }
361
- async function selectFromPool({ root, platform, slot = "default", idLabel, list, noCandidates, waitSeconds, deadline, noWait = false, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
362
- const held = heldPoolId(root, platform, now(), io, slot);
363
- const until = deadline ?? now() + waitSeconds * 1e3;
364
- let lastLine = null;
365
- for (;;) {
366
- const { usable, leases, unreadable } = readPool(platform, list(), io);
367
- const selection = selectPoolDevice({
368
- candidates: usable,
369
- leases,
370
- held,
371
- now: now()
372
- });
373
- if (selection.status === "selected") return selection;
374
- if (selection.status === "held-disconnected") return {
375
- status: "refused",
376
- refusal: disconnectedHeldRefusal(selection.id, idLabel)
377
- };
378
- if (selection.status === "none") {
379
- if (unreadable.length) return {
380
- status: "refused",
381
- refusal: poolBusyRefusal([], leases, unreadable, now(), waitSeconds)
382
- };
383
- const { message, remedy } = noCandidates();
384
- return {
385
- status: "refused",
386
- refusal: {
387
- code: "STIM_NO_DEVICE",
388
- message,
389
- remedy,
390
- lease: null
391
- }
392
- };
393
- }
394
- const first = selection.holders[0];
395
- if (noWait && first) {
396
- const candidate = usable.find((entry) => entry.id === first.id);
397
- if (candidate) return {
398
- status: "selected",
399
- candidate
400
- };
401
- }
402
- if (now() >= until) return {
403
- status: "refused",
404
- refusal: poolBusyRefusal(selection.holders, leases, unreadable, now(), waitSeconds, selection.spare, idLabel)
405
- };
406
- if (lastLine === null || now() - lastLine >= 3e4) {
407
- lastLine = now();
408
- warn(`waiting for a free device; every connected one is leased: ${selection.holders.map((holder) => `${holder.id} (${holder.holder}, until ${leaseExpiryText(holder.expiresAt, now())})`).join("; ")}`);
409
- }
410
- await sleep(DEVICE_WAIT_POLL_MS);
411
- }
412
- }
413
- //#endregion
414
- export { lostLine as a, releaseLeaseOnSignal as c, waitForDevice as d, leaseExpiryText as i, runLease as l, DEBUG_VERIFY_STEP_MS as n, lostRefusal as o, acquireRunLease as r, parseDeviceWait as s, selectFromPool as t, waitFlagConflict as u };
294
+ export { acquireRunLease as a, lostRefusal as c, runLease as d, waitFlagConflict as f, DEVICE_WAIT_POLL_MS as i, parseDeviceWait as l, DEVICE_BUSY as n, leaseExpiryText as o, waitForDevice as p, DEVICE_WAIT_LINE_MS as r, lostLine as s, DEBUG_VERIFY_STEP_MS as t, releaseLeaseOnSignal as u };
@@ -0,0 +1,125 @@
1
+ import { R as deviceSlotKey } from "./config-D0DbpZtM.mjs";
2
+ import { a as parseLease, f as selectPoolDevice, n as fileLeaseIo, r as leaseIsExpired, t as deviceLeasePath } from "./device-lease-D-hLqx1b.mjs";
3
+ import { i as DEVICE_WAIT_POLL_MS, n as DEVICE_BUSY, o as leaseExpiryText } from "./device-lease-run-CqjShqWW.mjs";
4
+ //#region src/engine/device-pool.ts
5
+ function heldPoolId(root, platform, now, io = fileLeaseIo, slot = "default") {
6
+ const record = io.readHolder(root)[deviceSlotKey(platform, slot)];
7
+ if (!record) return null;
8
+ const lease = parseLease(io.readLease(deviceLeasePath(platform, record.id)));
9
+ if (!lease || lease.token !== record.token || leaseIsExpired(lease, now)) return null;
10
+ return record.id;
11
+ }
12
+ function readPool(platform, candidates, io) {
13
+ const reading = {
14
+ usable: [],
15
+ leases: [],
16
+ unreadable: []
17
+ };
18
+ for (const candidate of candidates) {
19
+ const path = deviceLeasePath(platform, candidate.id);
20
+ const raw = io.readLease(path);
21
+ const lease = parseLease(raw);
22
+ if (raw !== null && !lease) {
23
+ reading.unreadable.push(path);
24
+ continue;
25
+ }
26
+ reading.usable.push(candidate);
27
+ if (lease) reading.leases.push(lease);
28
+ }
29
+ return reading;
30
+ }
31
+ function disconnectedHeldRefusal(id, idLabel) {
32
+ return {
33
+ code: "STIM_NO_DEVICE",
34
+ message: `This workspace leases ${id}, and it is not connected.`,
35
+ remedy: `Run \`stim device unlock\` to give it up, then run this command again. Naming another \`--device <${idLabel}>\` refuses the same way while this lease is held.`,
36
+ lease: null
37
+ };
38
+ }
39
+ const NO_LEASE_FACTS = {
40
+ platform: null,
41
+ id: null,
42
+ deviceName: null,
43
+ holder: null,
44
+ expiresAt: null
45
+ };
46
+ function unreadableSentence(unreadable) {
47
+ return unreadable.length ? ` Unreadable lease file, so no run may take that device around it: ${unreadable.join("; ")}.` : "";
48
+ }
49
+ function poolBusyRefusal(holders, leases, unreadable, now, waitSeconds, spare = [], idLabel = "id") {
50
+ const named = holders.map((holder) => `${holder.id} (${holder.holder}, until ${leaseExpiryText(holder.expiresAt, now)})`);
51
+ const first = holders[0];
52
+ const lease = first ? leases.find((entry) => entry.id === first.id) : void 0;
53
+ if (!first) return {
54
+ code: DEVICE_BUSY,
55
+ message: `Every connected device has an unreadable lease file, so Stim cannot tell who holds them: ${unreadable.join("; ")}.`,
56
+ remedy: "Read each file and, once you know no run depends on it, delete it. `stim gc` reports them on every run.",
57
+ lease: NO_LEASE_FACTS
58
+ };
59
+ return {
60
+ code: DEVICE_BUSY,
61
+ message: `Every connected device is leased by another workspace${waitSeconds > 0 ? `, and this run waited ${waitSeconds}s for one` : ""}: ${named.join("; ")}.${unreadableSentence(unreadable)}`,
62
+ remedy: (spare.length ? `Also connected and free: ${spare.join(", ")}. Name one with \`--device <${idLabel}>\` to use it now. ` : "") + "Wait longer with `--wait <seconds>`, connect another device, or pass `--no-wait` to install anyway -- which takes no lease and, when both workspaces build the same app id, terminates the holder's running app.",
63
+ lease: {
64
+ platform: lease?.platform ?? null,
65
+ id: first.id,
66
+ deviceName: lease?.deviceName ?? null,
67
+ holder: first.holder,
68
+ expiresAt: first.expiresAt
69
+ }
70
+ };
71
+ }
72
+ async function selectFromPool({ root, platform, slot = "default", idLabel, list, noCandidates, waitSeconds, deadline, noWait = false, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), warn = () => {}, io = fileLeaseIo }) {
73
+ const held = heldPoolId(root, platform, now(), io, slot);
74
+ const until = deadline ?? now() + waitSeconds * 1e3;
75
+ let lastLine = null;
76
+ for (;;) {
77
+ const { usable, leases, unreadable } = readPool(platform, list(), io);
78
+ const selection = selectPoolDevice({
79
+ candidates: usable,
80
+ leases,
81
+ held,
82
+ now: now()
83
+ });
84
+ if (selection.status === "selected") return selection;
85
+ if (selection.status === "held-disconnected") return {
86
+ status: "refused",
87
+ refusal: disconnectedHeldRefusal(selection.id, idLabel)
88
+ };
89
+ if (selection.status === "none") {
90
+ if (unreadable.length) return {
91
+ status: "refused",
92
+ refusal: poolBusyRefusal([], leases, unreadable, now(), waitSeconds)
93
+ };
94
+ const { message, remedy } = noCandidates();
95
+ return {
96
+ status: "refused",
97
+ refusal: {
98
+ code: "STIM_NO_DEVICE",
99
+ message,
100
+ remedy,
101
+ lease: null
102
+ }
103
+ };
104
+ }
105
+ const first = selection.holders[0];
106
+ if (noWait && first) {
107
+ const candidate = usable.find((entry) => entry.id === first.id);
108
+ if (candidate) return {
109
+ status: "selected",
110
+ candidate
111
+ };
112
+ }
113
+ if (now() >= until) return {
114
+ status: "refused",
115
+ refusal: poolBusyRefusal(selection.holders, leases, unreadable, now(), waitSeconds, selection.spare, idLabel)
116
+ };
117
+ if (lastLine === null || now() - lastLine >= 3e4) {
118
+ lastLine = now();
119
+ warn(`waiting for a free device; every connected one is leased: ${selection.holders.map((holder) => `${holder.id} (${holder.holder}, until ${leaseExpiryText(holder.expiresAt, now())})`).join("; ")}`);
120
+ }
121
+ await sleep(DEVICE_WAIT_POLL_MS);
122
+ }
123
+ }
124
+ //#endregion
125
+ export { selectFromPool as t };
@@ -1,20 +1,21 @@
1
- import { d as getConfigDir } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { A as workspaceId, F as withDirLock, M as workspaceLogsDir, P as workspaceStateFile, _ as updateWorkspaceState, a as pidExists, h as readWorkspaceState$1, k as workspaceDir, p as clearWorkspaceStateKey } from "./metro-CcEo8Fl8.mjs";
4
- import { c as ownedDeviceLabel } from "./project-CWlvEaeN.mjs";
5
- import { a as clearRemoteSession, c as readMetroTunnel, u as readRemoteSession } from "./state-DHqvsbqC.mjs";
6
- import { i as signalDeclaredSpawns, r as refuseDeclaredSpawns, t as allowDeclaredSpawns } from "./spawn-claims-Drw_o-Fb.mjs";
7
- import { a as withWorkspaceProcessLock } from "./named-ports-Blg8IxOh.mjs";
8
- import { a as formatElapsed } from "./command-output-DVcpw6eE.mjs";
9
- import { V as planMetroReach } from "./settings-D61iNOsB.mjs";
10
- import { y as probePublicHttp } from "./stop-cause-hjGBOZTG.mjs";
11
- import { C as sanitizeDeviceLabel } from "./ios-BwDPrnYQ.mjs";
12
- import { a as LAUNCH_ERROR, i as INSTALL_ERROR, l as devClientDeepLink } from "./app-install-BpgSgkJj.mjs";
13
- import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-Deg4tB3Z.mjs";
14
- import { t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { d as getConfigDir } from "./config-D0DbpZtM.mjs";
3
+ import { a as formatElapsed } from "./command-output-DnJMdEY-.mjs";
4
+ import { K as planMetroReach } from "./settings-oTSkkNrM.mjs";
5
+ import { l as ownedDeviceLabel } from "./project-Du91Ilvc.mjs";
6
+ import { o as pidExists } from "./metro-BeK1ZPz1.mjs";
7
+ import { C as workspaceStateFile, v as workspaceDir, w as withDirLock, x as workspaceLogsDir$1, y as workspaceId } from "./workspace-state-B8-aJ1_5.mjs";
8
+ import { a as clearRemoteSession, c as readMetroTunnel, u as readRemoteSession } from "./state-DcW6vOd_.mjs";
9
+ import { T as sanitizeDeviceLabel } from "./ios-BaInn3sW.mjs";
10
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
11
+ import { u as probePublicHttp } from "./ownership-hCkPzGmr.mjs";
12
+ import { i as INSTALL_ERROR, o as LAUNCH_ERROR, u as devClientDeepLink } from "./app-install-CKWdbXIX.mjs";
13
+ import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-2DBRizjo.mjs";
14
+ import { t as compareStimVersions } from "./stim-installations-QMEI32v1.mjs";
15
+ import { c as nativeRunWaitNotice } from "./native-run-NaBrRFtQ.mjs";
15
16
  import { existsSync, mkdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
16
- import { easMachineStateRoot, easMachineStateRoot as easMachineStateRoot$1, easSessionLedgerFile, easSessionLedgerLock, readEasSessionLedger, readEasSessionLedger as readEasSessionLedger$1, readJsonObject, validEasSessionClaim } from "@stim-cli/core/state";
17
17
  import { dirname, join, resolve } from "node:path";
18
+ import { easMachineStateRoot, easMachineStateRoot as easMachineStateRoot$1, easSessionLedgerFile, easSessionLedgerLock, readEasSessionLedger, readEasSessionLedger as readEasSessionLedger$1, readJsonObject, validEasSessionClaim } from "@stim-cli/core/state";
18
19
  //#region src/engine/metro-gate.ts
19
20
  const REMOTE_METRO_WRONG = "STIM_REMOTE_METRO_WRONG";
20
21
  const GATE_TIMEOUT_MS = 25e3;
@@ -145,6 +146,27 @@ function disconnectArgs(profilePath) {
145
146
  function closeArgs(profilePath) {
146
147
  return withProfile(profilePath, ["close"]);
147
148
  }
149
+ const HEALTH_TIMEOUT_MS = 3e3;
150
+ function archField(value) {
151
+ if (!value || typeof value !== "object") return null;
152
+ const arch = value.hostArch;
153
+ return arch === "arm64" || arch === "x86_64" ? arch : null;
154
+ }
155
+ /** agent-device proxies report the daemon machine under `upstream`; a daemon reports itself at the top level. */
156
+ function parseHealthHostArch(body) {
157
+ if (!body || typeof body !== "object") return null;
158
+ return archField(body.upstream) ?? archField(body);
159
+ }
160
+ async function readRemoteSimulatorArch(daemon, { fetch: fetchImpl = fetch, timeoutMs = HEALTH_TIMEOUT_MS } = {}) {
161
+ if (!daemon) return "arm64";
162
+ try {
163
+ const response = await fetchImpl(`${daemon.baseUrl.replace(/\/+$/, "")}/health`, { signal: AbortSignal.timeout(timeoutMs) });
164
+ if (!response.ok) return "arm64";
165
+ return parseHealthHostArch(await response.json()) ?? "arm64";
166
+ } catch {
167
+ return "arm64";
168
+ }
169
+ }
148
170
  //#endregion
149
171
  //#region src/engine/eas-simulator.ts
150
172
  const OWNED_PREFIX = "stim-";
@@ -153,6 +175,7 @@ const LIVE_SESSION_STATUSES = /* @__PURE__ */ new Set(["NEW", "IN_PROGRESS"]);
153
175
  const TERMINAL_SESSION_STATUSES = /* @__PURE__ */ new Set(["STOPPED", "ERRORED"]);
154
176
  const SESSION_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
155
177
  const MIN_EAS_CLI_SIMULATOR_VERSION = "21.6.0";
178
+ const MIN_EAS_CLI_DEVICE_VERSION = "22.2.0";
156
179
  function easCliUpgradeRemedy(minimum) {
157
180
  return `Upgrade eas-cli to ${minimum} or later (\`npm install --global eas-cli@latest\`, or the project's eas-cli dependency)`;
158
181
  }
@@ -171,7 +194,7 @@ function ownedSessionName(label) {
171
194
  function isOwnedSessionName(name) {
172
195
  return typeof name === "string" && name.startsWith(OWNED_PREFIX);
173
196
  }
174
- function createSessionArgs({ label, platform, maxDurationMinutes = null }) {
197
+ function createSessionArgs({ label, platform, maxDurationMinutes = null, device = null }) {
175
198
  const args = [
176
199
  "sim",
177
200
  "--platform",
@@ -184,6 +207,7 @@ function createSessionArgs({ label, platform, maxDurationMinutes = null }) {
184
207
  ownedSessionName(label)
185
208
  ];
186
209
  if (maxDurationMinutes) args.push("--max-duration-minutes", String(maxDurationMinutes));
210
+ if (device) args.push("--device", device);
187
211
  return args;
188
212
  }
189
213
  function getSessionArgs(sessionId) {
@@ -504,144 +528,6 @@ function withEasProjectLock(_root, fn, options = {}) {
504
528
  });
505
529
  }
506
530
  //#endregion
507
- //#region src/engine/native-run.ts
508
- const NATIVE_RUN_LOCK = "native-run";
509
- const NATIVE_RUN_WAIT_MS = 18e5;
510
- const WAIT_HEARTBEAT_MS = 3e4;
511
- const CANCEL_REQUEST_KEY = "nativeRunCancel";
512
- function nativeRunHolder(holder) {
513
- const { command, platform, slot } = holder.details;
514
- return {
515
- command: typeof command === "string" ? command : null,
516
- platform: typeof platform === "string" ? platform : null,
517
- slot: typeof slot === "string" ? slot : "default"
518
- };
519
- }
520
- function describeNativeRunHolder(holder, now) {
521
- const { command, slot } = nativeRunHolder(holder);
522
- const started = Date.parse(holder.startedAt);
523
- const facts = [`pid ${holder.owner.pid}`];
524
- if (Number.isFinite(started)) facts.push(`running for ${formatElapsed(now - started)}`);
525
- if (command === null) return `another Stim run (${facts.join(", ")})`;
526
- return `\`stim ${command}${slot === "default" ? "" : ` --slot ${slot}`}\` (${facts.join(", ")})`;
527
- }
528
- /**
529
- * Report a wait on the native-run lock: one line as soon as a holder is seen, a line for each new
530
- * holder, and a heartbeat every WAIT_HEARTBEAT_MS while the same holder keeps it.
531
- */
532
- function nativeRunWaitNotice({ write, now = Date.now, describe = describeNativeRunHolder }) {
533
- let claimId = null;
534
- let lastAt = 0;
535
- return (holder, firstLine) => {
536
- const at = now();
537
- if (holder.claimId !== claimId) {
538
- claimId = holder.claimId;
539
- lastAt = at;
540
- write(firstLine ?? `waiting for ${describe(holder, at)} in this workspace to finish`);
541
- return;
542
- }
543
- if (at - lastAt < WAIT_HEARTBEAT_MS) return;
544
- lastAt = at;
545
- write(`still waiting for ${describe(holder, at)}`);
546
- };
547
- }
548
- /**
549
- * What `stop` does about the run holding the native-run lock. A build is interrupted only when the stop
550
- * leaves it nothing to deploy to: the whole workspace stops, the build targets the stopped slot, or the
551
- * stopped slot is the workspace's only device. A build for another slot that stays is left running and
552
- * the stop proceeds without the lock. Only an owner whose identity is proven live is signalled.
553
- */
554
- function decideStopAction({ stopSlot, holder, ownerIdentity, deviceSlots }) {
555
- if (holder.command !== "ios" && holder.command !== "android") return { action: "wait" };
556
- if (!(stopSlot === void 0 || holder.slot === stopSlot || deviceSlots.includes(stopSlot) && deviceSlots.every((slot) => slot === stopSlot))) return { action: "proceed" };
557
- return ownerIdentity === "same" ? { action: "interrupt" } : { action: "refuse" };
558
- }
559
- function requestNativeRunCancel(root, claimId, pid = process.pid) {
560
- updateWorkspaceState(root, (state) => ({
561
- ...state,
562
- [CANCEL_REQUEST_KEY]: {
563
- claimId,
564
- pid,
565
- at: (/* @__PURE__ */ new Date()).toISOString()
566
- }
567
- }));
568
- }
569
- function cancelRequestFor(root, claimId) {
570
- const request = readWorkspaceState$1(root)?.[CANCEL_REQUEST_KEY];
571
- if (request?.claimId !== claimId || typeof request.pid !== "number") return null;
572
- return { pid: request.pid };
573
- }
574
- function clearNativeRunCancel(root, claimId) {
575
- try {
576
- clearWorkspaceStateKey(root, CANCEL_REQUEST_KEY, (value) => value?.claimId === claimId);
577
- } catch {}
578
- }
579
- let cancelled = null;
580
- /** Why this process's native run was cancelled, or null while it was not. */
581
- function runCancellation() {
582
- return cancelled;
583
- }
584
- function cancelledFailure(platform, { code, message }) {
585
- if (cancelled === null) return null;
586
- return {
587
- code: "STIM_CANCELLED",
588
- message: `The ${platform} run was ${cancelled} before it finished.`,
589
- remedy: `Run \`stim ${platform}\` again when you want the app on a device.`,
590
- lines: [`it stopped at ${code ?? "an unfinished step"}${message ? `: ${message}` : ""}`]
591
- };
592
- }
593
- function cancelOnInterrupt({ root, claim, write, exit }) {
594
- let signals = 0;
595
- const onInterrupt = () => {
596
- signals += 1;
597
- if (signals > 1) {
598
- exit(130);
599
- return;
600
- }
601
- const request = cancelRequestFor(root, claim.claimId);
602
- cancelled = request ? `cancelled by \`stim stop\` (pid ${request.pid})` : "cancelled by an interrupt";
603
- refuseDeclaredSpawns(`the run was ${cancelled}`);
604
- const signalled = signalDeclaredSpawns("SIGINT");
605
- write(signalled > 0 ? `${cancelled}; stopping the running build tool` : request ? `${cancelled}; stopping before the next build tool or install` : cancelled);
606
- if (signalled === 0 && !request) exit(130);
607
- };
608
- process.on("SIGINT", onInterrupt);
609
- return () => {
610
- process.off("SIGINT", onInterrupt);
611
- allowDeclaredSpawns();
612
- clearNativeRunCancel(root, claim.claimId);
613
- };
614
- }
615
- /**
616
- * Run an `ios` or `android` invocation under the workspace's native-run lock: the claim records the
617
- * command, platform and slot, and a wait for another holder is reported on `write`. The first SIGINT
618
- * forwards the interrupt to the running declared build tool; a run `stim stop` cancelled between build
619
- * tools starts no further one; a terminal interrupt with no build tool running, or a second SIGINT, exits
620
- * with 130 at once.
621
- */
622
- function withNativeBuildRun(root, details, fn, { write, exit = (code) => process.exit(code) }) {
623
- const notice = nativeRunWaitNotice({ write });
624
- return withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN_LOCK, async (claim) => {
625
- const dispose = cancelOnInterrupt({
626
- root,
627
- claim,
628
- write,
629
- exit
630
- });
631
- try {
632
- return await fn(claim);
633
- } finally {
634
- dispose();
635
- }
636
- }, {
637
- external: true,
638
- waitMs: NATIVE_RUN_WAIT_MS,
639
- declareSpawns: true,
640
- details: { ...details },
641
- onHeld: (holder) => notice(holder)
642
- });
643
- }
644
- //#endregion
645
531
  //#region src/engine/device-remote.ts
646
532
  const REMOTE_SESSION_ERROR = "STIM_NO_REMOTE_SESSION";
647
533
  const REMOTE_METRO_ERROR = "STIM_REMOTE_METRO_UNREACHABLE";
@@ -726,6 +612,12 @@ function remoteDeviceDeps(ctx) {
726
612
  };
727
613
  if (recorded) {
728
614
  const existing = readLiveDaemon(ctx, recorded.sessionId);
615
+ if (existing && ctx.deviceType && recorded.deviceType !== ctx.deviceType) return {
616
+ failed: true,
617
+ code: "STIM_REMOTE_DEVICE_MISMATCH",
618
+ reason: `EAS Simulator session ${recorded.sessionId} runs ${recorded.deviceType ? `"${recorded.deviceType}"` : "the model EAS chose"}, not "${ctx.deviceType}". Stim will not reuse a session on a different model.`,
619
+ remedy: `Run \`stim stop\` to end that session, then run \`stim ${ctx.platform ?? "ios"} --remote eas --device-type "${ctx.deviceType}"\` again to start one on the requested model.`
620
+ };
729
621
  if (existing) {
730
622
  note(`Reusing EAS Simulator session ${recorded.sessionId}.`);
731
623
  daemon = existing;
@@ -750,7 +642,8 @@ function remoteDeviceDeps(ctx) {
750
642
  stdout = getExecutor().runFile(ctx.easBin, createSessionArgs({
751
643
  label: ctx.label,
752
644
  platform: ctx.platform ?? "ios",
753
- maxDurationMinutes: ctx.maxDurationMinutes ?? null
645
+ maxDurationMinutes: ctx.maxDurationMinutes ?? null,
646
+ device: ctx.deviceType ?? null
754
647
  }), {
755
648
  ...easEnv,
756
649
  timeoutMs: EAS_SESSION_CREATE_TIMEOUT_MS,
@@ -926,7 +819,7 @@ function remoteDeviceDeps(ctx) {
926
819
  failureRemedy: () => ctx.backend === "eas" && session?.id ? `EAS Simulator session ${session.id} is still running and billed until it reaches its time limit or \`stim stop\` ends it. Fix the error above and run this command again to reuse the session, or run \`stim stop\` to end it.` : "Check that the remote daemon is reachable and still has the device, then run this command again."
927
820
  };
928
821
  }
929
- async function resolveRemoteContext({ root, backend, platform = "ios", easBin, env = process.env, lookupAgentDevice = defaultLookupAgentDevice, readEasCliVersion = readInstalledEasCliVersion, maxDurationMinutes = null }) {
822
+ async function resolveRemoteContext({ root, backend, platform = "ios", easBin, env = process.env, lookupAgentDevice = defaultLookupAgentDevice, readEasCliVersion = readInstalledEasCliVersion, maxDurationMinutes = null, deviceType = null }) {
930
823
  const agentDeviceBin = lookupAgentDevice();
931
824
  if (!agentDeviceBin) return {
932
825
  failed: "agent-device is not on PATH, and it is what drives a remote device.",
@@ -961,12 +854,18 @@ async function resolveRemoteContext({ root, backend, platform = "ios", easBin, e
961
854
  code: "STIM_REMOTE_EAS_UNAVAILABLE"
962
855
  };
963
856
  else {
964
- const { supported, version } = easCliSupport(readEasCliVersion(easBin, root), MIN_EAS_CLI_SIMULATOR_VERSION);
857
+ const versionOutput = readEasCliVersion(easBin, root);
858
+ const { supported, version } = easCliSupport(versionOutput, MIN_EAS_CLI_SIMULATOR_VERSION);
965
859
  if (!supported) return {
966
860
  failed: version ? `eas-cli ${version} (${easBin}) has no EAS Simulator commands; the eas backend needs eas-cli ${MIN_EAS_CLI_SIMULATOR_VERSION} or later.` : `Could not read an eas-cli version from \`${easBin} --version\`; the eas backend needs eas-cli ${MIN_EAS_CLI_SIMULATOR_VERSION} or later.`,
967
861
  remedy: `${easCliUpgradeRemedy(MIN_EAS_CLI_SIMULATOR_VERSION)}, then run the device command with \`--remote eas\` again.`,
968
862
  code: "STIM_REMOTE_EAS_UNAVAILABLE"
969
863
  };
864
+ if (deviceType && !easCliSupport(versionOutput, "22.2.0").supported) return {
865
+ failed: `eas-cli ${version} (${easBin}) cannot choose the EAS Simulator model; --device-type on the eas backend needs eas-cli ${MIN_EAS_CLI_DEVICE_VERSION} or later.`,
866
+ remedy: `${easCliUpgradeRemedy(MIN_EAS_CLI_DEVICE_VERSION)}, or drop --device-type to let EAS pick the model.`,
867
+ code: "STIM_BAD_ARG"
868
+ };
970
869
  }
971
870
  return { ctx: {
972
871
  root,
@@ -976,6 +875,7 @@ async function resolveRemoteContext({ root, backend, platform = "ios", easBin, e
976
875
  easBin: easBin ?? "",
977
876
  agentDeviceBin,
978
877
  maxDurationMinutes,
878
+ deviceType: backend === "eas" ? deviceType : null,
979
879
  publicMetroUrl: null,
980
880
  existingDaemon
981
881
  } };
@@ -1066,7 +966,7 @@ function withRemoteSessionLock(root, fn, options = {}) {
1066
966
  });
1067
967
  }
1068
968
  const EAS_START_HOLD_MS = 108e4 + Math.ceil(DAEMON_WAIT_MS / DAEMON_POLL_MS) * 35e3;
1069
- async function ensureRemoteBootOwned({ root, platform, sessionName, startedAt, boot, createdSessionId, abandonCreatedSession, webPreviewUrl = () => null, writeState, register = () => {}, withProjectLock = withEasProjectLock, withLock = withRemoteSessionLock, removeEasSessionClaim: removeClaim = removeEasSessionClaim, ledgerRoot = easMachineStateRoot$1(), notice = () => {}, now = Date.now }) {
969
+ async function ensureRemoteBootOwned({ root, platform, sessionName, startedAt, deviceType = null, boot, createdSessionId, abandonCreatedSession, webPreviewUrl = () => null, writeState, register = () => {}, withProjectLock = withEasProjectLock, withLock = withRemoteSessionLock, removeEasSessionClaim: removeClaim = removeEasSessionClaim, ledgerRoot = easMachineStateRoot$1(), notice = () => {}, now = Date.now }) {
1070
970
  const waitNotice = nativeRunWaitNotice({
1071
971
  write: notice,
1072
972
  now,
@@ -1098,7 +998,8 @@ async function ensureRemoteBootOwned({ root, platform, sessionName, startedAt, b
1098
998
  platform,
1099
999
  sessionId,
1100
1000
  startedAt,
1101
- ...preview ? { webPreviewUrl: preview } : {}
1001
+ ...preview ? { webPreviewUrl: preview } : {},
1002
+ ...deviceType ? { deviceType } : {}
1102
1003
  } });
1103
1004
  return booted;
1104
1005
  } catch (err) {
@@ -1378,16 +1279,21 @@ async function ensureMetroReachable({ ctx, metroPort, isExpo, tunnelMode = "auto
1378
1279
  gate = true;
1379
1280
  } else {
1380
1281
  const port = Number(metroPort);
1381
- const providerMatches = tunnelMode === "auto" || recorded?.kind === "managed" && recorded.provider === plan.start;
1282
+ if (plan.start === "tailscale" && ctx.backend === "eas") return {
1283
+ failed: "metro.tunnel is \"tailscale\", but an EAS Simulator is not on your tailnet and cannot reach it.",
1284
+ remedy: "Use the proxy backend with a device on the same tailnet, or set metro.tunnel to \"auto\" or \"ngrok\".",
1285
+ code: REMOTE_METRO_ERROR
1286
+ };
1287
+ const providerMatches = recorded?.kind === "managed" && recorded.provider === plan.start;
1382
1288
  if (recorded && recorded.kind === "managed" && recorded.port === port && providerMatches && isTunnelAlive(recorded.pid)) resolvedUrl = recorded.url;
1383
1289
  else return {
1384
1290
  failed: `No live managed Metro tunnel is recorded for port ${port}.`,
1385
1291
  remedy: "Run `stim start --remote`, then retry the device command."
1386
1292
  };
1387
- gate = true;
1293
+ gate = recorded.provider !== "tailscale";
1388
1294
  }
1389
1295
  if (gate && resolvedUrl) {
1390
- const logsDir = workspaceLogsDir(root);
1296
+ const logsDir = workspaceLogsDir$1(root);
1391
1297
  const result = await gateOrigin({
1392
1298
  origin: resolvedUrl,
1393
1299
  metroPort,
@@ -1415,4 +1321,4 @@ function bundleEntryPoint(root, isExpo) {
1415
1321
  return local ? normalized : `node_modules/${normalized}`;
1416
1322
  }
1417
1323
  //#endregion
1418
- export { findOrphanedOwnedSessions as A, withEasProjectLock as C, MIN_EAS_CLI_SIMULATOR_VERSION as D, removeEasSessionClaim as E, ownedSessionName as F, parseSessionListPage as I, stopSessionArgs as L, inspectSessionForTeardown as M, isDefinitiveMissingSessionError as N, easCliSupport as O, listOwnedSessionsArgs as P, verifyStoppedSession as R, withNativeBuildRun as S, readEasSessionLedger$1 as T, describeNativeRunHolder as _, ensureMetroReachable as a, requestNativeRunCancel as b, remoteAndroidDeps as c, withRemoteSessionLock as d, NATIVE_RUN_LOCK as f, decideStopAction as g, clearNativeRunCancel as h, endRecordedSession as i, getSessionArgs as j, easCliUpgradeRemedy as k, remoteIosDeps as l, cancelledFailure as m, binOnPath as n, ensureRemoteBootOwned as o, NATIVE_RUN_WAIT_MS as p, bundleEntryPoint as r, readInstalledEasCliVersion as s, REMOTE_SESSION_ERROR as t, resolveRemoteContext as u, nativeRunHolder as v, easMachineStateRoot$1 as w, runCancellation as x, nativeRunWaitNotice as y, gateMetroOrigin as z };
1324
+ export { listOwnedSessionsArgs as C, verifyStoppedSession as D, stopSessionArgs as E, readRemoteSimulatorArch as O, isDefinitiveMissingSessionError as S, parseSessionListPage as T, easCliSupport as _, ensureMetroReachable as a, getSessionArgs as b, remoteAndroidDeps as c, withRemoteSessionLock as d, withEasProjectLock as f, MIN_EAS_CLI_SIMULATOR_VERSION as g, removeEasSessionClaim as h, endRecordedSession as i, gateMetroOrigin as k, remoteIosDeps as l, readEasSessionLedger$1 as m, binOnPath as n, ensureRemoteBootOwned as o, easMachineStateRoot$1 as p, bundleEntryPoint as r, readInstalledEasCliVersion as s, REMOTE_SESSION_ERROR as t, resolveRemoteContext as u, easCliUpgradeRemedy as v, ownedSessionName as w, inspectSessionForTeardown as x, findOrphanedOwnedSessions as y };