stim 1.15.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 (117) hide show
  1. package/dist/{activity-B2-kb7k0.mjs → activity-BxwdD81S.mjs} +29 -16
  2. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  3. package/dist/{android-Db2eEMaf.mjs → android-B_NumVyQ.mjs} +21 -20
  4. package/dist/{android-Cu_L4RpL.mjs → android-Ift4kVvt.mjs} +56 -47
  5. package/dist/{android-cas-PnKfAdB2.mjs → android-cas-8U4DPqeR.mjs} +1 -1
  6. package/dist/{android-CYmq0jNi.mjs → android-r2rBL8kV.mjs} +1 -1
  7. package/dist/{app-install-CAAplXAt.mjs → app-install-CKWdbXIX.mjs} +48 -5
  8. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  9. package/dist/{budget-CMiqghig.mjs → budget-DjrURfQn.mjs} +356 -366
  10. package/dist/{build-plan-aeGMFX7e.mjs → build-plan-DjeSb2Xl.mjs} +19 -20
  11. package/dist/{build-slots-Df1ApSuH.mjs → build-slots-rUk77xqh.mjs} +7 -2
  12. package/dist/{cli-0cX5s4NM.mjs → cli-BkYZ_6hy.mjs} +23 -17
  13. package/dist/cli.mjs +1 -1
  14. package/dist/{client-C2IS36Qf.mjs → client-BTY686R6.mjs} +57 -22
  15. package/dist/collector-run.mjs +5 -192
  16. package/dist/{command-output-CliHHn7a.mjs → command-output-DnJMdEY-.mjs} +1 -1
  17. package/dist/{created-devices-BwVb3liH.mjs → created-devices-D5owjN4C.mjs} +1 -1
  18. package/dist/{dependency-state-BmsjKZ2H.mjs → dependency-state-B6cMXZab.mjs} +1 -1
  19. package/dist/{deps-BFlKbOIp.mjs → deps-urbmfXig.mjs} +2 -2
  20. package/dist/{dev-client-CjzLoNs0.mjs → dev-client-CroAIjjj.mjs} +3 -40
  21. package/dist/{device-BZ1IyS5A.mjs → device-DZQjF7H5.mjs} +7 -6
  22. package/dist/{device-capacity-DMuKC7nx.mjs → device-capacity-BHwxNBlu.mjs} +2 -2
  23. package/dist/device-host-worker.mjs +223 -28
  24. package/dist/{device-ios-DMQBUPus.mjs → device-ios-BadmeL2V.mjs} +7 -7
  25. package/dist/{device-lease-W5Pi1q38.mjs → device-lease-D-hLqx1b.mjs} +2 -2
  26. package/dist/{device-pool-B2r38YjS.mjs → device-lease-run-CqjShqWW.mjs} +5 -125
  27. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  28. package/dist/{device-remote-9ulPsdhY.mjs → device-remote-D5WVWqOq.mjs} +12 -12
  29. package/dist/{doctor-BcE5xsBn.mjs → doctor-Cimf-gGB.mjs} +80 -34
  30. package/dist/{error-diagnostics-Cb0P1WLS.mjs → error-diagnostics-B9L4Ae0x.mjs} +11 -15
  31. package/dist/{gc-BuL1Genr.mjs → gc-Bqy4bjEu.mjs} +194 -31
  32. package/dist/{gradle-CmdOBq1d.mjs → gradle-BsO1MdsH.mjs} +4 -4
  33. package/dist/{guide-cCSGRFd3.mjs → guide-B_uyKTxW.mjs} +588 -53
  34. package/dist/{guide-status-CIoWK6mG.mjs → guide-status-CjEZMAD4.mjs} +1 -1
  35. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  36. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  37. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  38. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  39. package/dist/{build-progress-C2Jv1AtA.mjs → idle-DYVymG92.mjs} +233 -6
  40. package/dist/{idle-shutdown-B5LPaNyY.mjs → idle-shutdown-CTsDnAde.mjs} +6 -6
  41. package/dist/{in-use-CndPMilN.mjs → in-use-DvBw0Kxo.mjs} +29 -9
  42. package/dist/{ios-3CtCRhwr.mjs → ios-BaInn3sW.mjs} +2 -2
  43. package/dist/ios-Cr1h5gnO.mjs +189 -0
  44. package/dist/{ios-Chob8ny7.mjs → ios-aq3QoGI3.mjs} +729 -452
  45. package/dist/{ios-device-DERmMmk-.mjs → ios-device-FfCN_C8p.mjs} +1 -1
  46. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  47. package/dist/{launch-verify-CLlofxRt.mjs → launch-verify-2DBRizjo.mjs} +3 -3
  48. package/dist/logs-CHEVK8zD.mjs +371 -0
  49. package/dist/{machines-CYtOCqcj.mjs → machines-BIBUdhpj.mjs} +16 -10
  50. package/dist/{macos-BwtbUtTN.mjs → macos-CSYEAFmF.mjs} +22 -17
  51. package/dist/macos-run.mjs +1 -1
  52. package/dist/maintenance-run.d.mts +4 -0
  53. package/dist/maintenance-run.mjs +254 -0
  54. package/dist/{metro-Di8EN2Tb.mjs → metro-BeK1ZPz1.mjs} +1 -1
  55. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  56. package/dist/{named-ports-Dj_XBNaL.mjs → named-ports-Bk_YXEmE.mjs} +1 -1
  57. package/dist/{native-run-DFDoIRbY.mjs → native-run-NaBrRFtQ.mjs} +2 -2
  58. package/dist/{native-runtime-i6OkVg-w.mjs → native-runtime-DBzRnEKS.mjs} +6 -6
  59. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  60. package/dist/offload-worker.d.mts +1 -0
  61. package/dist/offload-worker.mjs +13 -8
  62. package/dist/{ownership-BkYxUtuj.mjs → ownership-DiA16AsN.mjs} +2 -2
  63. package/dist/{stop-cause-CZ_Mr-v9.mjs → ownership-hCkPzGmr.mjs} +4 -98
  64. package/dist/{ports-DddwttKM.mjs → ports-R2k4Knz3.mjs} +2 -2
  65. package/dist/{prebuild-BvxDlD6C.mjs → prebuild-D2fsca_Z.mjs} +4 -4
  66. package/dist/preview-DhVbvPAZ.mjs +318 -0
  67. package/dist/{project-CV3tDFHl.mjs → project-Du91Ilvc.mjs} +67 -3
  68. package/dist/{recordings-GEXp4iBT.mjs → recordings-BIXOzVnr.mjs} +1 -1
  69. package/dist/{reload-ChPiwvot.mjs → reload-rM9jV1fz.mjs} +49 -12
  70. package/dist/{remote-cache-CO2elm_b.mjs → remote-cache-C8-V13XN.mjs} +4 -4
  71. package/dist/{run-B-xqRrsg.mjs → run-f4vI6EnK.mjs} +2 -2
  72. package/dist/{server-bare-X7PUgizL.mjs → server-bare-CpPGmnmO.mjs} +1 -1
  73. package/dist/server-expo-CVqO2c35.mjs +2 -0
  74. package/dist/{server-expo-DhtX6bom.mjs → server-expo-MC0rHlOC.mjs} +4 -4
  75. package/dist/settings-BBpMcDbD.mjs +25 -0
  76. package/dist/{settings-DbL6uhH8.mjs → settings-ZkDzkzvB.mjs} +10 -4
  77. package/dist/settings-oTSkkNrM.mjs +1439 -0
  78. package/dist/settings.schema.json +432 -39
  79. package/dist/{simslim-Ciluxlq4.mjs → simslim-B8-7AoTI.mjs} +3 -3
  80. package/dist/{slot-launch-BY0BrOLh.mjs → slot-launch-B8VrHxn1.mjs} +10 -7
  81. package/dist/{spawn-entry-DncpJvWL.mjs → spawn-entry-jw19m8Nl.mjs} +1 -0
  82. package/dist/{start-CPO4wRg7.mjs → start-CX8Ig-U2.mjs} +1 -1
  83. package/dist/{start-CUD9nasA.mjs → start-ClDfwXa4.mjs} +21 -17
  84. package/dist/{state-BxCgeTQu.mjs → state-3DwSQIBK.mjs} +2 -2
  85. package/dist/{state-DFm77wRn.mjs → state-C1jB7Q8q.mjs} +5 -5
  86. package/dist/{state-CaeaHbDA.mjs → state-DcW6vOd_.mjs} +1 -1
  87. package/dist/{stats-DBLLFmKd.mjs → stats-CEZ_i5je.mjs} +16 -5
  88. package/dist/{status-l84Gbvp2.mjs → status-BuvBFeII.mjs} +5 -3
  89. package/dist/{status-DYXxNOT0.mjs → status-Cs7VLg9D.mjs} +148 -44
  90. package/dist/status-DlKvVJBE.mjs +49 -0
  91. package/dist/{stop-Bvf1MflS.mjs → stop-B0Yxtmiy.mjs} +3 -3
  92. package/dist/{stop-Bad61z_2.mjs → stop-Bffozsac.mjs} +61 -20
  93. package/dist/{stop-CceYh1yG.mjs → stop-Dt_dM16A.mjs} +2 -2
  94. package/dist/stop-cause-CidxIqup.mjs +97 -0
  95. package/dist/supervisor-run.mjs +148 -17
  96. package/dist/{support-CDQbDkL5.mjs → support-CAP8qDUF.mjs} +4 -3
  97. package/dist/{support-WLOzVKUv.mjs → support-D4aYJniO.mjs} +34 -9
  98. package/dist/{teardown-BYuQafiA.mjs → teardown-CL6vDs2Y.mjs} +56 -47
  99. package/dist/{toolchain-DmeYjXD9.mjs → toolchain-DkUflduf.mjs} +10 -10
  100. package/dist/trigger-Bemu8ytw.mjs +65 -0
  101. package/dist/trigger-CXts6qt1.mjs +2 -0
  102. package/dist/{warm-progress-DVHLN4uC.mjs → warm-progress-CpGv3Wb6.mjs} +274 -5
  103. package/dist/{web-k6w8-HnE.mjs → web-DWUaMp8P.mjs} +15 -16
  104. package/dist/web-run.mjs +5 -5
  105. package/dist/{workspace-state-CL2V2Ons.mjs → workspace-state-B8-aJ1_5.mjs} +33 -33
  106. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  107. package/dist/{worktree-qXKzADSC.mjs → worktree-K-xj9Db0.mjs} +497 -30
  108. package/dist/{worktree-BT_uULFx.mjs → worktree-jnG8Iph5.mjs} +1 -1
  109. package/package.json +5 -5
  110. package/dist/hosted-macos-DwowNU7W.mjs +0 -447
  111. package/dist/logs-h9h6MUvE.mjs +0 -568
  112. package/dist/machine-usage-D_YYMzfR.mjs +0 -283
  113. package/dist/merge-state-BnxLNQiM.mjs +0 -272
  114. package/dist/server-expo-KJeD109I.mjs +0 -2
  115. package/dist/settings-CduVcoxj.mjs +0 -680
  116. package/dist/watchman-Bx0wceoF.mjs +0 -238
  117. package/dist/worktree-C0c5Udwa.mjs +0 -753
@@ -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,18 +1,18 @@
1
1
  import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  import { d as getConfigDir } from "./config-D0DbpZtM.mjs";
3
- import { C as workspaceLogsDir, E as withDirLock, T as workspaceStateFile, b as workspaceDir, x as workspaceId } from "./workspace-state-CL2V2Ons.mjs";
4
- import { c as ownedDeviceLabel } from "./project-CV3tDFHl.mjs";
5
- import { a as clearRemoteSession, c as readMetroTunnel, u as readRemoteSession } from "./state-CaeaHbDA.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";
6
10
  import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
7
- import { a as formatElapsed } from "./command-output-CliHHn7a.mjs";
8
- import { o as pidExists } from "./metro-Di8EN2Tb.mjs";
9
- import { W as planMetroReach } from "./settings-CduVcoxj.mjs";
10
- import { T as sanitizeDeviceLabel } from "./ios-3CtCRhwr.mjs";
11
- import { y as probePublicHttp } from "./stop-cause-CZ_Mr-v9.mjs";
12
- import { i as INSTALL_ERROR, o as LAUNCH_ERROR, u as devClientDeepLink } from "./app-install-CAAplXAt.mjs";
13
- import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-CLlofxRt.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
14
  import { t as compareStimVersions } from "./stim-installations-QMEI32v1.mjs";
15
- import { c as nativeRunWaitNotice } from "./native-run-DFDoIRbY.mjs";
15
+ import { c as nativeRunWaitNotice } from "./native-run-NaBrRFtQ.mjs";
16
16
  import { existsSync, mkdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
17
17
  import { dirname, join, resolve } from "node:path";
18
18
  import { easMachineStateRoot, easMachineStateRoot as easMachineStateRoot$1, easSessionLedgerFile, easSessionLedgerLock, readEasSessionLedger, readEasSessionLedger as readEasSessionLedger$1, readJsonObject, validEasSessionClaim } from "@stim-cli/core/state";
@@ -1293,7 +1293,7 @@ async function ensureMetroReachable({ ctx, metroPort, isExpo, tunnelMode = "auto
1293
1293
  gate = recorded.provider !== "tailscale";
1294
1294
  }
1295
1295
  if (gate && resolvedUrl) {
1296
- const logsDir = workspaceLogsDir(root);
1296
+ const logsDir = workspaceLogsDir$1(root);
1297
1297
  const result = await gateOrigin({
1298
1298
  origin: resolvedUrl,
1299
1299
  metroPort,
@@ -1,36 +1,36 @@
1
1
  import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  import { _ as loadConfig$1, p as getProject, s as configCorruptRepair, u as getConcurrencyLimits } from "./config-D0DbpZtM.mjs";
3
- import { m as sharedBuildCache, y as workspaceDerivedData } from "./workspace-state-CL2V2Ons.mjs";
4
- import { D as makeTemporaryDirectory, E as filesystemDevice, a as dirtyFingerprintFiles, h as locallyKnownUpstream, k as temporaryRoot, m as listWorktrees, s as gitCommonDir, x as resolveSourceCheckout, y as repoRoot } from "./worktree-C0c5Udwa.mjs";
5
- import { a as findProjectRoot$1, n as appProjectProblem, r as detectIsExpo, s as isPackageResolvable } from "./project-CV3tDFHl.mjs";
6
- import { d as quotedPath, f as refuseNoProject, l as phaseLine, u as plural } from "./command-output-CliHHn7a.mjs";
7
- import { C as diffFingerprintSources, D as fingerprintProject, h as listBuildLocks } from "./device-lease-W5Pi1q38.mjs";
8
- import { D as hostSystemImageArch, O as listAdbDevices, at as POOL_SETTING_REMEDY, dt as parkedMaxSetting, o as androidHome } from "./android-Db2eEMaf.mjs";
9
- import { A as remoteAndroidSetting, B as unknownSettingKeys, D as projectOptimizations, F as settingOrigin, L as settingShapeErrors, N as resolveSettings, R as settingsLayers, V as webSettings, Y as resolveOptimizations, a as SETTING_SHAPE_REMEDY, b as mergeSettingsLayers, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, j as remoteIosSetting, n as PATH_SETTINGS, q as compilerCacheFallbackMessage, y as iosSimSlimProfileSetting } from "./settings-CduVcoxj.mjs";
10
- import { A as hostMemoryPressureAdvice, c as iosRuntimeMatches, j as readHostMemoryPressure, p as listIosRuntimes, u as listAllIosSims, y as pickDefaultIosCreation } from "./ios-3CtCRhwr.mjs";
11
- import { n as listBuildSlots } from "./build-slots-Df1ApSuH.mjs";
3
+ import { d as quotedPath, f as refuseNoProject, l as phaseLine, u as plural } from "./command-output-DnJMdEY-.mjs";
4
+ import { B as settingsLayers, Ct as filesystemDevice, Et as temporaryRoot, H as unknownSettingKeys, I as settingOrigin, M as remoteIosSetting, O as projectOptimizations, P as resolveSettings, R as settingShapeErrors, U as webSettings, Y as compilerCacheFallbackMessage, Z as resolveOptimizations, a as SETTING_SHAPE_REMEDY, b as mergeSettingsLayers, dt as listWorktrees, ft as locallyKnownUpstream, gt as repoRoot, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, it as gitCommonDir, j as remoteAndroidSetting, n as PATH_SETTINGS, nt as dirtyFingerprintFiles, vt as resolveSourceCheckout, wt as makeTemporaryDirectory, y as iosSimSlimProfileSetting } from "./settings-oTSkkNrM.mjs";
5
+ import { c as isPackageResolvable, n as appProjectProblem, o as findProjectRoot$1, r as detectIsExpo } from "./project-Du91Ilvc.mjs";
6
+ import { _ as workspaceDerivedData, d as sharedBuildCache } from "./workspace-state-B8-aJ1_5.mjs";
7
+ import { i as inspectBudget, n as budgetLine, z as inspectWatchmanMemory } from "./budget-DjrURfQn.mjs";
8
+ import { C as diffFingerprintSources, D as fingerprintProject, h as listBuildLocks } from "./device-lease-D-hLqx1b.mjs";
9
+ import { n as listBuildSlots } from "./build-slots-rUk77xqh.mjs";
10
+ import { D as hostSystemImageArch, O as listAdbDevices, ft as parkedMaxSetting, o as androidHome, ot as POOL_SETTING_REMEDY } from "./android-B_NumVyQ.mjs";
11
+ import { A as hostMemoryPressureAdvice, c as iosRuntimeMatches, j as readHostMemoryPressure, p as listIosRuntimes, u as listAllIosSims, y as pickDefaultIosCreation } from "./ios-BaInn3sW.mjs";
12
+ import { C as podInstallCommand, D as projectCmakeLauncher, S as bundlerPin, f as ccacheEnabled, i as macosToolchain, m as detectXcodeMajor, n as androidToolchain, r as iosToolchain, t as androidRequirements } from "./toolchain-DkUflduf.mjs";
13
+ import { b as realIo, h as inspectBuildMachines, o as offloadCheck, u as simulatorRuntime } from "./client-BTY686R6.mjs";
14
+ import { n as inspectDeviceHostMachines } from "./machines-BIBUdhpj.mjs";
12
15
  import { a as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-BrcVWQyD.mjs";
13
- import { i as liveOwnedDeviceCount } from "./device-capacity-DMuKC7nx.mjs";
14
- import { C as podInstallCommand, D as projectCmakeLauncher, S as bundlerPin, f as ccacheEnabled, i as macosToolchain, m as detectXcodeMajor, n as androidToolchain, r as iosToolchain, t as androidRequirements } from "./toolchain-DmeYjXD9.mjs";
15
- import { h as inspectBuildMachines, o as offloadCheck, u as simulatorRuntime } from "./client-C2IS36Qf.mjs";
16
- import { n as inspectDeviceHostMachines } from "./machines-CYtOCqcj.mjs";
17
- import { d as androidPathRoomRemedy, l as androidPathRoom, n as apkOutputsDir, t as androidSdkRefusal, u as androidPathRoomMessage } from "./gradle-CmdOBq1d.mjs";
18
- import { m as resolveRuntime, p as resolveDeviceType } from "./support-WLOzVKUv.mjs";
19
- import { f as resolveEasCliBin, i as checkEasAuth$1, l as ownerFromConfig, u as providerFromConfig } from "./remote-cache-CO2elm_b.mjs";
20
- import { _ as easCliSupport, g as MIN_EAS_CLI_SIMULATOR_VERSION, s as readInstalledEasCliVersion, v as easCliUpgradeRemedy } from "./device-remote-9ulPsdhY.mjs";
16
+ import { i as liveOwnedDeviceCount } from "./device-capacity-BHwxNBlu.mjs";
17
+ import { d as androidPathRoomRemedy, l as androidPathRoom, n as apkOutputsDir, t as androidSdkRefusal, u as androidPathRoomMessage } from "./gradle-BsO1MdsH.mjs";
18
+ import { h as resolveRuntime, p as resolveDeviceType } from "./support-D4aYJniO.mjs";
19
+ import { f as resolveEasCliBin, i as checkEasAuth$1, l as ownerFromConfig, u as providerFromConfig } from "./remote-cache-C8-V13XN.mjs";
20
+ import { _ as easCliSupport, g as MIN_EAS_CLI_SIMULATOR_VERSION, s as readInstalledEasCliVersion, v as easCliUpgradeRemedy } from "./device-remote-D5WVWqOq.mjs";
21
21
  import { n as inspectStimVersions, t as compareStimVersions } from "./stim-installations-QMEI32v1.mjs";
22
- import { n as simslimIsOnPath, r as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION } from "./simslim-Ciluxlq4.mjs";
23
- import { W as inspectWatchmanMemory, n as budgetLine, r as inspectBudget } from "./budget-CMiqghig.mjs";
24
- import { r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-PnKfAdB2.mjs";
25
- import { n as recordDoctorRun } from "./guide-status-CIoWK6mG.mjs";
26
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-BmsjKZ2H.mjs";
22
+ import { n as simslimIsOnPath, r as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION } from "./simslim-B8-7AoTI.mjs";
23
+ import { r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-8U4DPqeR.mjs";
24
+ import { r as maintenanceStatus, t as maintenanceLine } from "./status-DlKvVJBE.mjs";
25
+ import { n as recordDoctorRun } from "./guide-status-CjEZMAD4.mjs";
26
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-B6cMXZab.mjs";
27
27
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
28
28
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
29
29
  import { existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
30
30
  import { homedir } from "os";
31
31
  import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
32
32
  import { InvalidArgumentError } from "commander";
33
- import { isJsonObject, readHostedAppMetadata, readHostedSessions, readJsonObject, settingDefinition } from "@stim-cli/core/state";
33
+ import { isJsonObject, maintenanceNdjsonFile, readHostedAppMetadata, readHostedSessions, readJsonObject, readStats, settingDefinition } from "@stim-cli/core/state";
34
34
  import chalk from "chalk";
35
35
  import * as expoFingerprint from "@expo/fingerprint";
36
36
  //#region src/diagnostics/sandbox.ts
@@ -662,6 +662,33 @@ function finding(level, title, detail, fix) {
662
662
  fix
663
663
  };
664
664
  }
665
+ function checkOffloadCandidate(placements, machines, peers, now) {
666
+ if (machines.length || !peers.some((peer) => isJsonObject(peer) && peer.OS === "macOS" && peer.Online === true)) return null;
667
+ const cold = placements.filter((placement) => {
668
+ const age = now - Date.parse(placement.at);
669
+ return placement.decision === "here" && !placement.failed && (placement.buildMs ?? 0) > 0 && age >= 0 && age <= 6048e5;
670
+ });
671
+ if (cold.length < 3) return null;
672
+ const average = cold.reduce((total, placement) => total + placement.buildMs, 0) / cold.length;
673
+ if (average <= 18e4) return null;
674
+ return {
675
+ code: "offload-candidate",
676
+ ...finding("note", "Builds could run on another Mac", `Cold builds averaged ~${Math.round(average / 6e4)} min over ${cold.length} builds this week.`, "In Stim Desktop, open Settings > Build machines > Add.")
677
+ };
678
+ }
679
+ function readOffloadCandidate(tailnetStatus, now, host) {
680
+ if (host !== "darwin") return null;
681
+ try {
682
+ const entries = loadConfig$1()?.offload?.machines;
683
+ const machines = Array.isArray(entries) ? entries.filter((entry) => typeof entry === "string") : [];
684
+ const placements = readStats().record?.placements ?? [];
685
+ if (machines.length || placements.length < 3) return null;
686
+ const status = tailnetStatus();
687
+ return checkOffloadCandidate(placements, machines, isJsonObject(status) && isJsonObject(status.Peer) ? Object.values(status.Peer) : [], now);
688
+ } catch {
689
+ return null;
690
+ }
691
+ }
665
692
  function mainCheckoutProjectRoot(projectRoot) {
666
693
  const currentRepoRoot = repoRoot(projectRoot);
667
694
  if (!currentRepoRoot) return projectRoot;
@@ -969,7 +996,11 @@ function checkXcodeEnvLineEndings(projectRoot, platform, host) {
969
996
  if (!content.includes("\r")) return null;
970
997
  return finding("cost", "ios/.xcode.env has CRLF line endings", "An EAS build uploads the working-tree bytes of ios/.xcode.env, and Xcode sources that file with sh on the build worker, where each carriage return becomes part of the line and the build fails with `: command not found`. On Windows the usual cause is core.autocrlf=true rewriting the file at checkout.", "Run `git config core.autocrlf input` then `git checkout -- ios/.xcode.env`, or add `ios/.xcode.env text eol=lf` to .gitattributes and check the file out again.");
971
998
  }
972
- function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, lookupChrome, platform, host = process.platform } = {}) {
999
+ function configuredRemoteBackends(projectSettings, platform) {
1000
+ const iosTarget = remoteIosSetting(projectSettings);
1001
+ return [.../* @__PURE__ */ new Set([...platform !== "android" ? [iosTarget?.kind === "backend" ? iosTarget.backend : null] : [], ...platform !== "ios" ? [remoteAndroidSetting(projectSettings)] : []])].filter((backend) => backend !== null);
1002
+ }
1003
+ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, lookupChrome, tailnetStatus = realIo.status, now = Date.now, platform, host = process.platform } = {}) {
973
1004
  const localIos = platform !== "android" && host === "darwin";
974
1005
  const read = (rel) => {
975
1006
  const p = join$1(projectRoot, rel);
@@ -1045,7 +1076,7 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1045
1076
  profileError: simslimProfileError,
1046
1077
  onPath: simslimProfile ? lookupSimSlim ? lookupSimSlim() : simslimIsOnPath() : false
1047
1078
  });
1048
- const remoteBackends = [.../* @__PURE__ */ new Set([...platform !== "android" ? [remoteIosSetting(projectSettings)] : [], ...platform !== "ios" ? [remoteAndroidSetting(projectSettings)] : []])].filter((backend) => backend !== null);
1079
+ const remoteBackends = configuredRemoteBackends(projectSettings, platform);
1049
1080
  const daemonInEnv = Boolean(remoteEnv.AGENT_DEVICE_DAEMON_BASE_URL?.trim() && remoteEnv.AGENT_DEVICE_DAEMON_AUTH_TOKEN?.trim());
1050
1081
  const agentDeviceOnPath = remoteBackends.length ? lookupAgentDevice ? lookupAgentDevice() : agentDeviceIsOnPath() : false;
1051
1082
  const easCliResolvable = remoteBackends.includes("eas") ? lookupEasCli ? lookupEasCli() : Boolean(resolveEasCliBin(projectRoot)) : false;
@@ -1085,6 +1116,7 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1085
1116
  easFinding,
1086
1117
  easBuildDownloadFinding,
1087
1118
  concurrencyFinding,
1119
+ readOffloadCandidate(tailnetStatus, now(), host),
1088
1120
  memoryAdvice ? finding("cost", "Host memory pressure can stall the iOS simulator", memoryAdvice, "Free host memory, then retry. Run `stim guide lifecycle simslim` for the optional memory reduction setup.") : null,
1089
1121
  simslimFinding,
1090
1122
  ...remoteFindings,
@@ -1338,15 +1370,15 @@ function hostedAgentDriverFinding(driver, apps) {
1338
1370
  return {
1339
1371
  code: "hosted-agent-driver",
1340
1372
  level: "note",
1341
- title: `${apps === 1 ? "A hosted macOS app runs" : `${apps} hosted macOS apps run`} with no agent driver`,
1342
- detail: "The client's coding agent cannot drive these apps because `hosting.agentDriver` is none, so stim-server starts no driver. agent-device starts only when its daemon offers the macos-app lease, which limits a client to one app.",
1343
- fix: "stim settings set hosting.agentDriver agent-device, with an agent-device that offers the macos-app lease"
1373
+ title: `${apps === 1 ? "A hosted app runs" : `${apps} hosted apps run`} with no agent driver`,
1374
+ detail: "The client's coding agent cannot drive these apps because `hosting.agentDriver` is none, so stim-server starts no driver. agent-device confines macOS control to one macos-app lease and iOS control to one simulator through its daemon policy.",
1375
+ fix: "stim settings set hosting.agentDriver agent-device, with agent-device 0.21.20 or later"
1344
1376
  };
1345
1377
  }
1346
- function runningHostedMacosApps() {
1378
+ function runningHostedApps() {
1347
1379
  try {
1348
1380
  return readHostedSessions().filter((session) => {
1349
- if (session.platform !== "macos" || session.state !== "ready" || !session.appAttempt) return false;
1381
+ if (!["macos", "ios"].includes(session.platform) || session.state !== "ready" || !session.appAttempt) return false;
1350
1382
  try {
1351
1383
  return readHostedAppMetadata(session.id, session.appAttempt).state === "installed";
1352
1384
  } catch {
@@ -1357,10 +1389,10 @@ function runningHostedMacosApps() {
1357
1389
  return 0;
1358
1390
  }
1359
1391
  }
1360
- /** Notes installed hosted macOS apps on this Mac while `hosting.agentDriver` starts no driver. */
1392
+ /** Notes installed hosted macOS and iOS apps on this Mac while `hosting.agentDriver` starts no driver. */
1361
1393
  function inspectHostedAgentDriver() {
1362
1394
  const hosting = loadConfig$1()?.hosting;
1363
- return hostedAgentDriverFinding(isJsonObject(hosting) ? hosting.agentDriver : void 0, runningHostedMacosApps());
1395
+ return hostedAgentDriverFinding(isJsonObject(hosting) ? hosting.agentDriver : void 0, runningHostedApps());
1364
1396
  }
1365
1397
  //#endregion
1366
1398
  //#region src/commands/doctor.ts
@@ -1560,12 +1592,22 @@ function doctorCommand(program, version, inspectVersions = inspectStimVersions,
1560
1592
  findings.push(...deviceHosts.findings);
1561
1593
  const agentDriver = inspectHostedAgentDriver();
1562
1594
  if (agentDriver) findings.push(agentDriver);
1595
+ const maintenance = maintenanceStatus();
1596
+ const maintenanceLines = [
1597
+ phaseLine("maintenance.mode", maintenance.mode),
1598
+ phaseLine("maintenance log", maintenanceNdjsonFile()),
1599
+ phaseLine("last pass", maintenanceLine(maintenance, false) ?? "no pass has run yet")
1600
+ ];
1563
1601
  if (opts.json) {
1564
1602
  console.log(JSON.stringify({
1565
1603
  project: root,
1566
1604
  platform: opts.platform ?? null,
1567
1605
  stim,
1568
1606
  budget: budget.report,
1607
+ maintenance: {
1608
+ ...maintenance,
1609
+ logPath: maintenanceNdjsonFile()
1610
+ },
1569
1611
  buildMachines: buildMachines.machines,
1570
1612
  deviceHosts: deviceHosts.machines,
1571
1613
  findings
@@ -1574,7 +1616,7 @@ function doctorCommand(program, version, inspectVersions = inspectStimVersions,
1574
1616
  return;
1575
1617
  }
1576
1618
  if (findings.length === 0) {
1577
- const lines = doctorSuccessLines(opts.platform, stim, budget.report);
1619
+ const lines = [...doctorSuccessLines(opts.platform, stim, budget.report), ...maintenanceLines];
1578
1620
  for (const [index, line] of lines.entries()) if (index === 1) console.log(chalk.green(line));
1579
1621
  else if (line && !line.startsWith(" ")) console.log(chalk.bold(line));
1580
1622
  else console.log(chalk.dim(line));
@@ -1583,7 +1625,11 @@ function doctorCommand(program, version, inspectVersions = inspectStimVersions,
1583
1625
  }
1584
1626
  const ordered = findings.toSorted((a, b) => a.level === b.level ? 0 : a.level === "cost" ? -1 : 1);
1585
1627
  console.log(chalk.bold(`Doctor (${doctorTarget(opts.platform)})`));
1586
- for (const line of [...stimVersionLines(stim), ...budgetLines(budget.report)]) console.log(chalk.dim(line));
1628
+ for (const line of [
1629
+ ...stimVersionLines(stim),
1630
+ ...budgetLines(budget.report),
1631
+ ...maintenanceLines
1632
+ ]) console.log(chalk.dim(line));
1587
1633
  for (const f of ordered) {
1588
1634
  const tag = f.level === "cost" ? chalk.yellow("costs time") : chalk.dim("note");
1589
1635
  console.log(`\n${tag} ${chalk.bold(f.title)}`);
@@ -1,17 +1,17 @@
1
1
  import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  import { R as deviceSlotKey, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
3
- import { E as withDirLock, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
4
- import { f as readWorkspaceLaunches } from "./state-CaeaHbDA.mjs";
5
- import { v as launchErrorPreview } from "./command-output-CliHHn7a.mjs";
6
- import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-W5Pi1q38.mjs";
7
- import { o as androidHome, u as androidToolCwd } from "./android-Db2eEMaf.mjs";
8
- import { l as resolveProjectMetro } from "./metro-Di8EN2Tb.mjs";
3
+ import { v as launchErrorPreview } from "./command-output-DnJMdEY-.mjs";
4
+ import { l as resolveProjectMetro } from "./metro-BeK1ZPz1.mjs";
5
+ import { r as readWorkspaceState$1, w as withDirLock } from "./workspace-state-B8-aJ1_5.mjs";
6
+ import { a as parseLease, n as fileLeaseIo, t as deviceLeasePath } from "./device-lease-D-hLqx1b.mjs";
7
+ import { f as readWorkspaceLaunches } from "./state-DcW6vOd_.mjs";
8
+ import { o as androidHome, u as androidToolCwd } from "./android-B_NumVyQ.mjs";
9
9
  import { r as deviceConsoleLevel } from "./ios-device-Ee7sDXfp.mjs";
10
- import { n as parseLogcatLine, t as androidClockOffset } from "./android-CYmq0jNi.mjs";
10
+ import { n as parseLogcatLine, t as androidClockOffset } from "./android-r2rBL8kV.mjs";
11
11
  import { createHash, randomUUID } from "node:crypto";
12
12
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
13
13
  import { basename, dirname, join, resolve, sep } from "node:path";
14
- import { attachExpoErrorContext, readLogRecords, sortByTs } from "@stim-cli/core/state";
14
+ import { NATIVE_CRASH_LOG_PREFIX, attachExpoErrorContext, readLogRecords, readLogTimeline } from "@stim-cli/core/state";
15
15
  import { homedir } from "node:os";
16
16
  //#region src/diagnostics/diagnostic-store.ts
17
17
  /** Publishes an immutable diagnostic snapshot atomically; existing evidence wins. */
@@ -35,7 +35,6 @@ function writeDiagnosticOnce(path, content) {
35
35
  }
36
36
  //#endregion
37
37
  //#region src/diagnostics/native-crash.ts
38
- const CRASH_LOG_PREFIX = "native-crash-";
39
38
  const IOS_CRASH_REPORT_RETRY = "iOS crash reports can take about a minute or longer to appear. Run `stim logs --errors` again for the native stack.";
40
39
  /** Accepts only a report for this app, simulator and launch window. */
41
40
  function parseIosCrash(text, target) {
@@ -347,7 +346,7 @@ function captureNativeCrashes(target, logsDir) {
347
346
  record.incident ?? record.deviceTs,
348
347
  record.rawReport
349
348
  ])).digest("hex");
350
- writeDiagnosticOnce(join(logsDir, `${CRASH_LOG_PREFIX}${key}.ndjson`), `${JSON.stringify(record)}\n`);
349
+ writeDiagnosticOnce(join(logsDir, `${NATIVE_CRASH_LOG_PREFIX}${key}.ndjson`), `${JSON.stringify(record)}\n`);
351
350
  } catch {}
352
351
  }
353
352
  return records;
@@ -358,7 +357,7 @@ function printNativeCrashReport(target, logsDir, emit, remote) {
358
357
  }
359
358
  /** Captures native crash reports for this workspace's current launches and returns the whole timeline. */
360
359
  function captureWorkspaceCrashes(root, logsDir) {
361
- const timeline = readLogRecords(logsDir, (name) => !isCrashLog(name));
360
+ const timeline = readLogRecords(logsDir, (name) => !name.startsWith(NATIVE_CRASH_LOG_PREFIX));
362
361
  const attempts = timeline.filter((record) => record.event === "launch_attempt");
363
362
  const launches = readWorkspaceLaunches(root);
364
363
  const latest = /* @__PURE__ */ new Map();
@@ -409,10 +408,7 @@ function captureWorkspaceCrashes(root, logsDir) {
409
408
  }, logsDir);
410
409
  } catch {}
411
410
  }
412
- return sortByTs([...timeline, ...readLogRecords(logsDir, isCrashLog)]);
413
- }
414
- function isCrashLog(name) {
415
- return name.startsWith(CRASH_LOG_PREFIX);
411
+ return readLogTimeline(logsDir, timeline);
416
412
  }
417
413
  //#endregion
418
414
  //#region src/diagnostics/error-symbolication.ts