stim 1.16.0 → 1.17.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 (107) 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--GorydsK.mjs} +679 -317
  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-DM984mnd.mjs} +34 -18
  9. package/dist/{build-plan-DjeSb2Xl.mjs → build-plan-CmQCoyT-.mjs} +345 -86
  10. package/dist/{build-slots-rUk77xqh.mjs → build-slots-41v_d2x9.mjs} +35 -25
  11. package/dist/{cli-BkYZ_6hy.mjs → cli-Daxug5-K.mjs} +17 -17
  12. package/dist/cli.mjs +1 -1
  13. package/dist/collector-run.mjs +3 -3
  14. package/dist/{command-output-DnJMdEY-.mjs → command-output-CDAIFD-8.mjs} +3 -3
  15. package/dist/{created-devices-D5owjN4C.mjs → created-devices-CpvzBL25.mjs} +1 -1
  16. package/dist/{dependency-state-B6cMXZab.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
  17. package/dist/{deps-urbmfXig.mjs → deps-DNWaQs4V.mjs} +1 -1
  18. package/dist/{dev-client-CroAIjjj.mjs → dev-client-B-XC4uRH.mjs} +2 -2
  19. package/dist/{device-DZQjF7H5.mjs → device-C8IOoJPv.mjs} +7 -7
  20. package/dist/device-capacity-D0GWHnTM.mjs +580 -0
  21. package/dist/device-host-worker.mjs +436 -154
  22. package/dist/{device-ios-BadmeL2V.mjs → device-ios-Bc7vbGwl.mjs} +75 -44
  23. package/dist/{device-lease-D-hLqx1b.mjs → device-lease-DpoS36_u.mjs} +2 -2
  24. package/dist/{device-lease-run-CqjShqWW.mjs → device-lease-run-B8YltIH_.mjs} +3 -3
  25. package/dist/{device-pool-CUaEWMMm.mjs → device-pool-3o7s5lo4.mjs} +2 -2
  26. package/dist/{device-remote-D5WVWqOq.mjs → device-remote-hFSkpfQu.mjs} +12 -12
  27. package/dist/{doctor-Cimf-gGB.mjs → doctor-Cnk9d7Hm.mjs} +32 -51
  28. package/dist/{error-diagnostics-B9L4Ae0x.mjs → error-diagnostics-C-NMnhrk.mjs} +7 -7
  29. package/dist/{gc-Bqy4bjEu.mjs → gc-DxCL2A46.mjs} +308 -26
  30. package/dist/{gradle-BsO1MdsH.mjs → gradle-D7-L2Th3.mjs} +3 -3
  31. package/dist/{guide-B_uyKTxW.mjs → guide-DjrTQGik.mjs} +1662 -565
  32. package/dist/{guide-status-CjEZMAD4.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
  33. package/dist/hosted-android-DwLS4Mse.mjs +32 -0
  34. package/dist/{hosted-client-a-WmqE2L.mjs → hosted-client-wk1W_CNW.mjs} +2 -3
  35. package/dist/hosted-ios-aMo7Cljf.mjs +15 -0
  36. package/dist/{hosted-logs-D5g041Q3.mjs → hosted-logs-DqYmWtXc.mjs} +16 -16
  37. package/dist/{hosted-macos-BSZyIb3y.mjs → hosted-macos-dlHXEeF5.mjs} +4 -4
  38. package/dist/{hosted-ios-CIWelIqf.mjs → hosted-native-C9xJ9rpA.mjs} +77 -42
  39. package/dist/{idle-DYVymG92.mjs → idle-B2m-Ovod.mjs} +122 -58
  40. package/dist/{idle-shutdown-CTsDnAde.mjs → idle-shutdown-_3oOV1V_.mjs} +25 -17
  41. package/dist/{in-use-DvBw0Kxo.mjs → in-use-CvGOUn-x.mjs} +11 -11
  42. package/dist/{ios-aq3QoGI3.mjs → ios-DE6duQoz.mjs} +170 -88
  43. package/dist/{ios-BaInn3sW.mjs → ios-DSfnPfse.mjs} +4 -4
  44. package/dist/{ios-device-FfCN_C8p.mjs → ios-device-pAN4t54-.mjs} +1 -1
  45. package/dist/ios-state-8L3lgum6.mjs +89 -0
  46. package/dist/{launch-verify-2DBRizjo.mjs → launch-verify-lTnWZikR.mjs} +2 -2
  47. package/dist/{logs-CHEVK8zD.mjs → logs-DUoKlDoS.mjs} +36 -23
  48. package/dist/{client-BTY686R6.mjs → machines-C2XiNwh9.mjs} +278 -20
  49. package/dist/{macos-CSYEAFmF.mjs → macos-32po3kUq.mjs} +14 -14
  50. package/dist/macos-run.mjs +1 -1
  51. package/dist/maintenance-run.mjs +2 -2
  52. package/dist/{metro-BeK1ZPz1.mjs → metro-g8NqiJp-.mjs} +1 -1
  53. package/dist/{metro-gateway-BJ-rxQAB.mjs → metro-gateway-DmslUM30.mjs} +22 -3
  54. package/dist/{named-ports-Bk_YXEmE.mjs → named-ports-C8m8EQs2.mjs} +2 -2
  55. package/dist/{native-run-NaBrRFtQ.mjs → native-run-D1Q56rAD.mjs} +17 -6
  56. package/dist/{native-runtime-DBzRnEKS.mjs → native-runtime-DhJI3IVQ.mjs} +6 -6
  57. package/dist/offload-worker.mjs +8 -8
  58. package/dist/{teardown-CL6vDs2Y.mjs → ownership-BMLqyBgJ.mjs} +603 -26
  59. package/dist/{ownership-DiA16AsN.mjs → ownership-DyRQru0q.mjs} +2 -2
  60. package/dist/{ports-R2k4Knz3.mjs → ports-4nYqxPWC.mjs} +2 -2
  61. package/dist/{prebuild-D2fsca_Z.mjs → prebuild-D7RUMkwN.mjs} +3 -3
  62. package/dist/{preview-DhVbvPAZ.mjs → preview-CxPRIEht.mjs} +7 -7
  63. package/dist/{project-Du91Ilvc.mjs → project-D47Rh0ly.mjs} +7 -2
  64. package/dist/{recordings-BIXOzVnr.mjs → recordings-BNgsD1Tr.mjs} +1 -1
  65. package/dist/{reload-rM9jV1fz.mjs → reload-qZei6iWn.mjs} +62 -35
  66. package/dist/{remote-cache-C8-V13XN.mjs → remote-cache-Ci4DQYYj.mjs} +3 -3
  67. package/dist/{run-f4vI6EnK.mjs → run-CkE9y0nc.mjs} +1 -1
  68. package/dist/{server-bare-CpPGmnmO.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
  69. package/dist/{server-expo-MC0rHlOC.mjs → server-expo-CHTyXD0G.mjs} +2 -2
  70. package/dist/server-expo-CeakhI8P.mjs +2 -0
  71. package/dist/{settings-oTSkkNrM.mjs → settings-Cfd-0r2d.mjs} +10 -9
  72. package/dist/{settings-ZkDzkzvB.mjs → settings-SR9OmW9h.mjs} +3 -3
  73. package/dist/settings.schema.json +42 -13
  74. package/dist/{simslim-B8-7AoTI.mjs → simslim-BWNrc3M3.mjs} +3 -3
  75. package/dist/{slot-launch-B8VrHxn1.mjs → slot-launch-DmjBmpQV.mjs} +8 -8
  76. package/dist/{start-CX8Ig-U2.mjs → start-BopZcr4n.mjs} +1 -1
  77. package/dist/{start-ClDfwXa4.mjs → start-uee1wWjY.mjs} +11 -11
  78. package/dist/{state-DcW6vOd_.mjs → state-BKa8wNN5.mjs} +1 -1
  79. package/dist/{state-3DwSQIBK.mjs → state-DyaNTgfR.mjs} +1 -1
  80. package/dist/{state-C1jB7Q8q.mjs → state-PeQiJuIu.mjs} +2 -2
  81. package/dist/{stats-CEZ_i5je.mjs → stats-Bq4fJOdB.mjs} +7 -4
  82. package/dist/{status-BuvBFeII.mjs → status-CzS4bPO9.mjs} +4 -4
  83. package/dist/{status-Cs7VLg9D.mjs → status-dXX2JX1C.mjs} +125 -35
  84. package/dist/{stop-Dt_dM16A.mjs → stop-BCwvvs7F.mjs} +2 -2
  85. package/dist/{stop-Bffozsac.mjs → stop-BjizrQwv.mjs} +48 -27
  86. package/dist/{stop-B0Yxtmiy.mjs → stop-Cr1d8DSE.mjs} +4 -4
  87. package/dist/{stop-cause-CidxIqup.mjs → stop-cause-rhmGfwvt.mjs} +1 -1
  88. package/dist/supervisor-run.d.mts +7 -4
  89. package/dist/supervisor-run.mjs +26 -28
  90. package/dist/{support-D4aYJniO.mjs → support-BJPgYCu-.mjs} +44 -19
  91. package/dist/{support-CAP8qDUF.mjs → support-F9-sr0Oi.mjs} +6 -6
  92. package/dist/{toolchain-DkUflduf.mjs → toolchain-CNIBTIOv.mjs} +7 -7
  93. package/dist/{warm-progress-CpGv3Wb6.mjs → warm-progress-DF4JRvYI.mjs} +3 -3
  94. package/dist/watchman-APYjXSQa.mjs +48 -0
  95. package/dist/{web-DWUaMp8P.mjs → web-exQtGkoc.mjs} +12 -13
  96. package/dist/web-run.mjs +4 -4
  97. package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
  98. package/dist/{workspace-state-B8-aJ1_5.mjs → workspace-state-DqcCbpE7.mjs} +2 -2
  99. package/dist/{workspaces-BV4pdHFz.mjs → workspaces-Bcib97-6.mjs} +3 -3
  100. package/dist/{worktree-jnG8Iph5.mjs → worktree-Dc98cE6_.mjs} +1 -1
  101. package/dist/{worktree-K-xj9Db0.mjs → worktree-ZpZR3S1k.mjs} +141 -40
  102. package/package.json +5 -5
  103. package/dist/device-capacity-BHwxNBlu.mjs +0 -136
  104. package/dist/ios-state-DS5UpYTH.mjs +0 -48
  105. package/dist/machines-BIBUdhpj.mjs +0 -265
  106. package/dist/ownership-hCkPzGmr.mjs +0 -571
  107. package/dist/server-expo-CVqO2c35.mjs +0 -2
@@ -1,34 +1,34 @@
1
1
  import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  import { B as parseDeviceSlotKey, F as volumeRootFor, H as projectDeviceSlots, R as deviceSlotKey, _ as loadConfig$1, d as getConfigDir, j as formatBytes } from "./config-D0DbpZtM.mjs";
3
3
  import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
- import { a as formatElapsed, i as formatDuration, r as clockTime, u as plural } from "./command-output-DnJMdEY-.mjs";
5
- import { P as resolveSettings, at as gitCommonDirOnDisk, gt as repoRoot, it as gitCommonDir, ut as linkedWorktreesOnDisk } from "./settings-oTSkkNrM.mjs";
6
- import { i as detectPlatforms, r as detectIsExpo, s as findServerWorkspace, u as projectShortcut } from "./project-Du91Ilvc.mjs";
7
- import { a as listeningPidsByPort, c as processCwds, d as isMetroRunning, i as listeningPids, l as resolveProjectMetro, o as pidExists, s as processCwd } from "./metro-BeK1ZPz1.mjs";
8
- import { b as workspaceLogErrorIndex, r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
9
- import { a as readWebPage, o as readWebRecord, u as webFacts } from "./state-C1jB7Q8q.mjs";
10
- import { _ as statusActivity, a as capacity, b as withWebFacts, c as diskLine, d as gitSummaryText, f as metroReverseTargets, g as remoteDeviceState, h as remoteDeviceLine, i as applyMachineMemory, l as environmentState, m as poolLine, n as activityLabel, o as deviceLeaseLines, p as parseDfFree, r as addReverseMissingIssue, s as deviceLeaseStates, v as tightVolumes, y as unprovisionedWorktrees } from "./status-BuvBFeII.mjs";
11
- import { J as attributeMachineUsage, Y as readFootprints, a as machineNumber } from "./budget-DjrURfQn.mjs";
12
- import { d as readWebActivity, f as tailLines, i as createDeviceProcessTables, r as createActivityReader } from "./activity-BxwdD81S.mjs";
13
- import { i as listLeaseFiles, r as leaseIsExpired, s as parseWorkspaceLeases } from "./device-lease-D-hLqx1b.mjs";
14
- import { c as readMetroTunnel, f as readWorkspaceLaunches, u as readRemoteSession } from "./state-DcW6vOd_.mjs";
15
- import { S as statsProjectKey$1, b as readStats$1, c as ACTIVE_BUILD_KEY, d as buildReport, f as buildStatusLine, m as parseActiveBuild, u as activeBuildState } from "./idle-DYVymG92.mjs";
16
- import { F as ownedAvdDeviceProfile, O as listAdbDevices, d as androidToolPath, ft as parkedMaxSetting, ot as POOL_SETTING_REMEDY, pt as readParked, z as ownedAvdSerialResolver } from "./android-B_NumVyQ.mjs";
17
- import { d as listAllIosSimsAsync, v as parseSimctlList } from "./ios-BaInn3sW.mjs";
18
- import { t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
4
+ import { a as formatElapsed, i as formatDuration, r as clockTime, u as plural } from "./command-output-CDAIFD-8.mjs";
5
+ import { P as resolveSettings, at as gitCommonDirOnDisk, gt as repoRoot, it as gitCommonDir, ut as linkedWorktreesOnDisk } from "./settings-Cfd-0r2d.mjs";
6
+ import { a as detectTutorial, c as findServerWorkspace, d as projectShortcut, i as detectPlatforms, p as readAppJson, r as detectIsExpo } from "./project-D47Rh0ly.mjs";
7
+ import { a as listeningPidsByPort, c as processCwds, d as isMetroRunning, i as listeningPids, l as resolveProjectMetro, o as pidExists, s as processCwd } from "./metro-g8NqiJp-.mjs";
8
+ import { b as workspaceLogErrorIndex, r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-DqcCbpE7.mjs";
9
+ import { a as readWebPage, o as readWebRecord, u as webFacts } from "./state-PeQiJuIu.mjs";
10
+ import { _ as statusActivity, a as capacity, b as withWebFacts, c as diskLine, d as gitSummaryText, f as metroReverseTargets, g as remoteDeviceState, h as remoteDeviceLine, i as applyMachineMemory, l as environmentState, m as poolLine, n as activityLabel, o as deviceLeaseLines, p as parseDfFree, r as addReverseMissingIssue, s as deviceLeaseStates, v as tightVolumes, y as unprovisionedWorktrees } from "./status-CzS4bPO9.mjs";
11
+ import { X as readFootprints, Y as attributeMachineUsage, a as machineNumber } from "./budget-DM984mnd.mjs";
12
+ import { d as readWebActivity, f as tailLines, i as createDeviceProcessTables, r as createActivityReader } from "./activity-IRvJY_ru.mjs";
13
+ import { i as listLeaseFiles, r as leaseIsExpired, s as parseWorkspaceLeases } from "./device-lease-DpoS36_u.mjs";
14
+ import { c as readMetroTunnel, f as readWorkspaceLaunches, u as readRemoteSession } from "./state-BKa8wNN5.mjs";
15
+ import { F as ownedAvdDeviceProfile, O as listAdbDevices, d as androidToolPath, ft as parkedMaxSetting, ot as POOL_SETTING_REMEDY, pt as readParked, z as ownedAvdSerialResolver } from "./android-B_YNZy61.mjs";
16
+ import { d as listAllIosSimsAsync, v as parseSimctlList } from "./ios-DSfnPfse.mjs";
17
+ import { t as resolveSupervisorTarget } from "./ownership-BMLqyBgJ.mjs";
18
+ import { b as readStats$1, c as ACTIVE_BUILD_KEY, d as buildReport, f as buildStatusLine, m as parseActiveBuild, u as activeBuildState, w as statsProjectKey$1 } from "./idle-B2m-Ovod.mjs";
19
19
  import { a as parseNdjsonLine } from "./ndjson-PclOIwVY.mjs";
20
- import { n as describeMetroLastStop, o as metroLastStop } from "./stop-cause-CidxIqup.mjs";
21
- import { l as probeHostedSession } from "./hosted-client-a-WmqE2L.mjs";
22
- import { m as hasMetroReverse } from "./app-install-CKWdbXIX.mjs";
23
- import "./hosted-macos-BSZyIb3y.mjs";
24
- import { m as readEasSessionLedger$1, p as easMachineStateRoot$1 } from "./device-remote-D5WVWqOq.mjs";
25
- import { d as readIosDevices, s as isReachable } from "./ios-device-FfCN_C8p.mjs";
20
+ import { n as describeMetroLastStop, o as metroLastStop } from "./stop-cause-rhmGfwvt.mjs";
21
+ import { l as probeHostedSession } from "./hosted-client-wk1W_CNW.mjs";
22
+ import { m as hasMetroReverse } from "./app-install-VPkI5WEg.mjs";
23
+ import "./hosted-macos-dlHXEeF5.mjs";
24
+ import { m as readEasSessionLedger$1, p as easMachineStateRoot$1 } from "./device-remote-hFSkpfQu.mjs";
25
+ import { d as readIosDevices, s as isReachable } from "./ios-device-pAN4t54-.mjs";
26
26
  import { n as maintenanceLines, r as maintenanceStatus } from "./status-DlKvVJBE.mjs";
27
- import { i as createStatusMeasurer, n as workspacePhase, r as applyStatusMeasures, s as mergeState } from "./warm-progress-CpGv3Wb6.mjs";
28
- import { r as workspaceRecordingEnabled } from "./recordings-BIXOzVnr.mjs";
27
+ import { i as createStatusMeasurer, n as workspacePhase, r as applyStatusMeasures, s as mergeState } from "./warm-progress-DF4JRvYI.mjs";
28
+ import { r as workspaceRecordingEnabled } from "./recordings-BNgsD1Tr.mjs";
29
29
  import { t as watchStdoutReader } from "./stdout-reader-C8yjre1x.mjs";
30
30
  import { t as triggerMaintenance } from "./trigger-Bemu8ytw.mjs";
31
- import { i as readSupervisorState } from "./stop-Bffozsac.mjs";
31
+ import { i as readSupervisorState } from "./stop-BjizrQwv.mjs";
32
32
  import { createHash, randomUUID } from "node:crypto";
33
33
  import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
34
34
  import { join, posix, resolve } from "node:path";
@@ -36,12 +36,47 @@ import { closeSync as closeSync$1, existsSync as existsSync$1, fstatSync as fsta
36
36
  import { homedir, totalmem } from "os";
37
37
  import { basename as basename$1, dirname as dirname$1, join as join$1 } from "path";
38
38
  import { rotatedLogPath } from "@stim-cli/core";
39
- import { ERROR_SOURCES, archiveRoot, archivedUsage, bundleMarkerPlatform, countErrorEntries, gitMergeCacheDir, hostedIosPlacements, hostedIosRecords, hostedIosStatus, linkReplacedArchives, logFiles, macosAppState, parseHostedIosPlacement, queryLogs, readArchives, readBuildDetail, readBuildHistory, readDeviceIdleShutdowns, readIdleStop, readLastBuilds, readMacosRecord, recordMatches, sortByTs, unreadableHostedIos, withDerivedFacts, withGitChip, withStateReadCache } from "@stim-cli/core/state";
39
+ import { DEVICE_RECLAIM_REASON, ERROR_SOURCES, archiveRoot, archivedUsage, bundleMarkerPlatform, countErrorEntries, gitMergeCacheDir, hostedAndroidPlacements, hostedAndroidRecords, hostedAndroidStatus, hostedIosPlacements, hostedIosRecords, hostedIosStatus, linkReplacedArchives, logFiles, macosAppState, parseHostedAndroidPlacement, parseHostedIosPlacement, queryLogs, readArchives, readBuildDetail, readBuildHistory, readDeviceIdleShutdowns, readDevicePlacement, readIdleStop, readLastBuilds, readMacosRecord, recordMatches, sortByTs, unreadableHostedAndroid, unreadableHostedIos, withDerivedFacts, withGitChip, withStateReadCache } from "@stim-cli/core/state";
40
40
  import chalk from "chalk";
41
+ //#region src/device-host/hosted-native-status.ts
42
+ function hostedNativeProbe(placement, probe, slot, platform) {
43
+ const label = platform === "ios" ? "iOS" : "Android";
44
+ const rerun = `stim ${platform} --remote ${placement.machine}${slot === "default" ? "" : ` --slot ${slot}`}`;
45
+ return {
46
+ state: probe.state === "ready" ? "ready" : probe.state === "stopped" ? "stopped" : "unverified",
47
+ ...probe.state === "ready" ? {} : { warning: probe.state === "stopped" ? `The ${label} session on ${placement.machine} stopped or no longer exists. Run ${rerun} to launch it again, or stim stop to clear the placement.` : `${placement.machine} could not confirm its ${label} session${probe.state === "unknown" ? probe.notice ? `: ${probe.notice}` : "" : `: ${probe.reason.replace(/\.+$/, "")}`}. The placement stays recorded; run stim stop when the host answers.` }
48
+ };
49
+ }
50
+ //#endregion
51
+ //#region src/device-host/hosted-android-status.ts
52
+ function applyHostedAndroidProbe(placement, probe, slot = "default") {
53
+ const { state, warning } = hostedNativeProbe(placement, probe, slot, "android");
54
+ const host = {
55
+ ...hostedAndroidStatus(placement),
56
+ state
57
+ };
58
+ return {
59
+ android: {
60
+ host,
61
+ name: host.device?.name,
62
+ serial: null,
63
+ owned: false,
64
+ physical: false,
65
+ state
66
+ },
67
+ ...warning ? { warning } : {}
68
+ };
69
+ }
70
+ async function readHostedAndroidStatus(placements) {
71
+ return Object.fromEntries(await Promise.all(Object.entries(placements).map(async ([slot, placement]) => [slot, applyHostedAndroidProbe(placement, await probeHostedSession({
72
+ ...placement,
73
+ platform: "android"
74
+ }), slot)])));
75
+ }
76
+ //#endregion
41
77
  //#region src/device-host/hosted-ios-status.ts
42
78
  function applyHostedIosProbe(placement, probe, slot = "default") {
43
- const rerun = `stim ios --remote ${placement.machine}${slot === "default" ? "" : ` --slot ${slot}`}`;
44
- const state = probe.state === "ready" ? "ready" : probe.state === "stopped" ? "stopped" : "unverified";
79
+ const { state, warning } = hostedNativeProbe(placement, probe, slot, "ios");
45
80
  return {
46
81
  ios: {
47
82
  host: {
@@ -53,7 +88,7 @@ function applyHostedIosProbe(placement, probe, slot = "default") {
53
88
  owned: false,
54
89
  state
55
90
  },
56
- ...probe.state === "ready" ? {} : { warning: probe.state === "stopped" ? `The iOS session on ${placement.machine} stopped or no longer exists. Run ${rerun} to launch it again, or stim stop to clear the placement.` : `${placement.machine} could not confirm its iOS session${probe.state === "unknown" ? probe.notice ? `: ${probe.notice}` : "" : `: ${probe.reason.replace(/\.+$/, "")}`}. The placement stays recorded; run stim stop when the host answers.` }
91
+ ...warning ? { warning } : {}
57
92
  };
58
93
  }
59
94
  async function readHostedIosStatus(placements) {
@@ -1003,7 +1038,7 @@ function physicalDeviceLine(device, now) {
1003
1038
  const WATCH_GIT_MAX_AGE_MS = 6e4;
1004
1039
  function iosStatusName(ios) {
1005
1040
  const host = ios.host;
1006
- return host && !ios.udid ? `${host.device?.name ?? "iOS simulator"} (${host.device ? `iOS ${host.device.runtime.replace(/^iOS /, "")}` : "runtime pending"}) on ${host.machine}` : ios.name ?? ios.udid;
1041
+ return host && !ios.udid ? `${host.device?.name ?? "iOS simulator"} (${host.device ? `iOS ${host.device.runtime.replace(/^iOS /, "")}` : "runtime pending"}) on ${host.machine}${host.selected === "auto" && host.reason ? ` (auto: ${host.reason})` : ""}` : ios.name ?? ios.udid;
1007
1042
  }
1008
1043
  function iosStatusLines(record, slotLabel) {
1009
1044
  const out = [];
@@ -1024,6 +1059,48 @@ function iosStatusLines(record, slotLabel) {
1024
1059
  }
1025
1060
  return out;
1026
1061
  }
1062
+ function applyHostedAndroidStates(state, saved, factsBySlot) {
1063
+ for (const [slot, record] of Object.entries(hostedAndroidRecords(saved))) if (!parseHostedAndroidPlacement(record)) state.warnings.push(unreadableHostedAndroid(slot));
1064
+ for (const [slot, facts] of Object.entries(factsBySlot)) {
1065
+ const local = slot === "default" ? state.android : state.slots?.find((entry) => entry.slot === slot)?.android;
1066
+ const conflict = local && (local.state === "detected" || local.state === "unknown");
1067
+ const android = conflict ? {
1068
+ ...local,
1069
+ host: facts.android.host
1070
+ } : facts.android;
1071
+ if (conflict) state.warnings.push(`Slot ${slot} records both a local and hosted Android emulator (${facts.android.name} on ${facts.android.host?.machine}); run stim stop to reconcile them.`);
1072
+ if (slot === "default") state.android = android;
1073
+ else {
1074
+ state.slots ??= [];
1075
+ const savedSlot = state.slots.find((entry) => entry.slot === slot);
1076
+ if (savedSlot) savedSlot.android = android;
1077
+ else state.slots.push({
1078
+ slot,
1079
+ ios: null,
1080
+ android
1081
+ });
1082
+ }
1083
+ if (facts.warning) state.warnings.push(facts.warning);
1084
+ state.live ||= facts.android.state === "ready";
1085
+ }
1086
+ }
1087
+ function androidStatusLines(record, slotLabel) {
1088
+ const out = [];
1089
+ if (record?.host && !record.serial) {
1090
+ const host = record.host;
1091
+ out.push(` android${slotLabel}: ${chalk.cyan(`${host.device?.name ?? "Android emulator"} on ${host.machine}${host.selected === "auto" && host.reason ? ` (auto: ${host.reason})` : ""}`)} ${host.state ?? "unverified"}`);
1092
+ } else if (record) {
1093
+ const kind = record.physical ? chalk.dim("(physical)") : chalk.dim("(emulator)");
1094
+ const observed = record.state ? ` ${record.state}${record.serial ? ` (${record.serial})` : ""}` : "";
1095
+ out.push(` android${slotLabel}: ${chalk.cyan(record.name)} ${kind}${observed}${record.owned ? chalk.dim(" (owned)") : ""}${activitySuffix(record.activity)}${appSuffix(record.app)}${idleShutdownSuffix(record.idleShutdown)}`);
1096
+ if (record.host) {
1097
+ const host = record.host;
1098
+ out.push(` android${slotLabel}: ${chalk.cyan(`${host.device?.name ?? "Android emulator"} on ${host.machine}${host.selected === "auto" && host.reason ? ` (auto: ${host.reason})` : ""}`)} ${host.state ?? "unverified"}`);
1099
+ }
1100
+ }
1101
+ if (record?.host?.agent.driver === "agent-device") out.push(chalk.dim(` agent: ${record.host.agent.command}`));
1102
+ return out;
1103
+ }
1027
1104
  function formatGb(mb) {
1028
1105
  return `${(mb / 1024).toFixed(1)} GB`;
1029
1106
  }
@@ -1041,6 +1118,7 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing, cachedArchives) {
1041
1118
  const cfg = loadConfig$1();
1042
1119
  const projects = Object.entries(cfg?.projects || {});
1043
1120
  const iosReads = projects.map(([path]) => readHostedIosStatus(hostedIosPlacements(readWorkspaceState$1(path))));
1121
+ const androidReads = projects.map(([path]) => readHostedAndroidStatus(hostedAndroidPlacements(readWorkspaceState$1(path))));
1044
1122
  const macosReads = projects.map(([path]) => readHostedMacosStatus(readMacosRecord(path)));
1045
1123
  const cwdRoot = findServerWorkspace(process.cwd())?.root ?? null;
1046
1124
  const worktrees = linkedWorktrees([process.cwd(), ...projects.map(([path]) => path)]);
@@ -1158,6 +1236,7 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing, cachedArchives) {
1158
1236
  if (facts.warning) state.warnings.push(facts.warning);
1159
1237
  state.live ||= facts.ios.state === "ready";
1160
1238
  }
1239
+ applyHostedAndroidStates(state, saved, await androidReads[i]);
1161
1240
  const tunnel = readMetroTunnel(path);
1162
1241
  if (state.metro && tunnel?.kind === "managed" && tunnel.port === state.metro.port && pidExists(tunnel.pid)) state.metro.tunnel = {
1163
1242
  provider: tunnel.provider,
@@ -1176,6 +1255,8 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing, cachedArchives) {
1176
1255
  })),
1177
1256
  recording: { enabled: workspaceRecordingEnabled(path, proj, cfg, process.env) }
1178
1257
  });
1258
+ const tutorial = detectTutorial(readAppJson(path));
1259
+ if (tutorial) state.tutorial = tutorial;
1179
1260
  }
1180
1261
  const physicalDevices = physicalDeviceStates(leaseFiles, parseWorkspaceLeases(saved?.deviceLeases), {
1181
1262
  root: path,
@@ -1228,6 +1309,19 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing, cachedArchives) {
1228
1309
  function readLogDerivedFacts(states) {
1229
1310
  const now = Date.now();
1230
1311
  for (const state of states) {
1312
+ const saved = readWorkspaceState$1(state.path);
1313
+ for (const devices of [{
1314
+ slot: "default",
1315
+ ios: state.ios,
1316
+ android: state.android
1317
+ }, ...state.slots ?? []]) for (const platform of ["ios", "android"]) {
1318
+ const device = devices[platform];
1319
+ const placement = readDevicePlacement(saved, platform, devices.slot);
1320
+ if (device) {
1321
+ if (placement) device.devicePlacement = placement;
1322
+ else delete device.devicePlacement;
1323
+ }
1324
+ }
1231
1325
  if (state.metro) {
1232
1326
  const bundle = metroBundleState(tailLines(join$1(workspaceLogsDir$1(state.path), "metro.ndjson")) ?? [], {
1233
1327
  running: state.metro.running,
@@ -1399,11 +1493,7 @@ function renderStatus({ archived, archivedUsage: usage, projects, states, labelO
1399
1493
  }, ...state.slots ?? []]) {
1400
1494
  const slotLabel = deviceState.slot === "default" ? "" : ` [${deviceState.slot}]`;
1401
1495
  out.push(...iosStatusLines(deviceState.ios, slotLabel));
1402
- if (deviceState.android) {
1403
- const kind = deviceState.android.physical ? chalk.dim("(physical)") : chalk.dim("(emulator)");
1404
- const observed = deviceState.android.state ? ` ${deviceState.android.state}${deviceState.android.serial ? ` (${deviceState.android.serial})` : ""}` : "";
1405
- out.push(` android${slotLabel}: ${chalk.cyan(deviceState.android.name)} ${kind}${observed}${deviceState.android.owned ? chalk.dim(" (owned)") : ""}${activitySuffix(deviceState.android.activity)}${appSuffix(deviceState.android.app)}${idleShutdownSuffix(deviceState.android.idleShutdown)}`);
1406
- }
1496
+ out.push(...androidStatusLines(deviceState.android, slotLabel));
1407
1497
  }
1408
1498
  if (state.macos) {
1409
1499
  const host = state.macos.host;
@@ -1535,7 +1625,7 @@ function lastBuildText(report) {
1535
1625
  return `${report.platform} ${report.status === "ok" ? source : `failed (${report.errorCode ?? "error"}), ${source}`}${took}${placement}`;
1536
1626
  }
1537
1627
  function idleShutdownSuffix(record) {
1538
- return record ? chalk.dim(` -- shut down after ${record.idleMinutes}m idle at ${record.at} (devices.idleShutdownMinutes)`) : "";
1628
+ return record ? chalk.dim(` -- shut down after ${record.idleMinutes}m idle at ${record.at} (${record.reason === DEVICE_RECLAIM_REASON ? "reclaimed for a waiting run; devices.reclaimIdleMinutes" : "devices.idleShutdownMinutes"})`) : "";
1539
1629
  }
1540
1630
  function activitySuffix(activity) {
1541
1631
  const label = activityLabel(activity, Date.now());
@@ -1,3 +1,3 @@
1
- import { t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
2
- import { a as resolveCollectorTargets, c as stopWorkspaceNow, i as readSupervisorState, n as clearSupervisorState, o as runStop, r as readCollectorState, s as stopCommand, t as clearCollectorState } from "./stop-Bffozsac.mjs";
1
+ import { t as resolveSupervisorTarget } from "./ownership-BMLqyBgJ.mjs";
2
+ import { a as resolveCollectorTargets, c as stopWorkspaceNow, i as readSupervisorState, n as clearSupervisorState, o as runStop, r as readCollectorState, s as stopCommand, t as clearCollectorState } from "./stop-BjizrQwv.mjs";
3
3
  export { clearCollectorState, clearSupervisorState, stopCommand as default, readCollectorState, readSupervisorState, resolveCollectorTargets, resolveSupervisorTarget, runStop, stopWorkspaceNow };
@@ -1,22 +1,22 @@
1
1
  import { B as parseDeviceSlotKey, D as withConfigLock$1, E as upsertProject, H as projectDeviceSlots, V as parseDeviceSlotOption, W as validateDeviceSlot, o as clearSupervisor, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
2
2
  import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
3
- import { f as refuseNoProject, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-DnJMdEY-.mjs";
4
- import { a as findCommandWorkspace } from "./project-Du91Ilvc.mjs";
5
- import { l as resolveProjectMetro, o as pidExists, r as killMetroTree, u as signalProcessTree } from "./metro-BeK1ZPz1.mjs";
6
- import { c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-B8-aJ1_5.mjs";
7
- import { n as fileLeaseIo, u as releaseWorkspaceLeases } from "./device-lease-D-hLqx1b.mjs";
8
- import { a as clearRemoteSession, c as readMetroTunnel, i as clearManagedMetroTunnel, o as clearWorkspaceLaunches, s as clearWorkspaceSupervisor, u as readRemoteSession } from "./state-DcW6vOd_.mjs";
9
- import { a as teardownOwnedBrowser, i as teardownOwnedAvd, o as teardownOwnedIosSim } from "./teardown-CL6vDs2Y.mjs";
10
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
11
- import { i as stopTunnel, t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
12
- import { a as logVanishedSupervisor, l as withdrawDevServerStopRequest, s as requestDevServerStop } from "./stop-cause-CidxIqup.mjs";
13
- import { n as verifyCollectorOwnership } from "./ownership-DiA16AsN.mjs";
14
- import { t as stopMacosApp } from "./stop-B0Yxtmiy.mjs";
15
- import { f as resolveEasCliBin } from "./remote-cache-C8-V13XN.mjs";
16
- import { i as endRecordedSession } from "./device-remote-D5WVWqOq.mjs";
17
- import { a as decideStopAction, c as nativeRunWaitNotice, i as clearNativeRunCancel, l as requestNativeRunCancel, n as NATIVE_RUN_WAIT_MS, o as describeNativeRunHolder, s as nativeRunHolder, t as NATIVE_RUN_LOCK } from "./native-run-NaBrRFtQ.mjs";
18
- import { r as stopHostedIos } from "./hosted-ios-CIWelIqf.mjs";
19
- import { hostedIosRecords, parseHostedIosPlacement } from "@stim-cli/core/state";
3
+ import { f as refuseNoProject, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-CDAIFD-8.mjs";
4
+ import { o as findCommandWorkspace } from "./project-D47Rh0ly.mjs";
5
+ import { l as resolveProjectMetro, o as pidExists, r as killMetroTree, u as signalProcessTree } from "./metro-g8NqiJp-.mjs";
6
+ import { a as updateWorkspaceState, c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-DqcCbpE7.mjs";
7
+ import { n as fileLeaseIo, u as releaseWorkspaceLeases } from "./device-lease-DpoS36_u.mjs";
8
+ import { a as clearRemoteSession, c as readMetroTunnel, i as clearManagedMetroTunnel, o as clearWorkspaceLaunches, s as clearWorkspaceSupervisor, u as readRemoteSession } from "./state-BKa8wNN5.mjs";
9
+ import { g as teardownOwnedIosSim, h as teardownOwnedBrowser, i as stopTunnel, m as teardownOwnedAvd, t as resolveSupervisorTarget } from "./ownership-BMLqyBgJ.mjs";
10
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
11
+ import { a as logVanishedSupervisor, l as withdrawDevServerStopRequest, s as requestDevServerStop } from "./stop-cause-rhmGfwvt.mjs";
12
+ import { n as verifyCollectorOwnership } from "./ownership-DyRQru0q.mjs";
13
+ import { t as stopMacosApp } from "./stop-Cr1d8DSE.mjs";
14
+ import { i as stopHostedAndroid } from "./hosted-android-DwLS4Mse.mjs";
15
+ import { f as resolveEasCliBin } from "./remote-cache-Ci4DQYYj.mjs";
16
+ import { i as endRecordedSession } from "./device-remote-hFSkpfQu.mjs";
17
+ import { a as decideStopAction, c as nativeRunWaitNotice, i as clearNativeRunCancel, l as requestNativeRunCancel, n as NATIVE_RUN_WAIT_MS, o as describeNativeRunHolder, s as nativeRunHolder, t as NATIVE_RUN_LOCK } from "./native-run-D1Q56rAD.mjs";
18
+ import { r as stopHostedIos } from "./hosted-ios-aMo7Cljf.mjs";
19
+ import { hostedAndroidDeviceName, hostedAndroidRecords, hostedIosRecords, hostedNativeRecords, isJsonObject, parseHostedNativePlacement } from "@stim-cli/core/state";
20
20
  import chalk from "chalk";
21
21
  //#region src/commands/stop.ts
22
22
  const DEFAULT_WAIT_MS = 1e4;
@@ -218,23 +218,23 @@ async function stopBrowserOnly({ root, project = void 0, teardownBrowser = (proj
218
218
  summary: summarize(root, outcomes, ok)
219
219
  };
220
220
  }
221
- async function stopWorkspaceHostedIos(root, slot, report, failed) {
221
+ async function stopWorkspaceHostedNative(root, slot, report, failed) {
222
222
  const devices = {};
223
223
  let anyFailed = false;
224
- for (const [name, record] of Object.entries(hostedIosRecords(readWorkspaceState$1(root)))) {
224
+ for (const platform of ["ios", "android"]) for (const [name, record] of Object.entries(hostedNativeRecords(readWorkspaceState$1(root), platform))) {
225
225
  if (slot !== void 0 && name !== slot) continue;
226
- const placement = parseHostedIosPlacement(record);
227
- const label = placement ? `${placement.device?.name ?? "iOS simulator"} on ${placement.machine}` : `hosted iOS simulator for slot ${name}`;
226
+ const placement = parseHostedNativePlacement(record, platform);
227
+ const label = placement ? `${placement.device ? "udid" in placement.device ? placement.device.name : hostedAndroidDeviceName(placement.device) : `${platform} device`} on ${placement.machine}` : `hosted ${platform === "ios" ? "iOS simulator" : "Android emulator"} for slot ${name}`;
228
228
  try {
229
- await stopHostedIos(root, name);
230
- devices[`ios:host:${name}`] = {
229
+ await (platform === "ios" ? stopHostedIos : stopHostedAndroid)(root, name);
230
+ devices[`${platform}:host:${name}`] = {
231
231
  status: "shut-down",
232
232
  label
233
233
  };
234
234
  report(chalk.dim(phaseLine("device", `stopped ${label}`)));
235
235
  } catch (error) {
236
236
  const reason = error.message;
237
- devices[`ios:host:${name}`] = {
237
+ devices[`${platform}:host:${name}`] = {
238
238
  status: "failed",
239
239
  label,
240
240
  reason
@@ -424,10 +424,25 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
424
424
  ok = false;
425
425
  report(reason);
426
426
  }, stopMacos);
427
- const hosted = await stopWorkspaceHostedIos(root, hostedSlot, report, () => {
427
+ const hosted = await stopWorkspaceHostedNative(root, hostedSlot, report, () => {
428
428
  ok = false;
429
429
  });
430
430
  Object.assign(outcomes.device, hosted.devices);
431
+ updateWorkspaceState(root, (workspace) => {
432
+ const slots = isJsonObject(workspace.deviceSlots) ? workspace.deviceSlots : {};
433
+ for (const name of ["default", ...Object.keys(slots)]) {
434
+ if (hostedSlot !== void 0 && name !== hostedSlot) continue;
435
+ const records = name === "default" ? workspace : slots[name];
436
+ if (!isJsonObject(records)) continue;
437
+ for (const platform of ["ios", "android"]) {
438
+ const hostOutcome = outcomes.device[`${platform}:host:${name}`];
439
+ if (hostOutcome?.status === "failed" || stillHolding && hostOutcome?.status !== "shut-down") continue;
440
+ if (outcomes.device[name === "default" || hostedSlot !== void 0 ? platform : `${platform}:${name}`]?.status === "failed") continue;
441
+ if (isJsonObject(records[platform])) delete records[platform].devicePlacement;
442
+ }
443
+ }
444
+ return workspace;
445
+ });
431
446
  const remote = remoteDevice === void 0 ? readRemoteSession(root) : remoteDevice;
432
447
  const sessionId = typeof remote?.sessionId === "string" ? remote.sessionId : null;
433
448
  if (sessionId) {
@@ -875,12 +890,18 @@ function recordedDeviceSlots(root) {
875
890
  const parsed = parseDeviceSlotKey(key);
876
891
  if (parsed) slots.add(parsed.slot);
877
892
  }
878
- for (const slot of Object.keys(hostedIosRecords(readWorkspaceState$1(root)))) slots.add(slot);
893
+ for (const slot of Object.keys({
894
+ ...hostedIosRecords(readWorkspaceState$1(root)),
895
+ ...hostedAndroidRecords(readWorkspaceState$1(root))
896
+ })) slots.add(slot);
879
897
  return [...slots].toSorted();
880
898
  }
881
899
  function workspaceDeviceSlots(root) {
882
900
  const slots = projectDeviceSlots(getProject(root)).filter(({ platforms }) => Object.values(platforms).some(Boolean)).map(({ slot }) => slot);
883
- for (const slot of Object.keys(hostedIosRecords(readWorkspaceState$1(root)))) if (!slots.includes(slot)) slots.push(slot);
901
+ for (const slot of Object.keys({
902
+ ...hostedIosRecords(readWorkspaceState$1(root)),
903
+ ...hostedAndroidRecords(readWorkspaceState$1(root))
904
+ })) if (!slots.includes(slot)) slots.push(slot);
884
905
  if (typeof readRemoteSession(root)?.sessionId === "string" && !slots.includes("default")) slots.push("default");
885
906
  return slots;
886
907
  }
@@ -1,8 +1,8 @@
1
1
  import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
2
- import { c as writeWorkspaceState, v as workspaceDir } from "./workspace-state-B8-aJ1_5.mjs";
3
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
4
- import { a as requiredMacosRecord, i as macosRuntimeClaim } from "./state-3DwSQIBK.mjs";
5
- import { n as stopHostedMacos } from "./hosted-macos-BSZyIb3y.mjs";
2
+ import { c as writeWorkspaceState, v as workspaceDir } from "./workspace-state-DqcCbpE7.mjs";
3
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
4
+ import { a as requiredMacosRecord, i as macosRuntimeClaim } from "./state-DyaNTgfR.mjs";
5
+ import { n as stopHostedMacos } from "./hosted-macos-dlHXEeF5.mjs";
6
6
  import { readMacosRecord } from "@stim-cli/core/state";
7
7
  //#region src/macos/stop.ts
8
8
  async function stopProcess(record) {
@@ -1,5 +1,5 @@
1
1
  import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
2
- import { c as writeWorkspaceState, t as clearWorkspaceStateKey, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
2
+ import { c as writeWorkspaceState, t as clearWorkspaceStateKey, x as workspaceLogsDir$1 } from "./workspace-state-DqcCbpE7.mjs";
3
3
  import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
4
4
  import { join } from "node:path";
5
5
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
@@ -1,6 +1,6 @@
1
1
  import { t as NdjsonWriter } from "./ndjson-Du3j0AQ9.mjs";
2
2
  import { a as readPidFile, i as clearWorkspaceSupervisor, n as MODE_EXPO, o as writePidFile, r as clearExpoMetroTunnel, t as MODE_BARE } from "./state-B4PQFXMW.mjs";
3
- import { DevServerStopRecord, DeviceActivity, IOS_SIMULATOR_APPS } from "@stim-cli/core/state";
3
+ import { DevServerStopRecord, DeviceActivity, DeviceIdleShutdownRecord, IOS_SIMULATOR_APPS } from "@stim-cli/core/state";
4
4
  //#region src/supervisor/idle-stop.d.ts
5
5
  interface IdleProbe {
6
6
  lastActivityAt(): number;
@@ -22,8 +22,8 @@ interface DueDevice {
22
22
  device: OwnedDeviceActivity;
23
23
  idleForMs: number;
24
24
  }
25
- /** The workspace's booted owned simulators and emulators that have been idle for `idleMs` or more. */
26
- declare function dueIdleDevices(root: string, idleMs: number, now?: number): DueDevice[];
25
+ /** Booted owned devices idle for `idleMs`, in one workspace or all registered workspaces when root is absent. */
26
+ declare function dueIdleDevices(root: string | undefined, idleMs: number, now?: number): DueDevice[];
27
27
  interface IdleShutdownEvent {
28
28
  level: "info" | "warn";
29
29
  event: "device_idle_shutdown" | "device_idle_shutdown_failed";
@@ -34,7 +34,10 @@ interface IdleShutdownEvent {
34
34
  * and records why. The caller holds the workspace's native-run lock, so no `ios` or `android` run is booting or
35
35
  * installing on them.
36
36
  */
37
- declare function shutDownIdleDevices(root: string, idleMs: number, log: (event: IdleShutdownEvent) => void, now?: number): number;
37
+ declare function shutDownIdleDevices(root: string, idleMs: number, log: (event: IdleShutdownEvent) => void, now?: number, { only, reason }?: {
38
+ only?: OwnedDeviceActivity;
39
+ reason?: DeviceIdleShutdownRecord["reason"];
40
+ }): number;
38
41
  //#endregion
39
42
  //#region src/watchman.d.ts
40
43
  type WatchmanCommand = (args: string[], timeoutMs: number) => Promise<unknown>;
@@ -1,25 +1,26 @@
1
1
  import { B as parseDeviceSlotKey, H as projectDeviceSlots, T as setSupervisor, o as clearSupervisor, p as getProject } from "./config-D0DbpZtM.mjs";
2
2
  import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
3
3
  import { t as relaunchWithLogFile } from "./detached-entry-CiTEgMR1.mjs";
4
- import { r as detectIsExpo } from "./project-Du91Ilvc.mjs";
5
- import { C as workspaceStateFile, a as updateWorkspaceState, c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
6
- import { n as withIdleWorkspace } from "./in-use-DvBw0Kxo.mjs";
7
- import { r as createActivityReader } from "./activity-BxwdD81S.mjs";
8
- import { a as parseLease, r as leaseIsExpired, s as parseWorkspaceLeases, t as deviceLeasePath } from "./device-lease-D-hLqx1b.mjs";
9
- import { l as readPidFile, n as MODE_EXPO, p as writePidFile, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-DcW6vOd_.mjs";
10
- import { C as parseSubscriberNames, O as runWatchman, T as parseWatchRoots, s as workspaceBuildInProgress, w as parseTriggerCount } from "./idle-DYVymG92.mjs";
11
- import { z as ownedAvdSerialResolver } from "./android-B_NumVyQ.mjs";
12
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
4
+ import { r as detectIsExpo } from "./project-D47Rh0ly.mjs";
5
+ import { C as workspaceStateFile, a as updateWorkspaceState, c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-DqcCbpE7.mjs";
6
+ import { n as withIdleWorkspace } from "./in-use-CvGOUn-x.mjs";
7
+ import { m as workspaceDeviceLastUseAt, r as createActivityReader } from "./activity-IRvJY_ru.mjs";
8
+ import { a as parseLease, r as leaseIsExpired, s as parseWorkspaceLeases, t as deviceLeasePath } from "./device-lease-DpoS36_u.mjs";
9
+ import { l as readPidFile, n as MODE_EXPO, p as writePidFile, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-BKa8wNN5.mjs";
10
+ import { n as parseTriggerCount, o as runWatchman, r as parseWatchRoots, t as parseSubscriberNames } from "./watchman-APYjXSQa.mjs";
11
+ import { z as ownedAvdSerialResolver } from "./android-B_YNZy61.mjs";
12
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
13
+ import { s as workspaceBuildInProgress } from "./idle-B2m-Ovod.mjs";
13
14
  import { t as describeError } from "./errors-B6W7FkxQ.mjs";
14
15
  import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
15
- import { c as vanishedSupervisorMessage, i as devServerStopRecord, r as devServerStopLevel, t as describeDevServerStop } from "./stop-cause-CidxIqup.mjs";
16
- import { l as probeHostedSession } from "./hosted-client-a-WmqE2L.mjs";
17
- import { t as clearHostedMetro } from "./metro-gateway-BJ-rxQAB.mjs";
18
- import { n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-CTsDnAde.mjs";
16
+ import { c as vanishedSupervisorMessage, i as devServerStopRecord, r as devServerStopLevel, t as describeDevServerStop } from "./stop-cause-rhmGfwvt.mjs";
17
+ import { l as probeHostedSession } from "./hosted-client-wk1W_CNW.mjs";
18
+ import { t as clearHostedMetro } from "./metro-gateway-DmslUM30.mjs";
19
+ import { n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-_3oOV1V_.mjs";
19
20
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
20
21
  import { dirname, isAbsolute, join, resolve, sep } from "node:path";
21
22
  import { LOG_ROTATE_BYTES, createMetroGateway } from "@stim-cli/core";
22
- import { DEV_SERVER_STOP_REQUEST_KEY, HOSTED_METRO_GATEWAYS_KEY, HOSTED_METRO_REQUESTS_KEY, IDLE_STOP_KEY, hostedIosPlacements, hostedMetroGateways, hostedMetroRequests, readDevServerStopRequest } from "@stim-cli/core/state";
23
+ import { DEV_SERVER_STOP_REQUEST_KEY, HOSTED_METRO_GATEWAYS_KEY, HOSTED_METRO_REQUESTS_KEY, IDLE_STOP_KEY, hostedAndroidPlacements, hostedIosPlacements, hostedMetroGateways, hostedMetroRequests, hostedNativeRecords, parseHostedNativePlacement, readDevServerStopRequest } from "@stim-cli/core/state";
23
24
  import { fileURLToPath } from "node:url";
24
25
  //#region src/supervisor/hosted-metro.ts
25
26
  function reconcileHostedMetro(requests, active) {
@@ -68,12 +69,12 @@ function watchHostedMetro(root, metroPort, processToken) {
68
69
  if (closed) return;
69
70
  const state = readState();
70
71
  if (state?.supervisor?.processToken !== processToken) return;
71
- const placements = Object.values(hostedIosPlacements(state));
72
+ const placements = [...Object.values(hostedIosPlacements(state)), ...Object.values(hostedAndroidPlacements(state)).map((placement) => Object.assign({ platform: "android" }, placement))];
72
73
  const sessions = new Set(placements.map((placement) => placement.session));
73
74
  const requests = Object.fromEntries(Object.entries(hostedMetroRequests(state)).filter(([session]) => sessions.has(session)));
74
75
  if (Object.keys(requests).length !== Object.keys(hostedMetroRequests(state)).length) updateWorkspaceState(root, (current) => {
75
76
  if (current.supervisor?.processToken !== processToken) return current;
76
- const recorded = new Set(Object.values(hostedIosPlacements(current)).map((placement) => placement.session));
77
+ const recorded = new Set([...Object.values(hostedIosPlacements(current)), ...Object.values(hostedAndroidPlacements(current))].map((placement) => placement.session));
77
78
  return {
78
79
  ...current,
79
80
  [HOSTED_METRO_REQUESTS_KEY]: Object.fromEntries(Object.entries(hostedMetroRequests(current)).filter(([session]) => recorded.has(session)))
@@ -215,8 +216,13 @@ function workspaceIdleProbe(root, { platform = process.platform } = {}) {
215
216
  },
216
217
  blocker() {
217
218
  if (workspaceBuildInProgress(root)) return "a build is in progress";
218
- const hosted = Object.values(hostedIosPlacements(readWorkspaceState$1(root)))[0];
219
- if (hosted) return `its iOS simulator runs on ${hosted.machine}`;
219
+ for (const native of ["ios", "android"]) {
220
+ const records = Object.values(hostedNativeRecords(readWorkspaceState$1(root), native));
221
+ if (records.length) {
222
+ const placement = parseHostedNativePlacement(records[0], native);
223
+ return placement ? `its ${native === "ios" ? "iOS simulator" : "Android emulator"} runs on ${placement.machine}` : `its hosted ${native} placement is unreadable`;
224
+ }
225
+ }
220
226
  const lease = heldDeviceLease(root, Date.now());
221
227
  if (lease) return lease;
222
228
  const readActivity = createActivityReader();
@@ -592,7 +598,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
592
598
  return writer.write(entry);
593
599
  } });
594
600
  try {
595
- server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-CVqO2c35.mjs")).startExpoServer : startBare || (await import("./server-bare-CpPGmnmO.mjs")).startBareServer)({
601
+ server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-CeakhI8P.mjs")).startExpoServer : startBare || (await import("./server-bare-BkrSxSsZ.mjs")).startBareServer)({
596
602
  root,
597
603
  port,
598
604
  logsDir,
@@ -650,7 +656,6 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
650
656
  });
651
657
  closeHostedMetro = watchHostedMetro(root, port, processToken);
652
658
  const deviceIdleMs = deviceIdleMinutes * 6e4;
653
- const workspaceProbe = workspaceIdleProbe(root);
654
659
  const shutDownIdleDevices = (idleMs) => deviceIdle.shutDown(root, idleMs, (entry) => writer.write({
655
660
  src: "metro",
656
661
  ...entry
@@ -658,14 +663,7 @@ async function runSupervisor({ root, port, tunnel = false, resetCache = false, i
658
663
  if (deviceIdleMinutes > 0) stopWatchingDevices = watchIdleDevices({
659
664
  idleMs: deviceIdleMs,
660
665
  now,
661
- lastUseAt: () => {
662
- let deviceLog = NaN;
663
- try {
664
- deviceLog = statSync(join(logsDir, "device.ndjson")).mtimeMs;
665
- } catch {}
666
- const times = [workspaceProbe.lastActivityAt(), deviceLog].filter(Number.isFinite);
667
- return times.length ? Math.max(...times) : NaN;
668
- },
666
+ lastUseAt: () => workspaceDeviceLastUseAt(root),
669
667
  hasDue: () => deviceIdle.due(root, deviceIdleMs).length > 0,
670
668
  onDue: async () => {
671
669
  try {