stim 1.27.0 → 1.29.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 (154) hide show
  1. package/dist/{acquire-artifact-BKN2Yuhg.mjs → acquire-artifact-C2DKfenN.mjs} +1 -1
  2. package/dist/{act-ROviTF_t.mjs → act-CrTcskiE.mjs} +502 -34
  3. package/dist/{activity-Cd-ysFes.mjs → activity-BR5oHSO5.mjs} +6 -5
  4. package/dist/{agent-device-usage-output-C5nCKWp9.mjs → agent-device-usage-output-W0x4SoC7.mjs} +4 -4
  5. package/dist/{android-DriNoAVy.mjs → android-BHKgeOuq.mjs} +5 -5
  6. package/dist/{android--HBgpvpW.mjs → android-BPmCmyAy.mjs} +5 -5
  7. package/dist/{android-Deb9X3_Y.mjs → android-CLU_MMnb.mjs} +1 -1
  8. package/dist/{android-cas-D5e6ecUg.mjs → android-cas-BH6K12__.mjs} +4 -4
  9. package/dist/{android-Cd6w7_qw.mjs → android-l616dRP1.mjs} +42 -39
  10. package/dist/{android-project-DUGMbq28.d.mts → android-project-CCEQyYJK.d.mts} +6 -2
  11. package/dist/api.d.mts +8 -5
  12. package/dist/api.mjs +27 -27
  13. package/dist/{apk-signed-tail-BHQHx3Rm.mjs → apk-signed-tail-B-wKoF_8.mjs} +2 -2
  14. package/dist/{artifact-0AGJYB9O.mjs → artifact-DHuFYfhD.mjs} +12 -12
  15. package/dist/{artifact-faemIZv5.mjs → artifact-Dm9ScLrh.mjs} +12 -12
  16. package/dist/{auto-placement-z-H5pJza.mjs → auto-placement-C8sJsyMN.mjs} +8 -8
  17. package/dist/{browser-web-slD8AvLp.mjs → browser-web-_tQTbQUs.mjs} +12 -12
  18. package/dist/{build-hS0QyFlQ.mjs → build-DtCU9Kim.mjs} +23 -23
  19. package/dist/{build-cache-gBJ-WKzA.mjs → build-cache-BGwTsIEa.mjs} +4 -4
  20. package/dist/{build-plan-CBqYbzYM.mjs → build-plan-CWyjMPKo.mjs} +6 -6
  21. package/dist/{cache-manifest-DEjqGGat.mjs → cache-manifest-evF_zJ9U.mjs} +1 -1
  22. package/dist/cache-manifest.mjs +1 -1
  23. package/dist/{ccache-BWTze1Ls.mjs → ccache-DsDkdP72.mjs} +19 -18
  24. package/dist/{cli-ftXDLto1.mjs → cli-3fjOg-Oc.mjs} +19 -19
  25. package/dist/cli.mjs +1 -1
  26. package/dist/{client-xsdxrhWA.mjs → client-BTUH8TF1.mjs} +41 -8
  27. package/dist/collector-run.mjs +5 -5
  28. package/dist/{command-output-Dj-WT4BP.mjs → command-output-DjCsmzLZ.mjs} +1 -1
  29. package/dist/{config-Cn94Jc0g.mjs → config-AbUjIBOQ.mjs} +121 -19
  30. package/dist/{dependencies-BGokZ-Ng.mjs → dependencies-CHNqX6Pr.mjs} +38 -38
  31. package/dist/{dependency-state-C3OtgG94.mjs → dependency-state-CPKAKCZ8.mjs} +1 -1
  32. package/dist/{dev-client-BMca82xo.mjs → dev-client-CROrxj1x.mjs} +2 -2
  33. package/dist/{device-tM41-TUq.mjs → device-Bmm5qjM-.mjs} +14 -14
  34. package/dist/device-BvpasThQ.mjs +297 -0
  35. package/dist/{device-capacity-6LP7QsaA.mjs → device-capacity-B2ChA-bz.mjs} +86 -18
  36. package/dist/device-host-worker.mjs +15 -13
  37. package/dist/{device-ios-534c14fz.mjs → device-ios-CYzivd31.mjs} +9 -9
  38. package/dist/{device-lease-run-DwsGfjUN.mjs → device-lease-run-qRq_2W2V.mjs} +4 -4
  39. package/dist/{device-pool-BnQqvs6o.mjs → device-pool-CrmiE4_x.mjs} +3 -3
  40. package/dist/{device-BJm0irrq.mjs → device-provision-CK2XiEp1.mjs} +11 -302
  41. package/dist/{doctor-Dbx1ZpD2.mjs → doctor-BO_kaFWX.mjs} +1 -1
  42. package/dist/{doctor-DyqE9n1J.mjs → doctor-Cp4-HN1f.mjs} +20 -15
  43. package/dist/{doctor-F-3VUUyN.mjs → doctor-Dr3C1iXV.mjs} +19 -19
  44. package/dist/doctor-fingerprint.mjs +1 -1
  45. package/dist/{doctor-watchman-CxDbyrrt.mjs → doctor-watchman-CJr2aXXv.mjs} +4 -4
  46. package/dist/{eas-build-CjNSWmjo.mjs → eas-build-D-9HmGZS.mjs} +2 -2
  47. package/dist/{eas-fallback-9fqb46kW.mjs → eas-fallback-Cemt2GVL.mjs} +4 -4
  48. package/dist/{eas-session-ledger-DrP4OuuM.mjs → eas-session-ledger-B0KLZx5m.mjs} +36 -7
  49. package/dist/{error-diagnostics-snej9k4j.mjs → error-diagnostics-CV_1RoEH.mjs} +8 -8
  50. package/dist/{gc-CGZ1Yg88.mjs → gc-CKHqL4aS.mjs} +81 -18
  51. package/dist/{gc-BRwAY3qT.mjs → gc-DjXwQXHK.mjs} +2 -2
  52. package/dist/{guide-_rfEh_zM.mjs → guide-CeRlWVsS.mjs} +230 -62
  53. package/dist/{hosted-android-BMKzUNnY.mjs → hosted-android-DI_vvv7U.mjs} +5 -6
  54. package/dist/{hosted-client-D7Pk_Oo1.mjs → hosted-client-CgoxdU9j.mjs} +44 -17
  55. package/dist/{hosted-logs-B1gqQuva.mjs → hosted-logs-C-4INmkn.mjs} +17 -8
  56. package/dist/{hosted-logs-sync-Dg3qrZmV.mjs → hosted-logs-sync-Bf5s02fi.mjs} +5 -5
  57. package/dist/{hosted-macos-B9w4H8hc.mjs → hosted-macos-Doh85YUM.mjs} +5 -5
  58. package/dist/{hosted-native-CdgaiuyP.mjs → hosted-native-DvZENAL-.mjs} +11 -8
  59. package/dist/{idle-shutdown-Cbvo5KEh.mjs → idle-shutdown-BM37gMfU.mjs} +7 -7
  60. package/dist/{ios-Bk4mWbtS.mjs → ios-CNDH6fNF.mjs} +8 -8
  61. package/dist/{ios-E26qSpkb.mjs → ios-CoMCWfub.mjs} +4 -4
  62. package/dist/{ios-B5HlVMhQ.mjs → ios-UM_dManb.mjs} +31 -29
  63. package/dist/{ios-device-C_QkIf1Z.mjs → ios-device-L1-pota_.mjs} +1 -1
  64. package/dist/{ios-project-D8Jcy_br.mjs → ios-project-DJYptOCh.mjs} +7 -7
  65. package/dist/{ios-state-BZ03NFgF.mjs → ios-state-Ce_dystM.mjs} +1 -1
  66. package/dist/{js-swap-Dg2a945i.mjs → js-swap-BthE4kZC.mjs} +4 -4
  67. package/dist/{launch-CRyBeNsA.mjs → launch-D798aSm8.mjs} +19 -19
  68. package/dist/{lightened-BCsdAGj_.mjs → lightened-vSxkstg6.mjs} +4 -4
  69. package/dist/{logs-aJQb-RDg.mjs → logs-BND8MiBq.mjs} +9 -8
  70. package/dist/{logs-CC_-ZzEE.mjs → logs-CilPftm3.mjs} +5 -5
  71. package/dist/{macos-BGmTpnv9.mjs → macos-BOQrKYeJ.mjs} +1 -1
  72. package/dist/{macos-DlCBhMw5.mjs → macos-CxUi9xcE.mjs} +20 -20
  73. package/dist/macos-run.mjs +1 -1
  74. package/dist/maintenance-run.mjs +13 -9
  75. package/dist/{metro-CvyD-xwe.mjs → metro-BMQHczcf.mjs} +2 -2
  76. package/dist/{metro-gateway-BnHdqMO_.mjs → metro-request-BtF7ox53.mjs} +25 -46
  77. package/dist/{named-ports-BCyMWA6b.mjs → named-ports-DcktE2Pw.mjs} +2 -2
  78. package/dist/{native-android-DlpxCyOP.mjs → native-android-BkhrCjdX.mjs} +16 -16
  79. package/dist/{native-gradle-artifact-inputs-C13qT7R_.mjs → native-gradle-artifact-inputs-Cw4P6Gyr.mjs} +3 -3
  80. package/dist/{native-runtime-DqaIBO5w.mjs → native-runtime-DyH2gkXK.mjs} +6 -6
  81. package/dist/{native-xcode-inputs-B1W8SrxB.mjs → native-xcode-inputs-DV8jr3kJ.mjs} +2 -2
  82. package/dist/{native-xcode-ios-QPP0Rm68.mjs → native-xcode-ios-DdpPG2iT.mjs} +15 -15
  83. package/dist/{ndjson-Dy2W4vRa.mjs → ndjson-CrjIsGzw.mjs} +2 -2
  84. package/dist/offload-worker.d.mts +1 -1
  85. package/dist/offload-worker.mjs +22 -22
  86. package/dist/{ownership-Bx-0cku-.mjs → ownership-Dtz9G5Gl.mjs} +2 -2
  87. package/dist/{phone-reverses-C9yAaKEv.mjs → phone-reverses-DZRad8HT.mjs} +4 -4
  88. package/dist/{placement-log-BXUbiZM6.mjs → placement-log-CoN6xrjY.mjs} +1 -1
  89. package/dist/{plan-placement-UxnQjv5Z.mjs → plan-placement-7sHuqR-r.mjs} +6 -6
  90. package/dist/{ports-uUrghqWN.mjs → ports-VQSAPZhl.mjs} +3 -3
  91. package/dist/{prebuild-BDpToDgR.mjs → prebuild-Bqp0LFGN.mjs} +9 -9
  92. package/dist/{project-DYj-HVx6.mjs → project-D1n0K4yg.mjs} +15 -15
  93. package/dist/{react-native-android-DDBccgJw.mjs → react-native-android-FQ-yX3Mv.mjs} +22 -22
  94. package/dist/{react-native-build-DaK2IFyP.mjs → react-native-build-D58bIqV9.mjs} +7 -7
  95. package/dist/{react-native-doctor-B-Mw6DcR.mjs → react-native-doctor-AkUH2aca.mjs} +5 -5
  96. package/dist/{react-native-ios-DjYwUF9U.mjs → react-native-ios-Dmsv8j7T.mjs} +14 -14
  97. package/dist/{recordings-C20iUZfp.mjs → recordings-CHMaF73C.mjs} +1 -1
  98. package/dist/{reload-fUdkH2_p.mjs → reload-BV0nD-k9.mjs} +14 -14
  99. package/dist/{reload-DJBeF_rC.mjs → reload-CDKEzzaf.mjs} +1 -1
  100. package/dist/{remote-y6n99WMW.mjs → remote-C8xyslUa.mjs} +9 -9
  101. package/dist/{result-CTqBw2Te.mjs → result-9fqcJ90W.mjs} +16 -16
  102. package/dist/{run-DaCeNKRm.mjs → run-BQAmrohh.mjs} +2 -2
  103. package/dist/{selection-Bs8phwhN.mjs → selection-D1ZDzHFX.mjs} +105 -62
  104. package/dist/{server-bare-BOh-iZEj.mjs → server-bare-CEQdB_F4.mjs} +37 -1
  105. package/dist/{server-command-8CuA51Cv.mjs → server-command-RlS-Kl4a.mjs} +5 -5
  106. package/dist/server-expo-BVV9qy31.mjs +2 -0
  107. package/dist/{server-expo-ABQs2XFA.mjs → server-expo-bB1VV2k-.mjs} +8 -5
  108. package/dist/{settings-CMHhQNwI.mjs → settings-BcVCcAT5.mjs} +2 -2
  109. package/dist/{settings-BDVI8Ivc.mjs → settings-D776ygML.mjs} +47 -3
  110. package/dist/{settings-Djl-mpjX.mjs → settings-D9LtQaUw.mjs} +2 -1
  111. package/dist/{settings-payload-cm60Zhib.mjs → settings-payload-_aatELz2.mjs} +4 -4
  112. package/dist/{settings-write-BdNRLlBn.mjs → settings-write-CkTVGQ4O.mjs} +5 -5
  113. package/dist/settings.schema.json +58 -9
  114. package/dist/{simslim-DCQwzoPy.mjs → simslim-CbpHERcv.mjs} +1 -1
  115. package/dist/{slot-launch-0l7RqSpr.mjs → slot-launch-Dbgfh8gc.mjs} +6 -6
  116. package/dist/{stage-F4cAfJGi.mjs → stage-BuHuRj1y.mjs} +1 -1
  117. package/dist/{start-DcqnF9p8.mjs → start-Cr-c3Xhq.mjs} +1 -1
  118. package/dist/{start-DrPwER_2.mjs → start-Dlv4XXTP.mjs} +11 -11
  119. package/dist/{state-CZxLyetD.mjs → state-C2AUjjhg.mjs} +1 -1
  120. package/dist/{state-BX1y5syV.mjs → state-D5ZxwCGJ.mjs} +2 -2
  121. package/dist/{stats-GxuCbles.mjs → stats-D5c-uIBp.mjs} +4 -4
  122. package/dist/{status-7ULDEJ1e.mjs → status-CK7ZoEhV.mjs} +2 -2
  123. package/dist/{status-BfnK9pUd.mjs → status-D6PBK6_-.mjs} +49 -26
  124. package/dist/{status-Bj7ZYvvc.mjs → status-DbQv--Yb.mjs} +1 -1
  125. package/dist/{status-watch-BfabQn03.mjs → status-watch-okrpea4i.mjs} +17 -6
  126. package/dist/status-worker.mjs +1 -1
  127. package/dist/{stim-desktop-CbWo21kb.mjs → stim-desktop-CYXLFpFS.mjs} +1 -1
  128. package/dist/{stop-x1Yu3He4.mjs → stop-CHtAiv79.mjs} +2 -2
  129. package/dist/{stop-D-EAr25B.mjs → stop-p9uSKuGE.mjs} +20 -19
  130. package/dist/{stop-CH79S3IZ.mjs → stop-uRQ-zYcl.mjs} +9 -5
  131. package/dist/supervisor-run.d.mts +1 -0
  132. package/dist/supervisor-run.mjs +243 -65
  133. package/dist/{support-Be0UeVZU.mjs → support-BMpQIug0.mjs} +11 -10
  134. package/dist/{support-CNg_YsOk.mjs → support-PFyTkrnr.mjs} +4 -4
  135. package/dist/{swiftpm-artifact-inputs-uPqYvGBv.mjs → swiftpm-artifact-inputs-Cddq6jN2.mjs} +1 -1
  136. package/dist/{swiftpm-macos-Ch4YjY_A.mjs → swiftpm-macos-BSj9iZlI.mjs} +8 -8
  137. package/dist/{toolchain-JYAOquPg.mjs → toolchain-B-mzR9Rh.mjs} +1 -1
  138. package/dist/{toolchain-CbCG6C5Z.mjs → toolchain-CLGnEUgT.mjs} +4 -4
  139. package/dist/{trigger-DMou-X7j.mjs → trigger-CfnOfpDd.mjs} +1 -1
  140. package/dist/trigger-DBZ-GBrl.mjs +2 -0
  141. package/dist/{warm-progress-C0RsbngN.mjs → warm-progress--KkkBBaq.mjs} +3 -3
  142. package/dist/web-BxSo493O.mjs +2 -0
  143. package/dist/web-run.mjs +5 -5
  144. package/dist/{web-Cq_AQ4DK.mjs → web-xlVssjQf.mjs} +8 -8
  145. package/dist/{workspace-state-BjqdjnJR.mjs → workspace-state-DGwkPXwq.mjs} +2 -2
  146. package/dist/{workspaces-DFbj07jp.mjs → workspaces-CvSYD_78.mjs} +106 -26
  147. package/dist/{worktree-COAJ3-OD.mjs → worktree-CaQBf6kh.mjs} +26 -47
  148. package/dist/worktree-DTOIohFe.mjs +2 -0
  149. package/package.json +7 -7
  150. package/shim/bundle-response.cjs +37 -0
  151. package/dist/server-expo-Ba_Y21vB.mjs +0 -2
  152. package/dist/trigger-qp2ji5wE.mjs +0 -2
  153. package/dist/web-Je6abcpz.mjs +0 -2
  154. package/dist/worktree-JGUd_53L.mjs +0 -2
@@ -1,5 +1,5 @@
1
1
  import { l as WorkspaceProcessCleanupError } from "./exec-aULwtNly.mjs";
2
- import { I as waitForSharedBuild } from "./activity-Cd-ysFes.mjs";
2
+ import { I as waitForSharedBuild } from "./activity-BR5oHSO5.mjs";
3
3
  import { rmSync } from "node:fs";
4
4
  //#region src/engine/artifact-lifecycle.ts
5
5
  async function runArtifactLifecycle(phases) {
@@ -1,30 +1,33 @@
1
- import { l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-aULwtNly.mjs";
2
- import { j as workspaceStateFile$1, k as workspaceLogsDir$1, r as readWorkspaceState$1, u as workspaceLastUsed } from "./workspace-state-BjqdjnJR.mjs";
3
- import { F as isOnMountedVolume, G as projectDeviceSlots, I as listMountedVolumes, L as measuredDirectorySizeAsync, N as directorySizeAsync, P as formatBytes, _ as loadConfig$1, p as getProject } from "./config-Cn94Jc0g.mjs";
4
- import { o as formatLongDuration, u as plural } from "./command-output-Dj-WT4BP.mjs";
1
+ import { S as process_identity_exports, d as workspaceProcessLockError, l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor, u as withWorkspaceProcessLock } from "./exec-aULwtNly.mjs";
2
+ import { E as workspaceDir, j as workspaceStateFile$1, k as workspaceLogsDir$1, r as readWorkspaceState$1, u as workspaceLastUsed, w as workspaceAgentDeviceDir } from "./workspace-state-DGwkPXwq.mjs";
3
+ import { F as isOnMountedVolume, G as projectDeviceSlots, I as listMountedVolumes, L as measuredDirectorySizeAsync, N as directorySizeAsync, P as formatBytes, _ as loadConfig$1, p as getProject } from "./config-AbUjIBOQ.mjs";
4
+ import { o as formatLongDuration, u as plural } from "./command-output-DjCsmzLZ.mjs";
5
5
  import { c as gitQuiet, l as gitQuietAsync, n as runOperationFileQuiet, r as childEnvironment } from "./operation-exec-Dp4FuIGR.mjs";
6
- import { At as listWorktreesAsync, Bt as resolveSourceCheckoutAsync, Ct as hasPopulatedSubmodulesAsync, Ht as sourceCheckoutOf, O as maintenanceKeepSetting, Tt as hasUncommittedWorkAsync, Ut as unpushedCommitsAsync, X as resolveProjectSettings, Z as resolveProjectSettingsAsync, h as deviceIdleShutdownMinutesSetting, kt as listWorktrees, tn as removeTemporaryEntryAsync, yt as dirtyPathsAsync, zt as resolveFullRefAsync } from "./settings-BDVI8Ivc.mjs";
7
- import { i as findProjectRoot$1 } from "./project-DYj-HVx6.mjs";
8
- import { F as listAvdsAsync, G as ownedAvdSerialResolverAsync, P as listAvds, V as ownedAvdDirectory, yt as readParked } from "./android-DriNoAVy.mjs";
9
- import { A as diskPressureLevel, G as planCacheEmptying, N as lowestFree, P as protectWindowMs, X as collectGradleVersions, Z as deleteGradleVersion, Zn as collectMemoryReport, _ as resolveBudget, at as DEVICE_LIST_TIMEOUT_MS, ct as collectParkedSims, d as enforceBudget, dr as pruneCache, dt as deleteProjectDevices, j as levelFloors, lt as deleteParkedAvds, mt as deviceSweepIsScoped, nr as memorySweepIsScoped, nt as waitForGradleExit, or as stopIdleGradleDaemons, p as machineNumber, sr as phase, st as collectParkedAvds, u as cacheLabel, ur as discoverCaches, ut as deleteParkedSims, v as trimmableCaches, vt as findStaleProjectDevices, y as stopOwnedMetro, z as emptyCaches } from "./ccache-BWTze1Ls.mjs";
10
- import { a as unregister, n as readManifest } from "./cache-manifest-DEjqGGat.mjs";
11
- import { Dt as readConcurrency, L as workspaceBuildInProgress, M as collectOwnedDeviceActivityAsync, St as GC_OUTPUT, Tt as eachLimited, _ as withIdleWorkspace, a as collectOrphanedWorkspaces, c as isInsideWorkspaces, h as ARTIFACT_DIRS, i as clearWorkspaceOutputs, j as collectOwnedDeviceActivity, l as listWorkspaceDirs, m as workspaceDirRemoved, o as collectWorkspaceOutputs, p as workspaceBuildReasons, s as deleteOrphanedWorkspaces, t as LIGHTEN_AFTER_MS, u as recentNativeUseReason, v as workspaceInUse, wt as takeGcResults, y as workspaceInUseAsync } from "./workspaces-DFbj07jp.mjs";
6
+ import { At as listWorktrees, Et as hasUncommittedWorkAsync, Ft as matchWorktreeEntry, Kt as sourceCheckoutOf, O as maintenanceKeepSetting, Pt as lockReason, Ut as resolveFullRefAsync, Wt as resolveSourceCheckoutAsync, X as resolveProjectSettings, Xt as worktreeLockAsync, Z as resolveProjectSettingsAsync, bt as dirtyPathsAsync, h as deviceIdleShutdownMinutesSetting, it as settingValueAt, jt as listWorktreesAsync, mt as LOCKED_REASON, qt as unpushedCommitsAsync, sn as removeTemporaryEntryAsync, wt as hasPopulatedSubmodulesAsync } from "./settings-D776ygML.mjs";
7
+ import { P as readRemoteSession, i as findProjectRoot$1 } from "./project-D1n0K4yg.mjs";
8
+ import { A as listAdbDevices, D as getAvdNameForSerial, F as listAvdsAsync, G as ownedAvdSerialResolverAsync, O as getAvdNameForSerialAsync, P as listAvds, V as ownedAvdDirectory, j as listAdbDevicesAsync, yt as readParked } from "./android-BHKgeOuq.mjs";
9
+ import { c as pidExists } from "./metro-BMQHczcf.mjs";
10
+ import { A as diskPressureLevel, G as planCacheEmptying, N as lowestFree, P as protectWindowMs, X as collectGradleVersions, Z as deleteGradleVersion, Zn as collectMemoryReport, _ as resolveBudget, at as DEVICE_LIST_TIMEOUT_MS, ct as collectParkedSims, d as enforceBudget, dr as pruneCache, dt as deleteProjectDevices, j as levelFloors, lt as deleteParkedAvds, mt as deviceSweepIsScoped, nr as memorySweepIsScoped, nt as waitForGradleExit, or as stopIdleGradleDaemons, p as machineNumber, sr as phase, st as collectParkedAvds, u as cacheLabel, ur as discoverCaches, ut as deleteParkedSims, v as trimmableCaches, vt as findStaleProjectDevices, y as stopOwnedMetro, z as emptyCaches } from "./ccache-DsDkdP72.mjs";
11
+ import { o as readWebRecord, u as webFacts } from "./state-D5ZxwCGJ.mjs";
12
+ import { a as unregister, n as readManifest } from "./cache-manifest-evF_zJ9U.mjs";
13
+ import { B as workspaceBuildInProgress, Et as GC_OUTPUT, F as collectOwnedDeviceActivityAsync, Ot as takeGcResults, P as collectOwnedDeviceActivity, a as collectOrphanedWorkspaces, b as workspaceInUseAsync, c as isInsideWorkspaces, d as recentNativeUseReason, g as ARTIFACT_DIRS, h as workspaceDirRemoved, i as clearWorkspaceOutputs, jt as readConcurrency, kt as eachLimited, l as keptByLock, m as workspaceBuildReasons, o as collectWorkspaceOutputs, s as deleteOrphanedWorkspaces, t as LIGHTEN_AFTER_MS, u as listWorkspaceDirs, v as withIdleWorkspace, x as resolveSupervisorTarget, y as workspaceInUse } from "./workspaces-CvSYD_78.mjs";
12
14
  import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
13
- import { N as listBuildLocks, _ as workspaceDeviceLastUseAt, b as leaseIsExpired, g as workspaceActivity, x as listLeaseFiles } from "./activity-Cd-ysFes.mjs";
14
- import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-E26qSpkb.mjs";
15
+ import { N as listBuildLocks, _ as workspaceDeviceLastUseAt, b as leaseIsExpired, g as workspaceActivity, x as listLeaseFiles } from "./activity-BR5oHSO5.mjs";
16
+ import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-CoMCWfub.mjs";
15
17
  import { s as metroStoreRoot } from "./errors-DHm_U5gv.mjs";
16
- import { t as resolveMaintenanceSettings } from "./settings-Djl-mpjX.mjs";
17
- import { D as stimCliEntries, E as serviceRunningFrom, N as worktreeArchiveLimits, O as stimCliUseOf, S as measurePressureAsync, T as uncommittedActivityOf, a as matchWorktreeEntry, b as measureDiskAsync, c as reclaimKeys, i as excludeWatchmanCookies, m as removeWorktreeTarget, o as nestedWorktrees, r as excludePodChurn, s as porcelainPath, v as cacheBlocked, x as measurePressure, y as measureDisk } from "./worktree-COAJ3-OD.mjs";
18
+ import { t as resolveMaintenanceSettings } from "./settings-D9LtQaUw.mjs";
19
+ import { o as runStop } from "./stop-p9uSKuGE.mjs";
20
+ import { D as stimCliUseOf, E as stimCliEntries, M as worktreeArchiveLimits, T as serviceRunningFrom, _ as cacheBlocked, a as nestedWorktrees, b as measurePressure, i as excludeWatchmanCookies, o as porcelainPath, p as removeWorktreeTarget, r as excludePodChurn, s as reclaimKeys, v as measureDisk, w as uncommittedActivityOf, x as measurePressureAsync, y as measureDiskAsync } from "./worktree-CaQBf6kh.mjs";
18
21
  import { n as describePullRequest, r as endedPullRequest, s as pullRequestLookups } from "./pull-request-BzLmzAIQ.mjs";
19
- import { a as lastFetchedAtAsync, c as mergeStateAsync, f as recordPullRequestLookup, i as fetchDefaultBranchAsync, o as localDefaultBranchAsync, r as FETCH_FRESH_MS } from "./warm-progress-C0RsbngN.mjs";
20
- import { a as restoreProblem, r as recordLightened, t as finishLightened } from "./lightened-BCsdAGj_.mjs";
21
- import { i as writeIdleShutdownEvent, n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-Cbvo5KEh.mjs";
22
+ import { a as lastFetchedAtAsync, c as mergeStateAsync, f as recordPullRequestLookup, i as fetchDefaultBranchAsync, o as localDefaultBranchAsync, r as FETCH_FRESH_MS } from "./warm-progress--KkkBBaq.mjs";
23
+ import { a as restoreProblem, r as recordLightened, t as finishLightened } from "./lightened-vSxkstg6.mjs";
24
+ import { i as writeIdleShutdownEvent, n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-BM37gMfU.mjs";
22
25
  import { readClaimSet } from "@stim-cli/core/ownership-claim";
23
- import { existsSync, lstatSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync } from "node:fs";
26
+ import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync } from "node:fs";
24
27
  import { tmpdir } from "node:os";
25
28
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
26
- import { buildCacheRoot, configDir, metroCacheRoot } from "@stim-cli/core";
27
- import { LAST_BUILD_KEYS, MAINTENANCE_LOG_NAME, lastNativeUseFrom, loadConfig, readBuildHistory, readMaintenanceState, readPullRequestCache, settingDefinition, sharedCompilationCache } from "@stim-cli/core/state";
29
+ import { buildCacheRoot, configDir, metroCacheRoot, rotatedLogPath } from "@stim-cli/core";
30
+ import { LAST_BUILD_KEYS, MAINTENANCE_LOG_NAME, coerceSettingText, deviceViewersDir, lastNativeUseFrom, loadConfig, parseNdjsonText, readBuildHistory, readJsonObject, readMaintenanceState, readPullRequestCache, settingDefinition, settingValueError, sharedCompilationCache } from "@stim-cli/core/state";
28
31
  import { isAbsolute as isAbsolute$1, join as join$1 } from "path";
29
32
  import chalk from "chalk";
30
33
  import { existsSync as existsSync$1, readdirSync as readdirSync$1, statSync as statSync$1 } from "fs";
@@ -126,7 +129,7 @@ function removalBlocker(facts, olderThan, now, noOwnCommitsIdleMs) {
126
129
  if (facts.bare) return skip("bare-repository", "bare repository");
127
130
  if (typeof facts.source === "object") return skip("source-checkout-unknown", `source checkout unknown: ${facts.source.refusal}`);
128
131
  if (facts.source === "source") return skip("source-checkout", "source checkout");
129
- if (facts.locked) return skip("locked", "locked with git worktree lock");
132
+ if (facts.locked) return skip("locked", LOCKED_REASON);
130
133
  if (facts.nested?.length) return skip("nested-worktree", `contains the worktree ${facts.nested.join(", ")}`);
131
134
  if (facts.inUse.length) return skip("in-use", `in use: ${facts.inUse.join("; ")}`);
132
135
  if (facts.porcelain === null) return skip("status-unreadable", "git status could not be read");
@@ -1320,6 +1323,11 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
1320
1323
  }) : { workspaces: [] };
1321
1324
  for (const entry of outputs.workspaces) {
1322
1325
  const root = entry.projectRoot === null ? null : canonicalPath(entry.projectRoot);
1326
+ if (root !== null && keptByLock(entry) && entry.keptReason) skips.push({
1327
+ target: root,
1328
+ reason: entry.keptReason,
1329
+ workspace: root
1330
+ });
1323
1331
  if (root === null || !entry.willClear || root === protectedRoot) continue;
1324
1332
  if (pinned(root)) {
1325
1333
  skips.push({
@@ -1481,6 +1489,432 @@ async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes
1481
1489
  };
1482
1490
  }
1483
1491
  //#endregion
1492
+ //#region src/maintenance/idle-stop.ts
1493
+ const HOUR_MS = 36e5;
1494
+ const SETTING = "maintenance.idleStopHours";
1495
+ const METRO_REQUESTS = /* @__PURE__ */ new Set([
1496
+ "bundle_response_started",
1497
+ "bundle_response_progress",
1498
+ "bundle_response_finished",
1499
+ "bundle_response_failed",
1500
+ "bundle_build_started",
1501
+ "bundle_build_done",
1502
+ "bundle_build_failed",
1503
+ "manifest_request_started",
1504
+ "manifest_request_finished",
1505
+ "manifest_request_failed",
1506
+ "hmr_update"
1507
+ ]);
1508
+ const STIM_SESSION = "stim-teardown-inventory";
1509
+ const AGENT_DEVICE_DRIVERS = /* @__PURE__ */ new Set(["agent-device-claim", "agent-device-lease"]);
1510
+ function decideIdleStop(evidence, { idleMs, now }) {
1511
+ if (!(idleMs > 0)) return {
1512
+ stop: false,
1513
+ reason: `${SETTING} is 0`
1514
+ };
1515
+ const last = evidence.activity.filter(({ at }) => Number.isFinite(at)).reduce((best, entry) => !best || entry.at > best.at ? entry : best, null);
1516
+ const idle = last ? now - last.at : NaN;
1517
+ if (last && idle < idleMs) return {
1518
+ stop: false,
1519
+ active: true,
1520
+ reason: `its last ${last.basis} was ${formatLongDuration(Math.max(0, idle))} ago, within ${SETTING}`
1521
+ };
1522
+ if (evidence.keep.length) return {
1523
+ stop: false,
1524
+ reason: evidence.keep.join("; ")
1525
+ };
1526
+ if (!last) return {
1527
+ stop: false,
1528
+ reason: "its last activity is unknown"
1529
+ };
1530
+ return {
1531
+ stop: true,
1532
+ idleMs: idle
1533
+ };
1534
+ }
1535
+ async function idleStopHours(root, env, operation) {
1536
+ const setting = settingDefinition(SETTING);
1537
+ if (!setting?.env) return { invalid: `${SETTING} is not in the settings registry` };
1538
+ const raw = env[setting.env];
1539
+ const fromEnv = raw !== void 0 && raw !== "";
1540
+ let value;
1541
+ if (fromEnv) value = coerceSettingText(setting, raw);
1542
+ else try {
1543
+ const resolved = operation ? await resolveProjectSettingsAsync(root, {
1544
+ env,
1545
+ signal: operation.signal,
1546
+ declareFor: operation
1547
+ }) : resolveProjectSettings(root);
1548
+ value = settingValueAt(resolved.settings, SETTING);
1549
+ } catch (error) {
1550
+ rethrowCleanup(error, operation?.signal);
1551
+ return { invalid: `${SETTING} cannot be resolved: ${error instanceof Error ? error.message : String(error)}` };
1552
+ }
1553
+ if (value === void 0) return { hours: Number(setting.default) };
1554
+ const error = settingValueError(setting, value);
1555
+ return error || typeof value !== "number" ? { invalid: `${fromEnv ? setting.env : SETTING} is invalid: expected ${error ?? "a number"}` } : { hours: value };
1556
+ }
1557
+ function deviceKeepReason(device, platform) {
1558
+ const { activity } = device;
1559
+ const what = `its ${device.kind === "ios" ? "simulator" : "emulator"} ${device.name}`;
1560
+ if (activity.state === "driven" && !activity.basis.every((basis) => AGENT_DEVICE_DRIVERS.has(basis))) return `${what} is driven by ${activity.driver?.tool ?? "an unknown tool"}`;
1561
+ if (activity.basis.includes("agent-device-claim") && !activity.recent?.["agent-action"]) return `${what} has a live agent-device claim whose session activity cannot be found`;
1562
+ const probed = activity.state === "driven" ? activity.unknown ?? [] : activity.state === "unknown" ? activity.basis : [];
1563
+ const unknown = platform === "win32" ? probed.filter((basis) => basis !== "driver-process") : probed;
1564
+ if (unknown.length) return `${what} has unknown activity (${unknown.join(", ")})`;
1565
+ return null;
1566
+ }
1567
+ function recentAt(activity, basis) {
1568
+ return Date.parse(activity.recent?.[basis] ?? "");
1569
+ }
1570
+ const missing = (error) => error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
1571
+ const message = (error) => error instanceof Error ? error.message : String(error);
1572
+ function agentSessionsActedAt(root) {
1573
+ const dir = join(workspaceAgentDeviceDir(root), "sessions");
1574
+ let names;
1575
+ try {
1576
+ names = readdirSync(dir);
1577
+ } catch (error) {
1578
+ return missing(error) ? NaN : `its agent-device sessions cannot be read (${message(error)})`;
1579
+ }
1580
+ let latest = NaN;
1581
+ for (const name of names) {
1582
+ if (name === STIM_SESSION) continue;
1583
+ try {
1584
+ const at = statSync(join(dir, name, "events.ndjson")).mtimeMs;
1585
+ if (!(at <= latest)) latest = at;
1586
+ } catch (error) {
1587
+ if (!missing(error)) return `its agent-device session ${name} cannot be read (${message(error)})`;
1588
+ }
1589
+ }
1590
+ return latest;
1591
+ }
1592
+ function metroRequestAt(root, since, serving) {
1593
+ const file = join(workspaceLogsDir$1(root), "metro.ndjson");
1594
+ const rotated = rotatedLogPath(file);
1595
+ const records = [];
1596
+ for (const path of [rotated, file]) try {
1597
+ records.push(...parseNdjsonText(readFileSync(path, "utf8")));
1598
+ } catch (error) {
1599
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
1600
+ return `its Metro log cannot be read (${error instanceof Error ? error.message : String(error)})`;
1601
+ }
1602
+ let latest = NaN;
1603
+ let earliest = Infinity;
1604
+ for (const record of records) {
1605
+ if (typeof record.ts !== "number") continue;
1606
+ earliest = Math.min(earliest, record.ts);
1607
+ if (typeof record.event === "string" && METRO_REQUESTS.has(record.event) && !(record.ts <= latest)) latest = record.ts;
1608
+ }
1609
+ if (!Number.isFinite(latest) && earliest > since && existsSync(rotated)) return `its rotated Metro log does not reach back ${SETTING}`;
1610
+ if (serving && earliest === Infinity) return "its dev server runs but its Metro log is missing or empty";
1611
+ return latest;
1612
+ }
1613
+ function unreadableViewers() {
1614
+ const dir = deviceViewersDir();
1615
+ let names;
1616
+ try {
1617
+ names = readdirSync(dir).filter((name) => name.endsWith(".json"));
1618
+ } catch (error) {
1619
+ return missing(error) ? null : `the stim-server viewer records cannot be read (${message(error)})`;
1620
+ }
1621
+ for (const name of names) {
1622
+ const record = readJsonObject(join(dir, name));
1623
+ if (!record) return `the stim-server viewer record ${name} cannot be read`;
1624
+ if ((0, process_identity_exports.inspectProcessIdentity)(record) === "unknown") return `the stim-server behind the viewer record ${name} cannot be verified`;
1625
+ }
1626
+ return null;
1627
+ }
1628
+ async function keepReasons(root, devices, { now, platform, locked, operation }) {
1629
+ const checks = {
1630
+ devServer: false,
1631
+ nativeRun: !locked
1632
+ };
1633
+ const keep = operation ? await workspaceInUseAsync(root, checks, operation.signal, operation) : workspaceInUse(root, checks);
1634
+ if (workspaceBuildInProgress(root)) keep.push("a build is in progress");
1635
+ if (readRemoteSession(root)) keep.push("it has a remote device session");
1636
+ const holder = canonicalPath(root);
1637
+ for (const { lease, path } of listLeaseFiles()) if (!lease) keep.push(`the device lease ${path} cannot be read`);
1638
+ else if (!leaseIsExpired(lease, now) && canonicalPath(lease.holder) === holder) keep.push(`it holds the ${lease.platform} device lease on ${lease.deviceName ?? lease.id}`);
1639
+ const browser = webFacts(readWebRecord(root));
1640
+ if (browser && browser.status !== "stopped") keep.push(`its owned Chrome is ${browser.status}`);
1641
+ for (const device of devices) {
1642
+ const reason = deviceKeepReason(device, platform);
1643
+ if (reason) keep.push(reason);
1644
+ }
1645
+ const viewers = devices.length ? unreadableViewers() : null;
1646
+ if (viewers) keep.push(viewers);
1647
+ return keep;
1648
+ }
1649
+ async function idleStopEvidence(root, devices, { now, idleMs, platform = process.platform, locked = false, serving = false, operation }) {
1650
+ const state = readWorkspaceState$1(root);
1651
+ if (!state && existsSync(workspaceStateFile$1(root))) return {
1652
+ activity: [],
1653
+ keep: ["its state.json cannot be read"]
1654
+ };
1655
+ const sessions = agentSessionsActedAt(root);
1656
+ if (typeof sessions === "string") return {
1657
+ activity: [],
1658
+ keep: [sessions]
1659
+ };
1660
+ const build = state?.lastBuild;
1661
+ const builtAt = Date.parse(String(build?.startedAt ?? "")) + (typeof build?.durationMs === "number" ? build.durationMs : 0);
1662
+ const activity = [
1663
+ {
1664
+ basis: "stim command",
1665
+ at: Date.parse(String(state?.lastUsedAt ?? ""))
1666
+ },
1667
+ {
1668
+ basis: "native run",
1669
+ at: builtAt
1670
+ },
1671
+ {
1672
+ basis: "agent-device command",
1673
+ at: typeof sessions === "number" ? sessions : NaN
1674
+ },
1675
+ ...devices.flatMap(({ activity: device }) => [{
1676
+ basis: "agent-device command",
1677
+ at: recentAt(device, "agent-action")
1678
+ }, {
1679
+ basis: "live view",
1680
+ at: recentAt(device, "viewer")
1681
+ }])
1682
+ ].filter(({ at }) => Number.isFinite(at));
1683
+ if (activity.some(({ at }) => now - at < idleMs)) return {
1684
+ activity,
1685
+ keep: []
1686
+ };
1687
+ const keep = await keepReasons(root, devices, {
1688
+ now,
1689
+ platform,
1690
+ locked,
1691
+ operation
1692
+ });
1693
+ if (keep.length) return {
1694
+ activity,
1695
+ keep
1696
+ };
1697
+ const metro = metroRequestAt(root, now - idleMs, serving);
1698
+ if (typeof metro === "string") return {
1699
+ activity,
1700
+ keep: [metro]
1701
+ };
1702
+ if (Number.isFinite(metro)) activity.push({
1703
+ basis: "Metro request",
1704
+ at: metro
1705
+ });
1706
+ return {
1707
+ activity,
1708
+ keep
1709
+ };
1710
+ }
1711
+ function rethrowCleanup(error, signal) {
1712
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
1713
+ signal?.throwIfAborted();
1714
+ }
1715
+ async function unseenEmulatorError(unseen, operation) {
1716
+ try {
1717
+ const signal = operation?.signal;
1718
+ const adb = operation ? await listAdbDevicesAsync({
1719
+ timeoutMs: 5e3,
1720
+ signal,
1721
+ operation
1722
+ }) : listAdbDevices({ timeoutMs: 5e3 });
1723
+ for (const { serial } of [...adb.emulators, ...adb.unhealthy.filter((entry) => entry.kind === "emulator")]) {
1724
+ const name = operation ? await getAvdNameForSerialAsync(serial, {
1725
+ timeoutMs: 5e3,
1726
+ signal,
1727
+ operation
1728
+ }) : getAvdNameForSerial(serial, { timeoutMs: 5e3 });
1729
+ if (name === null) return `its emulators cannot be identified to prove them idle (${serial} gave no AVD name)`;
1730
+ if (unseen.includes(name)) return `its emulator ${name} runs as ${serial}, but its activity could not be read`;
1731
+ }
1732
+ return null;
1733
+ } catch (error) {
1734
+ rethrowCleanup(error, operation?.signal);
1735
+ return `its emulators cannot be listed to prove them idle (${error instanceof Error ? error.message : String(error)})`;
1736
+ }
1737
+ }
1738
+ function survivingServer(recorded) {
1739
+ const pid = recorded?.serverPid;
1740
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return null;
1741
+ const token = recorded?.serverProcessToken;
1742
+ return (typeof token === "string" ? ["same", "unknown"].includes((0, process_identity_exports.inspectProcessIdentity)({
1743
+ pid,
1744
+ processToken: token
1745
+ })) : pidExists(pid)) ? `its dev server (pid ${pid}) may still run after its supervisor exited; stim stop ends it` : null;
1746
+ }
1747
+ async function candidate(path, env, devices, operation) {
1748
+ const signal = operation?.signal;
1749
+ const project = loadConfig$1()?.projects?.[path];
1750
+ if (!project) return null;
1751
+ const supervisor = resolveSupervisorTarget({
1752
+ state: readWorkspaceState$1(path)?.supervisor,
1753
+ record: project.supervisor,
1754
+ reservedPort: project.metroPort
1755
+ });
1756
+ const slots = projectDeviceSlots(project);
1757
+ const ownsIos = slots.some(({ platforms }) => platforms.ios?.owned === true);
1758
+ const owns = ownsIos || slots.some(({ platforms }) => platforms.android?.owned === true);
1759
+ if (!owns && (supervisor.status === "none" || supervisor.status === "stale")) return null;
1760
+ const setting = await idleStopHours(path, env, operation);
1761
+ if ("invalid" in setting) return { skip: setting.invalid };
1762
+ if (setting.hours === 0) return null;
1763
+ let booted = [];
1764
+ if (owns) try {
1765
+ booted = (await devices(ownsIos)).filter((device) => device.project === path);
1766
+ } catch (error) {
1767
+ rethrowCleanup(error, signal);
1768
+ return supervisor.status === "none" || supervisor.status === "stale" ? null : { skip: "its owned devices cannot be listed to prove them idle" };
1769
+ }
1770
+ if (supervisor.status === "unverified") return { skip: `its dev server supervisor cannot be verified: ${supervisor.reason ?? "unknown identity"}` };
1771
+ if (supervisor.status === "ours" && readWorkspaceState$1(path)?.supervisor?.mode === "command-child") return { skip: "its dev server runs metro.command, whose bundle and HMR requests Stim cannot see" };
1772
+ if (supervisor.status === "ours" && readWorkspaceState$1(path)?.supervisor?.hmrUpdates !== true) return { skip: "its dev server does not report HMR updates to Stim: an Expo SDK before 54, or a server an older Stim started, which stim stop and stim start restart" };
1773
+ const avdNames = slots.flatMap(({ platforms }) => platforms.android?.owned && platforms.android.avdName ? [platforms.android.avdName] : []);
1774
+ if (supervisor.status !== "ours" && booted.length === 0) return null;
1775
+ const orphan = supervisor.status === "stale" ? survivingServer(readWorkspaceState$1(path)?.supervisor) : null;
1776
+ if (orphan) return { skip: orphan };
1777
+ const seen = new Set(booted.flatMap((device) => device.kind === "android" ? [device.id] : []));
1778
+ const unseen = avdNames.filter((name) => !seen.has(name));
1779
+ const emulators = unseen.length ? await unseenEmulatorError(unseen, operation) : null;
1780
+ if (emulators) return { skip: emulators };
1781
+ return {
1782
+ path,
1783
+ hours: setting.hours,
1784
+ serving: supervisor.status === "ours",
1785
+ devices: booted
1786
+ };
1787
+ }
1788
+ /**
1789
+ * Plans stopping each workspace, other than `protectedRoot`, whose dev server runs or whose owned simulator or
1790
+ * emulator is booted, once it has had no real activity for its maintenance.idleStopHours.
1791
+ */
1792
+ async function planIdleStop({ protectedRoot, now, devices, env = process.env, platform = process.platform, operation }) {
1793
+ const plan = {
1794
+ actions: [],
1795
+ blocked: [],
1796
+ skips: []
1797
+ };
1798
+ if (deviceSweepIsScoped(env)) return plan;
1799
+ for (const path of Object.keys(loadConfig$1()?.projects ?? {})) {
1800
+ const root = canonicalPath(path);
1801
+ if (root === protectedRoot) continue;
1802
+ operation?.signal.throwIfAborted();
1803
+ const found = await candidate(path, env, devices, operation);
1804
+ if (!found) continue;
1805
+ if ("skip" in found) {
1806
+ plan.skips.push({
1807
+ target: root,
1808
+ reason: found.skip,
1809
+ workspace: root
1810
+ });
1811
+ continue;
1812
+ }
1813
+ const idleMs = found.hours * HOUR_MS;
1814
+ const decision = decideIdleStop(await idleStopEvidence(path, found.devices, {
1815
+ now,
1816
+ idleMs,
1817
+ platform,
1818
+ serving: found.serving,
1819
+ operation
1820
+ }), {
1821
+ idleMs,
1822
+ now
1823
+ });
1824
+ if (!decision.stop) {
1825
+ if (!decision.active) plan.skips.push({
1826
+ target: root,
1827
+ reason: decision.reason,
1828
+ workspace: root
1829
+ });
1830
+ continue;
1831
+ }
1832
+ plan.actions.push({
1833
+ kind: "would-stop-workspace",
1834
+ check: "idle",
1835
+ target: root,
1836
+ workspace: root,
1837
+ bytes: 0,
1838
+ reason: `no real activity for ${formatLongDuration(decision.idleMs)}, over ${SETTING} ${found.hours}`
1839
+ });
1840
+ }
1841
+ return plan;
1842
+ }
1843
+ const kept$1 = (detail) => ({
1844
+ status: "kept",
1845
+ bytes: 0,
1846
+ detail
1847
+ });
1848
+ /**
1849
+ * Stops a planned idle workspace the way `stim stop` does, under its native-run and metro-start locks, after
1850
+ * deciding again that it is still idle. Physical-device leases stay.
1851
+ */
1852
+ async function stopIdleWorkspace(action, { protectedRoot, devices, operation, env = operation?.env ?? process.env, by = "automatic maintenance" }) {
1853
+ const root = action.workspace ?? action.target;
1854
+ if (root === protectedRoot) return kept$1("the command that started this pass runs in it");
1855
+ if (deviceSweepIsScoped(env)) return kept$1("STIM_HOME scopes this config, and devices are machine-wide");
1856
+ const path = Object.keys(loadConfig$1()?.projects ?? {}).find((entry) => canonicalPath(entry) === root) ?? root;
1857
+ const stillIdle = async () => {
1858
+ const now = Date.now();
1859
+ const found = await candidate(path, env, devices(), operation);
1860
+ if (!found) return "nothing of it runs any more";
1861
+ if ("skip" in found) return found.skip;
1862
+ const idleMs = found.hours * HOUR_MS;
1863
+ const decision = decideIdleStop(await idleStopEvidence(path, found.devices, {
1864
+ now,
1865
+ idleMs,
1866
+ locked: true,
1867
+ serving: found.serving,
1868
+ operation
1869
+ }), {
1870
+ idleMs,
1871
+ now
1872
+ });
1873
+ return decision.stop ? null : decision.reason;
1874
+ };
1875
+ const signal = operation?.signal;
1876
+ const run = await withIdleWorkspace(path, async () => {
1877
+ try {
1878
+ return await withWorkspaceProcessLock(workspaceDir(path), "metro-start", async () => {
1879
+ const active = await stillIdle();
1880
+ if (active) return kept$1(active);
1881
+ const stopped = await runStop({
1882
+ root: path,
1883
+ operation,
1884
+ report: () => {},
1885
+ requester: { by: `${by} (${action.reason})` },
1886
+ releaseLeases: () => []
1887
+ });
1888
+ return stopped.ok ? {
1889
+ status: "done",
1890
+ bytes: 0,
1891
+ detail: stopped.summary
1892
+ } : {
1893
+ status: "failed",
1894
+ bytes: 0,
1895
+ detail: stopped.summary
1896
+ };
1897
+ }, {
1898
+ external: true,
1899
+ waitMs: 0,
1900
+ ownerPurpose: "idle stop",
1901
+ signal,
1902
+ declareSpawnsFor: operation
1903
+ });
1904
+ } catch (error) {
1905
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
1906
+ signal?.throwIfAborted();
1907
+ if (workspaceProcessLockError(error)) return kept$1("a stim start holds its metro-start lock");
1908
+ throw error;
1909
+ }
1910
+ }, {
1911
+ purpose: "automatic maintenance",
1912
+ devServer: false,
1913
+ operation
1914
+ });
1915
+ return run.ran ? run.value : kept$1(`in use: ${run.reasons.join("; ")}`);
1916
+ }
1917
+ //#endregion
1484
1918
  //#region src/workspace/running-from.ts
1485
1919
  const LIST_TIMEOUT_MS = 1e4;
1486
1920
  function parsePs(text) {
@@ -1629,6 +2063,7 @@ function artifactWorktree(root, entries, source = sourceCheckoutOf(entries)) {
1629
2063
  return {
1630
2064
  path: entry.path,
1631
2065
  linked: entry.path !== source.path,
2066
+ locked: Boolean(entry.locked),
1632
2067
  keys
1633
2068
  };
1634
2069
  }
@@ -1725,6 +2160,7 @@ function buildNativeUseReason(keys, now) {
1725
2160
  function artifactGuard(candidate, kind, pinned, locked, running) {
1726
2161
  const { worktree, path, keys } = candidate;
1727
2162
  if (keys.some(pinned)) return "pinned by maintenance.keep";
2163
+ if (candidate.locked) return LOCKED_REASON;
1728
2164
  if (!pathExists(path)) return "it is already gone";
1729
2165
  let real;
1730
2166
  try {
@@ -1770,7 +2206,8 @@ async function collectArtifactsAsync(kind, options, signal, operation) {
1770
2206
  const candidate = {
1771
2207
  worktree: worktree.path,
1772
2208
  path,
1773
- keys: worktree.keys
2209
+ keys: worktree.keys,
2210
+ locked: worktree.locked
1774
2211
  };
1775
2212
  let reason = protectedHere ? "the command that started this pass runs inside its worktree" : artifactGuard(candidate, kind, options.pinned, void 0, running);
1776
2213
  if (!reason) {
@@ -1828,7 +2265,8 @@ function collectArtifacts(kind, options) {
1828
2265
  const candidate = {
1829
2266
  worktree: worktree.path,
1830
2267
  path,
1831
- keys: worktree.keys
2268
+ keys: worktree.keys,
2269
+ locked: worktree.locked
1832
2270
  };
1833
2271
  const reason = protectedHere ? "the command that started this pass runs inside its worktree" : keptReason(candidate, kind, {
1834
2272
  ...options,
@@ -1867,7 +2305,8 @@ async function clearArtifact(action, kind, options) {
1867
2305
  const candidate = {
1868
2306
  worktree: worktree.path,
1869
2307
  path: action.target,
1870
- keys: worktree.keys
2308
+ keys: worktree.keys,
2309
+ locked: worktree.locked
1871
2310
  };
1872
2311
  const running = kind === "dependencies" ? await readRunningProcessesAsync() : null;
1873
2312
  const early = keptReason(candidate, kind, {
@@ -1889,6 +2328,8 @@ async function clearArtifact(action, kind, options) {
1889
2328
  const before = lowestFree(measureDisk());
1890
2329
  const [key] = candidate.keys;
1891
2330
  const remove = async () => {
2331
+ const lock = candidate.locked ? "locked" : await worktreeLockAsync(candidate.worktree);
2332
+ if (lock) return lockReason(lock);
1892
2333
  const reason = keptReason(candidate, kind, {
1893
2334
  ...options,
1894
2335
  now: Date.now(),
@@ -2285,6 +2726,11 @@ async function planLevel2({ settings, sizes, protectedRoot, pinned, now, operati
2285
2726
  lighten: true
2286
2727
  })).workspaces.flatMap((entry) => {
2287
2728
  const root = entry.projectRoot === null ? null : canonicalPath(entry.projectRoot);
2729
+ if (root !== null && keptByLock(entry) && entry.keptReason) skips.push({
2730
+ target: root,
2731
+ reason: entry.keptReason,
2732
+ workspace: root
2733
+ });
2288
2734
  if (root === null || !entry.willClear || root === protectedRoot || pinned(root)) return [];
2289
2735
  const used = workspaceLastUsed(root);
2290
2736
  return Number.isFinite(used) ? [{
@@ -2501,7 +2947,7 @@ async function planPressureLevels(level, context) {
2501
2947
  function triggeringRoot(operation) {
2502
2948
  return canonicalPath(findProjectRoot$1(operation?.cwd ?? process.cwd()) ?? join(configDir(), "maintenance", "no-project"));
2503
2949
  }
2504
- async function plannedMaintenance(pressure, recordedSizes, settings, { devices = true, sweep = false, worktrees = false, idle = false, refresh = true, operation } = {}) {
2950
+ async function plannedMaintenance(pressure, recordedSizes, settings, { devices = true, sweep = false, worktrees = false, idle = false, refresh = true, background = false, operation } = {}) {
2505
2951
  const env = operation?.env ?? process.env;
2506
2952
  const resolved = resolveBudget({ env });
2507
2953
  if (resolved.error) throw new Error(resolved.error);
@@ -2577,7 +3023,7 @@ async function plannedMaintenance(pressure, recordedSizes, settings, { devices =
2577
3023
  recentlyUsed: (workspace) => recentUse(workspace, settings)
2578
3024
  });
2579
3025
  const merge = (extra) => {
2580
- for (const action of extra.actions) if (!result.actions.some((entry) => entry.kind === action.kind && entry.target === action.target && (entry.level === action.level || action.kind === "would-delete-devices"))) result.actions.push(action);
3026
+ for (const action of extra.actions) if (!result.actions.some((entry) => entry.kind === action.kind && entry.target === action.target && (entry.level === action.level || action.kind === "would-delete-devices") && (action.check !== "idle" || entry.check === "idle"))) result.actions.push(action);
2581
3027
  result.blocked.push(...extra.blocked);
2582
3028
  result.skips.push(...extra.skips);
2583
3029
  };
@@ -2604,11 +3050,27 @@ async function plannedMaintenance(pressure, recordedSizes, settings, { devices =
2604
3050
  merge(planned);
2605
3051
  result.worktreeSweep = planned.sweep;
2606
3052
  }
2607
- if (idle && devices) merge(planIdleShutdown({
2608
- protectedRoot: root,
2609
- now: Date.now(),
2610
- env
2611
- }));
3053
+ if (idle && devices) {
3054
+ merge(planIdleShutdown({
3055
+ protectedRoot: root,
3056
+ now: Date.now(),
3057
+ env
3058
+ }));
3059
+ const now = Date.now();
3060
+ try {
3061
+ merge(await planIdleStop({
3062
+ protectedRoot: background ? "" : root,
3063
+ now,
3064
+ env,
3065
+ operation,
3066
+ devices: operation ? cachedDeviceActivityAsync(now, env, operation.signal, operation) : cachedDeviceActivity(now)
3067
+ }));
3068
+ } catch (error) {
3069
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
3070
+ operation?.signal.throwIfAborted();
3071
+ result.blocked.push(`idle stop could not be planned: ${error instanceof Error ? error.message : String(error)}`);
3072
+ }
3073
+ }
2612
3074
  if (pressure) {
2613
3075
  const pressured = await planPressureLevels(diskPressureLevel(lowestFree(pressure.disk), budget, settings), {
2614
3076
  settings,
@@ -2804,7 +3266,8 @@ async function clearOutputs(action, context) {
2804
3266
  olderThan,
2805
3267
  now,
2806
3268
  measure: false,
2807
- lighten
3269
+ lighten,
3270
+ only: root
2808
3271
  });
2809
3272
  const entry = report.workspaces.find((candidate) => candidate.projectRoot && canonicalPath(candidate.projectRoot) === root);
2810
3273
  if (!entry) return kept("it has no build outputs any more");
@@ -3096,7 +3559,12 @@ async function executeAction(action, context) {
3096
3559
  case "would-delete-parked-device": return deleteParkedDevice(action);
3097
3560
  case "would-delete-devices": return deleteDevices(action, context);
3098
3561
  case "would-shutdown-device": return action.check === "disk" ? kept("automatic maintenance does not stop devices or dev servers for disk") : shutDownIdleDevice(action);
3099
- case "would-stop-workspace": return action.level === 3 ? stopWorkspace(action, context) : kept("automatic maintenance stops dev servers only at disk pressure level 3");
3562
+ case "would-stop-workspace":
3563
+ if (action.level === 3) return stopWorkspace(action, context);
3564
+ return action.check === "idle" ? stopIdleWorkspace(action, {
3565
+ protectedRoot: context.background ? "" : context.protectedRoot,
3566
+ devices: () => cachedDeviceActivity(Date.now())
3567
+ }) : kept("automatic maintenance stops dev servers only at disk pressure level 3 or after maintenance.idleStopHours");
3100
3568
  case "would-clear-build-dirs":
3101
3569
  case "would-delete-dependencies": return clearArtifacts(action, context);
3102
3570
  case "would-stop-gradle-daemons": return stopGradleDaemonsAction(action);
@@ -3110,4 +3578,4 @@ function diskRecovered(budget) {
3110
3578
  return measureDisk().every((disk) => disk.freeMb >= floor);
3111
3579
  }
3112
3580
  //#endregion
3113
- export { worktreeRemovalReason as C, worktreePullRequestNote as S, ownedDeviceUsage as _, previewMaintenance as a, collectWorktreeSweep as b, deleteTempLeftovers as c, appRoots as d, artifactWorktrees as f, measureOwnedDevices as g, finishedWorkspaces as h, plannedMaintenance as i, tempLeftoverLines as l, collectArtifactsAsync as m, diskRecovered as n, triggeringRoot as o, clearArtifact as p, executeAction as r, collectTempLeftovers as s, deleteIdleWorkspaceDevices as t, tempLeftoverSection as u, maintenancePinned as v, removeWorktrees as x, candidateRootsAsync as y };
3581
+ export { candidateRootsAsync as C, worktreeRemovalReason as D, worktreePullRequestNote as E, maintenancePinned as S, removeWorktrees as T, finishedWorkspaces as _, previewMaintenance as a, cachedDeviceActivity as b, deleteTempLeftovers as c, appRoots as d, artifactWorktrees as f, stopIdleWorkspace as g, planIdleStop as h, plannedMaintenance as i, tempLeftoverLines as l, collectArtifactsAsync as m, diskRecovered as n, triggeringRoot as o, clearArtifact as p, executeAction as r, collectTempLeftovers as s, deleteIdleWorkspaceDevices as t, tempLeftoverSection as u, measureOwnedDevices as v, collectWorktreeSweep as w, cachedDeviceActivityAsync as x, ownedDeviceUsage as y };