stim 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/{activity-B2-kb7k0.mjs → activity-BxwdD81S.mjs} +29 -16
  2. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  3. package/dist/{android-Db2eEMaf.mjs → android-B_NumVyQ.mjs} +21 -20
  4. package/dist/{android-Cu_L4RpL.mjs → android-Ift4kVvt.mjs} +56 -47
  5. package/dist/{android-cas-PnKfAdB2.mjs → android-cas-8U4DPqeR.mjs} +1 -1
  6. package/dist/{android-CYmq0jNi.mjs → android-r2rBL8kV.mjs} +1 -1
  7. package/dist/{app-install-CAAplXAt.mjs → app-install-CKWdbXIX.mjs} +48 -5
  8. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  9. package/dist/{budget-CMiqghig.mjs → budget-DjrURfQn.mjs} +356 -366
  10. package/dist/{build-plan-aeGMFX7e.mjs → build-plan-DjeSb2Xl.mjs} +19 -20
  11. package/dist/{build-slots-Df1ApSuH.mjs → build-slots-rUk77xqh.mjs} +7 -2
  12. package/dist/{cli-0cX5s4NM.mjs → cli-BkYZ_6hy.mjs} +23 -17
  13. package/dist/cli.mjs +1 -1
  14. package/dist/{client-C2IS36Qf.mjs → client-BTY686R6.mjs} +57 -22
  15. package/dist/collector-run.mjs +5 -192
  16. package/dist/{command-output-CliHHn7a.mjs → command-output-DnJMdEY-.mjs} +1 -1
  17. package/dist/{created-devices-BwVb3liH.mjs → created-devices-D5owjN4C.mjs} +1 -1
  18. package/dist/{dependency-state-BmsjKZ2H.mjs → dependency-state-B6cMXZab.mjs} +1 -1
  19. package/dist/{deps-BFlKbOIp.mjs → deps-urbmfXig.mjs} +2 -2
  20. package/dist/{dev-client-CjzLoNs0.mjs → dev-client-CroAIjjj.mjs} +3 -40
  21. package/dist/{device-BZ1IyS5A.mjs → device-DZQjF7H5.mjs} +7 -6
  22. package/dist/{device-capacity-DMuKC7nx.mjs → device-capacity-BHwxNBlu.mjs} +2 -2
  23. package/dist/device-host-worker.mjs +223 -28
  24. package/dist/{device-ios-DMQBUPus.mjs → device-ios-BadmeL2V.mjs} +7 -7
  25. package/dist/{device-lease-W5Pi1q38.mjs → device-lease-D-hLqx1b.mjs} +2 -2
  26. package/dist/{device-pool-B2r38YjS.mjs → device-lease-run-CqjShqWW.mjs} +5 -125
  27. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  28. package/dist/{device-remote-9ulPsdhY.mjs → device-remote-D5WVWqOq.mjs} +12 -12
  29. package/dist/{doctor-BcE5xsBn.mjs → doctor-Cimf-gGB.mjs} +80 -34
  30. package/dist/{error-diagnostics-Cb0P1WLS.mjs → error-diagnostics-B9L4Ae0x.mjs} +11 -15
  31. package/dist/{gc-BuL1Genr.mjs → gc-Bqy4bjEu.mjs} +194 -31
  32. package/dist/{gradle-CmdOBq1d.mjs → gradle-BsO1MdsH.mjs} +4 -4
  33. package/dist/{guide-cCSGRFd3.mjs → guide-B_uyKTxW.mjs} +588 -53
  34. package/dist/{guide-status-CIoWK6mG.mjs → guide-status-CjEZMAD4.mjs} +1 -1
  35. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  36. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  37. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  38. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  39. package/dist/{build-progress-C2Jv1AtA.mjs → idle-DYVymG92.mjs} +233 -6
  40. package/dist/{idle-shutdown-B5LPaNyY.mjs → idle-shutdown-CTsDnAde.mjs} +6 -6
  41. package/dist/{in-use-CndPMilN.mjs → in-use-DvBw0Kxo.mjs} +29 -9
  42. package/dist/{ios-3CtCRhwr.mjs → ios-BaInn3sW.mjs} +2 -2
  43. package/dist/ios-Cr1h5gnO.mjs +189 -0
  44. package/dist/{ios-Chob8ny7.mjs → ios-aq3QoGI3.mjs} +729 -452
  45. package/dist/{ios-device-DERmMmk-.mjs → ios-device-FfCN_C8p.mjs} +1 -1
  46. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  47. package/dist/{launch-verify-CLlofxRt.mjs → launch-verify-2DBRizjo.mjs} +3 -3
  48. package/dist/logs-CHEVK8zD.mjs +371 -0
  49. package/dist/{machines-CYtOCqcj.mjs → machines-BIBUdhpj.mjs} +16 -10
  50. package/dist/{macos-BwtbUtTN.mjs → macos-CSYEAFmF.mjs} +22 -17
  51. package/dist/macos-run.mjs +1 -1
  52. package/dist/maintenance-run.d.mts +4 -0
  53. package/dist/maintenance-run.mjs +254 -0
  54. package/dist/{metro-Di8EN2Tb.mjs → metro-BeK1ZPz1.mjs} +1 -1
  55. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  56. package/dist/{named-ports-Dj_XBNaL.mjs → named-ports-Bk_YXEmE.mjs} +1 -1
  57. package/dist/{native-run-DFDoIRbY.mjs → native-run-NaBrRFtQ.mjs} +2 -2
  58. package/dist/{native-runtime-i6OkVg-w.mjs → native-runtime-DBzRnEKS.mjs} +6 -6
  59. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  60. package/dist/offload-worker.d.mts +1 -0
  61. package/dist/offload-worker.mjs +13 -8
  62. package/dist/{ownership-BkYxUtuj.mjs → ownership-DiA16AsN.mjs} +2 -2
  63. package/dist/{stop-cause-CZ_Mr-v9.mjs → ownership-hCkPzGmr.mjs} +4 -98
  64. package/dist/{ports-DddwttKM.mjs → ports-R2k4Knz3.mjs} +2 -2
  65. package/dist/{prebuild-BvxDlD6C.mjs → prebuild-D2fsca_Z.mjs} +4 -4
  66. package/dist/preview-DhVbvPAZ.mjs +318 -0
  67. package/dist/{project-CV3tDFHl.mjs → project-Du91Ilvc.mjs} +67 -3
  68. package/dist/{recordings-GEXp4iBT.mjs → recordings-BIXOzVnr.mjs} +1 -1
  69. package/dist/{reload-ChPiwvot.mjs → reload-rM9jV1fz.mjs} +49 -12
  70. package/dist/{remote-cache-CO2elm_b.mjs → remote-cache-C8-V13XN.mjs} +4 -4
  71. package/dist/{run-B-xqRrsg.mjs → run-f4vI6EnK.mjs} +2 -2
  72. package/dist/{server-bare-X7PUgizL.mjs → server-bare-CpPGmnmO.mjs} +1 -1
  73. package/dist/server-expo-CVqO2c35.mjs +2 -0
  74. package/dist/{server-expo-DhtX6bom.mjs → server-expo-MC0rHlOC.mjs} +4 -4
  75. package/dist/settings-BBpMcDbD.mjs +25 -0
  76. package/dist/{settings-DbL6uhH8.mjs → settings-ZkDzkzvB.mjs} +10 -4
  77. package/dist/settings-oTSkkNrM.mjs +1439 -0
  78. package/dist/settings.schema.json +432 -39
  79. package/dist/{simslim-Ciluxlq4.mjs → simslim-B8-7AoTI.mjs} +3 -3
  80. package/dist/{slot-launch-BY0BrOLh.mjs → slot-launch-B8VrHxn1.mjs} +10 -7
  81. package/dist/{spawn-entry-DncpJvWL.mjs → spawn-entry-jw19m8Nl.mjs} +1 -0
  82. package/dist/{start-CPO4wRg7.mjs → start-CX8Ig-U2.mjs} +1 -1
  83. package/dist/{start-CUD9nasA.mjs → start-ClDfwXa4.mjs} +21 -17
  84. package/dist/{state-BxCgeTQu.mjs → state-3DwSQIBK.mjs} +2 -2
  85. package/dist/{state-DFm77wRn.mjs → state-C1jB7Q8q.mjs} +5 -5
  86. package/dist/{state-CaeaHbDA.mjs → state-DcW6vOd_.mjs} +1 -1
  87. package/dist/{stats-DBLLFmKd.mjs → stats-CEZ_i5je.mjs} +16 -5
  88. package/dist/{status-l84Gbvp2.mjs → status-BuvBFeII.mjs} +5 -3
  89. package/dist/{status-DYXxNOT0.mjs → status-Cs7VLg9D.mjs} +148 -44
  90. package/dist/status-DlKvVJBE.mjs +49 -0
  91. package/dist/{stop-Bvf1MflS.mjs → stop-B0Yxtmiy.mjs} +3 -3
  92. package/dist/{stop-Bad61z_2.mjs → stop-Bffozsac.mjs} +61 -20
  93. package/dist/{stop-CceYh1yG.mjs → stop-Dt_dM16A.mjs} +2 -2
  94. package/dist/stop-cause-CidxIqup.mjs +97 -0
  95. package/dist/supervisor-run.mjs +148 -17
  96. package/dist/{support-CDQbDkL5.mjs → support-CAP8qDUF.mjs} +4 -3
  97. package/dist/{support-WLOzVKUv.mjs → support-D4aYJniO.mjs} +34 -9
  98. package/dist/{teardown-BYuQafiA.mjs → teardown-CL6vDs2Y.mjs} +56 -47
  99. package/dist/{toolchain-DmeYjXD9.mjs → toolchain-DkUflduf.mjs} +10 -10
  100. package/dist/trigger-Bemu8ytw.mjs +65 -0
  101. package/dist/trigger-CXts6qt1.mjs +2 -0
  102. package/dist/{warm-progress-DVHLN4uC.mjs → warm-progress-CpGv3Wb6.mjs} +274 -5
  103. package/dist/{web-k6w8-HnE.mjs → web-DWUaMp8P.mjs} +15 -16
  104. package/dist/web-run.mjs +5 -5
  105. package/dist/{workspace-state-CL2V2Ons.mjs → workspace-state-B8-aJ1_5.mjs} +33 -33
  106. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  107. package/dist/{worktree-qXKzADSC.mjs → worktree-K-xj9Db0.mjs} +497 -30
  108. package/dist/{worktree-BT_uULFx.mjs → worktree-jnG8Iph5.mjs} +1 -1
  109. package/package.json +5 -5
  110. package/dist/hosted-macos-DwowNU7W.mjs +0 -447
  111. package/dist/logs-h9h6MUvE.mjs +0 -568
  112. package/dist/machine-usage-D_YYMzfR.mjs +0 -283
  113. package/dist/merge-state-BnxLNQiM.mjs +0 -272
  114. package/dist/server-expo-KJeD109I.mjs +0 -2
  115. package/dist/settings-CduVcoxj.mjs +0 -680
  116. package/dist/watchman-Bx0wceoF.mjs +0 -238
  117. package/dist/worktree-C0c5Udwa.mjs +0 -753
@@ -2,37 +2,466 @@ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  import { E as upsertProject, H as projectDeviceSlots, S as removeProject, _ as loadConfig$1, a as clearDevice, d as getConfigDir, g as isPathPrefix, k as acquireAvdClaim, p as getProject } from "./config-D0DbpZtM.mjs";
3
3
  import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
4
  import { a as pullRequestLookup, r as endedPullRequest } from "./pull-request-pxnDCQzn.mjs";
5
- import { b as workspaceDir, i as recordWorkspaceUse, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
6
- import { C as sourceCheckoutOf, S as restoreFile, T as warmWorktreePaths, _ as readWorktreeExclude, b as resolveFullRef, d as hasUncommittedWork, f as isPodInstallChurn, g as podsOutOfSync, h as locallyKnownUpstream, i as depsOutOfSync, l as hasPopulatedSubmodules, m as listWorktrees, n as cloneIgnoredEntries, o as dirtyPaths, r as deleteBranch, s as gitCommonDir, t as branchExists, u as hasRemote, v as removeWorktree, w as unpushedCommits } from "./worktree-C0c5Udwa.mjs";
7
- import { a as findProjectRoot$1, n as appProjectProblem } from "./project-CV3tDFHl.mjs";
8
- import { a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, u as readRemoteSession } from "./state-CaeaHbDA.mjs";
9
- import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-CliHHn7a.mjs";
10
- import { u as releaseWorkspaceLeases } from "./device-lease-W5Pi1q38.mjs";
5
+ import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-DnJMdEY-.mjs";
6
+ import { $ as cloneIgnoredEntries, A as readCommittedSettings, H as unknownSettingKeys, P as resolveSettings, Q as branchExists, R as settingShapeErrors, St as warmWorktreePaths, _t as resolveFullRef, a as SETTING_SHAPE_REMEDY, at as gitCommonDirOnDisk, bt as sourceCheckoutOf, ct as hasUncommittedWork, dt as listWorktrees, et as deleteBranch, ft as locallyKnownUpstream, ht as removeWorktree, it as gitCommonDir, lt as isPodInstallChurn, mt as readWorktreeExclude, ot as hasPopulatedSubmodules, pt as podsOutOfSync, rt as dirtyPaths, st as hasRemote, tt as depsOutOfSync, xt as unpushedCommits, yt as restoreFile } from "./settings-oTSkkNrM.mjs";
7
+ import { n as appProjectProblem, o as findProjectRoot$1 } from "./project-Du91Ilvc.mjs";
8
+ import { l as resolveProjectMetro, o as pidExists, r as killMetroTree, u as signalProcessTree } from "./metro-BeK1ZPz1.mjs";
9
+ import { g as workspaceAgentDeviceDir, i as recordWorkspaceUse, r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-B8-aJ1_5.mjs";
10
+ import { o as readWebRecord } from "./state-C1jB7Q8q.mjs";
11
+ import { a as machineNumber } from "./budget-DjrURfQn.mjs";
12
+ import { n as withIdleWorkspace, t as emptyWorkspaceDir } from "./in-use-DvBw0Kxo.mjs";
13
+ import { t as agentDeviceDaemon } from "./activity-BxwdD81S.mjs";
14
+ import { u as releaseWorkspaceLeases } from "./device-lease-D-hLqx1b.mjs";
15
+ import { a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, u as readRemoteSession } from "./state-DcW6vOd_.mjs";
16
+ import { ft as parkedMaxSetting, ot as POOL_SETTING_REMEDY } from "./android-B_NumVyQ.mjs";
11
17
  import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
12
- import { at as POOL_SETTING_REMEDY, dt as parkedMaxSetting } from "./android-Db2eEMaf.mjs";
13
- import { l as resolveProjectMetro, o as pidExists, r as killMetroTree, u as signalProcessTree } from "./metro-Di8EN2Tb.mjs";
14
- import { B as unknownSettingKeys, L as settingShapeErrors, N as resolveSettings, a as SETTING_SHAPE_REMEDY } from "./settings-CduVcoxj.mjs";
15
- import { a as teardownOwnedBrowser, i as teardownOwnedAvd, o as teardownOwnedIosSim } from "./teardown-BYuQafiA.mjs";
16
- import { t as clearNamedPorts } from "./named-ports-Dj_XBNaL.mjs";
17
- import { o as readWebRecord } from "./state-DFm77wRn.mjs";
18
- import { n as withIdleWorkspace, t as emptyWorkspaceDir } from "./in-use-CndPMilN.mjs";
19
- import { g as withManagedRemoteWorktreeRemovalLock, l as withdrawDevServerStopRequest, p as stopTunnel, s as requestDevServerStop, u as resolveSupervisorTarget, v as withManagedTunnelRemovalLock } from "./stop-cause-CZ_Mr-v9.mjs";
20
- import { n as verifyCollectorOwnership } from "./ownership-BkYxUtuj.mjs";
21
- import { C as podInstallCommand, u as HEARTBEAT_INTERVAL_MS } from "./toolchain-DmeYjXD9.mjs";
22
- import { t as stopMacosApp } from "./stop-Bvf1MflS.mjs";
23
- import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-BFlKbOIp.mjs";
24
- import { f as resolveEasCliBin } from "./remote-cache-CO2elm_b.mjs";
25
- import { i as endRecordedSession } from "./device-remote-9ulPsdhY.mjs";
26
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-BmsjKZ2H.mjs";
27
- import { a as forgetStatusMeasures, t as startWarmProgress } from "./warm-progress-DVHLN4uC.mjs";
18
+ import { a as teardownOwnedBrowser, i as teardownOwnedAvd, o as teardownOwnedIosSim } from "./teardown-CL6vDs2Y.mjs";
19
+ import { t as clearNamedPorts } from "./named-ports-Bk_YXEmE.mjs";
20
+ import { i as stopTunnel, l as withManagedTunnelRemovalLock, s as withManagedRemoteWorktreeRemovalLock, t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
21
+ import { l as withdrawDevServerStopRequest, s as requestDevServerStop } from "./stop-cause-CidxIqup.mjs";
22
+ import { C as podInstallCommand, u as HEARTBEAT_INTERVAL_MS } from "./toolchain-DkUflduf.mjs";
23
+ import { n as verifyCollectorOwnership } from "./ownership-DiA16AsN.mjs";
24
+ import { t as stopMacosApp } from "./stop-B0Yxtmiy.mjs";
25
+ import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-urbmfXig.mjs";
26
+ import { f as resolveEasCliBin } from "./remote-cache-C8-V13XN.mjs";
27
+ import { i as endRecordedSession } from "./device-remote-D5WVWqOq.mjs";
28
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-B6cMXZab.mjs";
29
+ import { a as forgetStatusMeasures, s as mergeState, t as startWarmProgress } from "./warm-progress-CpGv3Wb6.mjs";
30
+ import { r as workspaceRecordingEnabled } from "./recordings-BIXOzVnr.mjs";
31
+ import { r as stopHostedIos } from "./hosted-ios-CIWelIqf.mjs";
28
32
  import { createHash, randomUUID } from "node:crypto";
29
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
30
- import { join, relative } from "node:path";
33
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
34
+ import { basename, dirname, join, relative } from "node:path";
31
35
  import { existsSync as existsSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1 } from "fs";
32
36
  import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "path";
33
- import { workspaceName } from "@stim-cli/core";
34
- import { LAST_BUILD_KEYS, readMacosRecord } from "@stim-cli/core/state";
37
+ import { withDirLock, workspaceName } from "@stim-cli/core";
38
+ import { LAST_BUILD_KEYS, archiveDir, archiveEnabled, archiveLock, archiveRoot, archivedUsage, countErrorEntries, endedAgentSessionOf, listRecordedDevices, loadConfig, queryLogs, readArchives, readBuildHistory, readJsonObject, readLastBuilds, readMacosRecord, readPullRequestCache, readWorkspaceState, recordingEnabled, sortByTs } from "@stim-cli/core/state";
39
+ import { claimRemoveCommand, clearFreeClaimSet, readClaimSet, releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
35
40
  import chalk from "chalk";
41
+ //#region src/archive.ts
42
+ const DAY = 864e5;
43
+ const KINDS = [
44
+ "logs",
45
+ "recordings",
46
+ "agentActions"
47
+ ];
48
+ const KIND_PATH = {
49
+ logs: "logs",
50
+ recordings: "recordings",
51
+ agentActions: "agent-device/sessions"
52
+ };
53
+ function archiveSettings() {
54
+ const config = loadConfig();
55
+ return Object.fromEntries([
56
+ "maxAgeDays",
57
+ "maxCount",
58
+ "maxTotalGb",
59
+ "logs.maxAgeDays",
60
+ "logs.maxMbPerWorkspace",
61
+ "recordings.maxAgeDays",
62
+ "recordings.maxTotalGb",
63
+ "agentActions.maxAgeDays"
64
+ ].map((key) => {
65
+ const result = machineNumber(`archive.${key}`, config, process.env);
66
+ if (result.error) throw new Error(result.error);
67
+ return [key, result.value];
68
+ }));
69
+ }
70
+ function enabled(root) {
71
+ const config = loadConfig();
72
+ const project = config?.projects?.[root];
73
+ const present = existsSync(root);
74
+ const common = present ? gitCommonDirOnDisk(root) : null;
75
+ if (!present && Object.values(config?.repos ?? {}).some((repo) => repo.settings?.archive?.enabled === false)) return false;
76
+ return archiveEnabled(process.env, [
77
+ project?.settings,
78
+ common ? config?.repos?.[common]?.settings : void 0,
79
+ present ? readCommittedSettings(root) : void 0,
80
+ config
81
+ ]);
82
+ }
83
+ function files(dir) {
84
+ if (!existsSync(dir)) return [];
85
+ return readdirSync(dir).flatMap((name) => {
86
+ const path = join(dir, name);
87
+ const stat = lstatSync(path);
88
+ if (stat.isSymbolicLink()) throw new Error(`refusing symlink ${path}`);
89
+ return stat.isDirectory() ? files(path) : [{
90
+ path,
91
+ bytes: stat.size,
92
+ at: stat.mtimeMs
93
+ }];
94
+ });
95
+ }
96
+ function secureTree(dir) {
97
+ if (lstatSync(dir).isSymbolicLink()) throw new Error(`refusing symlink ${dir}`);
98
+ chmodSync(dir, 448);
99
+ for (const name of readdirSync(dir)) {
100
+ const path = join(dir, name);
101
+ const stat = lstatSync(path);
102
+ if (stat.isSymbolicLink()) throw new Error(`refusing symlink ${path}`);
103
+ if (stat.isDirectory()) secureTree(path);
104
+ else chmodSync(path, 384);
105
+ }
106
+ }
107
+ function writeRecord(dir, record) {
108
+ const small = [
109
+ "state.json",
110
+ "ended-agents.json",
111
+ "log-error-index.json"
112
+ ].reduce((sum, name) => {
113
+ const path = join(dir, name);
114
+ return sum + (existsSync(path) ? statSync(path).size : 0);
115
+ }, 0);
116
+ for (let pass = 0; pass < 8; pass++) {
117
+ record.bytes.total = KINDS.reduce((sum, kind) => sum + record.bytes[kind], record.bytes.record);
118
+ const bytes = small + Buffer.byteLength(`${JSON.stringify(record)}\n`);
119
+ if (bytes === record.bytes.record) break;
120
+ record.bytes.record = bytes;
121
+ }
122
+ record.bytes.total = KINDS.reduce((sum, kind) => sum + record.bytes[kind], record.bytes.record);
123
+ const tmp = join(dir, `.archive-${randomUUID()}.tmp`);
124
+ try {
125
+ writeFileSync(tmp, `${JSON.stringify(record)}\n`, { mode: 384 });
126
+ renameSync(tmp, join(dir, "archive.json"));
127
+ } finally {
128
+ rmSync(tmp, { force: true });
129
+ }
130
+ const now = /* @__PURE__ */ new Date();
131
+ utimesSync(archiveRoot(), now, now);
132
+ }
133
+ function removeRecord(id) {
134
+ const dir = archiveDir(id);
135
+ if (!existsSync(dir)) return;
136
+ const removing = join(archiveRoot(), `.removing-${id}`);
137
+ renameSync(dir, removing);
138
+ rmSync(removing, {
139
+ recursive: true,
140
+ force: true
141
+ });
142
+ }
143
+ function sweepArchiveStaging(remove, waitMs = 0) {
144
+ if (!existsSync(archiveRoot())) return [];
145
+ return withDirLock(archiveLock(), () => readdirSync(archiveRoot()).filter((name) => name.startsWith(".incoming-") && (!name.endsWith(".claims") || !existsSync(join(archiveRoot(), name.slice(0, -7)))) || name.startsWith(".removing-")).flatMap((name) => {
146
+ const path = join(archiveRoot(), name.endsWith(".claims") ? name.slice(0, -7) : name);
147
+ const root = `${path}.claims`;
148
+ const survey = name.startsWith(".incoming-") ? readClaimSet(root) : null;
149
+ const unresolved = survey?.unresolved[0];
150
+ const kept = Boolean(survey && (survey.live.length || survey.unresolved.length));
151
+ const entry = {
152
+ path,
153
+ kept,
154
+ reason: unresolved?.reason ?? (kept ? "live archive writer" : null),
155
+ removeCommand: unresolved ? claimRemoveCommand(root) : null
156
+ };
157
+ if (remove && !kept) {
158
+ if (!survey || clearFreeClaimSet({
159
+ root,
160
+ label: "archive staging"
161
+ }).status === "cleared") {
162
+ rmSync(path, {
163
+ recursive: true,
164
+ force: true
165
+ });
166
+ return [];
167
+ }
168
+ }
169
+ return [entry];
170
+ }), { waitMs });
171
+ }
172
+ function stripPids(value) {
173
+ if (Array.isArray(value)) return value.map(stripPids);
174
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).filter(([key]) => !/pids?$/i.test(key)).map(([key, child]) => [key, stripPids(child)]));
175
+ return value;
176
+ }
177
+ function gitFacts(root) {
178
+ const empty = {
179
+ repository: null,
180
+ branch: null,
181
+ head: null,
182
+ subject: null,
183
+ merged: null,
184
+ pullRequest: null
185
+ };
186
+ if (!existsSync(root)) return empty;
187
+ const exec = getExecutor();
188
+ const run = (args) => exec.runFileQuiet("git", [
189
+ "-C",
190
+ root,
191
+ ...args
192
+ ], { timeoutMs: 1e3 });
193
+ const common = gitCommonDirOnDisk(root);
194
+ const summary = run([
195
+ "show",
196
+ "-s",
197
+ "--format=%H%n%s",
198
+ "HEAD"
199
+ ])?.split("\n");
200
+ const location = run([
201
+ "rev-parse",
202
+ "--show-toplevel",
203
+ "--abbrev-ref",
204
+ "HEAD"
205
+ ])?.split("\n");
206
+ const branch = location?.[1] && location[1] !== "HEAD" ? location[1] : null;
207
+ const defaultRef = run([
208
+ "symbolic-ref",
209
+ "--quiet",
210
+ "refs/remotes/origin/HEAD"
211
+ ]);
212
+ let merged = null;
213
+ if (defaultRef?.startsWith("refs/remotes/")) {
214
+ const state = mergeState(root, {
215
+ ref: defaultRef,
216
+ name: defaultRef.slice(13)
217
+ }, { timeoutMs: 2e3 });
218
+ merged = state.merged ? true : state.unknown ? null : false;
219
+ }
220
+ let prPath = root;
221
+ try {
222
+ if (location?.[0]) prPath = realpathSync(location[0]);
223
+ } catch {}
224
+ const pr = readPullRequestCache(prPath)?.pullRequest;
225
+ if (pr?.state === "merged") merged = true;
226
+ return {
227
+ repository: common ? basename(common) === ".git" ? dirname(common) : common : null,
228
+ branch,
229
+ head: summary?.[0] ?? null,
230
+ subject: summary?.[1] ?? null,
231
+ merged,
232
+ pullRequest: pr ? {
233
+ number: pr.number,
234
+ state: pr.state,
235
+ title: pr.title,
236
+ url: pr.url
237
+ } : null
238
+ };
239
+ }
240
+ function archiveWorkspace(root, removedBy, state = readWorkspaceState(root)) {
241
+ writeArchive(root, removedBy, state);
242
+ try {
243
+ enforceArchiveRetention();
244
+ } catch (error) {
245
+ console.error(`could not enforce archive retention: ${String(error?.message ?? error).split("\n")[0]}`);
246
+ }
247
+ }
248
+ function writeArchive(root, removedBy, state) {
249
+ let staging = null;
250
+ let claim;
251
+ try {
252
+ const dir = workspaceDir(root);
253
+ if (!existsSync(dir) || lstatSync(dir).isSymbolicLink()) return;
254
+ if (existsSync(root)) root = realpathSync(root);
255
+ try {
256
+ sweepArchiveStaging(true, 5e3);
257
+ } catch {}
258
+ if (!enabled(root)) return;
259
+ const limits = archiveSettings();
260
+ if (!limits.maxAgeDays || !limits.maxCount) return;
261
+ const logs = join(dir, "logs");
262
+ if (!existsSync(join(dir, "state.json")) && !Object.values(readBuildHistory(state)).some((entries) => entries?.length) && (!existsSync(logs) || readdirSync(logs).length === 0)) return;
263
+ const worktree = gitFacts(root);
264
+ mkdirSync(archiveRoot(), {
265
+ recursive: true,
266
+ mode: 448
267
+ });
268
+ chmodSync(archiveRoot(), 448);
269
+ const token = randomUUID();
270
+ staging = join(archiveRoot(), `.incoming-${token}`);
271
+ const attempt = tryAcquireClaim({
272
+ root: `${staging}.claims`,
273
+ mode: "exclusive",
274
+ label: "archive staging"
275
+ });
276
+ claim = attempt.acquired;
277
+ if (!claim) {
278
+ releaseClaim(attempt.pending);
279
+ throw new Error("archive staging claim is held");
280
+ }
281
+ withDirLock(archiveLock(), () => {
282
+ mkdirSync(staging, { mode: 448 });
283
+ let at = Date.now();
284
+ const workspace = workspaceName(root);
285
+ while (existsSync(archiveDir(`${workspace}--${at}`))) at++;
286
+ const id = `${workspace}--${at}`;
287
+ if (state) {
288
+ const { supervisor: _supervisor, collectors: _collectors, warm: _warm, ...stable } = state;
289
+ writeFileSync(join(staging, "state.json"), `${JSON.stringify(stripPids(stable))}\n`, { mode: 384 });
290
+ }
291
+ for (const name of ["ended-agents.json", "log-error-index.json"]) {
292
+ const data = readJsonObject(join(dir, name));
293
+ if (data) writeFileSync(join(staging, name), `${JSON.stringify(stripPids(data))}\n`, { mode: 384 });
294
+ }
295
+ const move = (kind) => {
296
+ const from = join(dir, KIND_PATH[kind]);
297
+ if (!limits[`${kind}.maxAgeDays`] || !existsSync(from)) return;
298
+ const to = join(staging, KIND_PATH[kind]);
299
+ mkdirSync(join(to, ".."), {
300
+ recursive: true,
301
+ mode: 448
302
+ });
303
+ renameSync(from, to);
304
+ secureTree(to);
305
+ };
306
+ move("logs");
307
+ move("agentActions");
308
+ const config = loadConfig();
309
+ if (limits["recordings.maxAgeDays"] && workspaceRecordingEnabled(root, config?.projects?.[root] ?? {}, config, process.env)) {
310
+ const recordingDir = join(dir, "recordings");
311
+ if (existsSync(recordingDir) && lstatSync(recordingDir).isSymbolicLink()) throw new Error(`refusing symlink ${recordingDir}`);
312
+ for (const device of listRecordedDevices(recordingDir)) {
313
+ if (lstatSync(device.dir).isSymbolicLink()) throw new Error(`refusing symlink ${device.dir}`);
314
+ const to = join(staging, "recordings", `${device.platform}-${device.slot}`);
315
+ for (const segment of device.segments.filter((entry) => !entry.open)) {
316
+ mkdirSync(to, {
317
+ recursive: true,
318
+ mode: 448
319
+ });
320
+ try {
321
+ if (lstatSync(segment.file).isSymbolicLink()) throw new Error(`refusing symlink ${segment.file}`);
322
+ renameSync(segment.file, join(to, basename(segment.file)));
323
+ chmodSync(join(to, basename(segment.file)), 384);
324
+ } catch (error) {
325
+ if (error.code !== "ENOENT") throw error;
326
+ }
327
+ }
328
+ }
329
+ }
330
+ const index = readJsonObject(join(staging, "log-error-index.json"));
331
+ const indexed = Object.values(index?.files ?? {}).flatMap((file) => Object.values(file.markers ?? {}).concat(file.errors ?? []));
332
+ const lastErrorCount = countErrorEntries(sortByTs(queryLogs({
333
+ records: indexed,
334
+ errorsOnly: true
335
+ })));
336
+ const history = Object.values(readBuildHistory(state)).flat();
337
+ const last = Object.values(readLastBuilds(state)).toSorted((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt))[0] ?? null;
338
+ const bytes = {
339
+ logs: 0,
340
+ recordings: 0,
341
+ agentActions: 0,
342
+ record: 0,
343
+ total: 0
344
+ };
345
+ const expires = {
346
+ logs: null,
347
+ recordings: null,
348
+ agentActions: null,
349
+ record: new Date(at + limits.maxAgeDays * DAY).toISOString()
350
+ };
351
+ for (const kind of KINDS) {
352
+ bytes[kind] = files(join(staging, KIND_PATH[kind])).reduce((sum, file) => sum + file.bytes, 0);
353
+ if (bytes[kind]) expires[kind] = new Date(at + limits[`${kind}.maxAgeDays`] * DAY).toISOString();
354
+ }
355
+ const record = {
356
+ id,
357
+ projectRoot: root,
358
+ project: basename(root),
359
+ workspace,
360
+ worktree,
361
+ removedAt: new Date(at).toISOString(),
362
+ removedBy,
363
+ lastUsedAt: typeof state?.lastUsedAt === "string" ? state.lastUsedAt : null,
364
+ builds: {
365
+ count: history.length,
366
+ last,
367
+ lastErrorCount
368
+ },
369
+ agents: (readJsonObject(join(dir, "ended-agents.json"))?.sessions ?? []).map(endedAgentSessionOf).filter((session) => session !== null),
370
+ bytes,
371
+ expires,
372
+ version: 1
373
+ };
374
+ writeRecord(staging, record);
375
+ renameSync(staging, archiveDir(id));
376
+ }, { waitMs: 3e4 });
377
+ releaseClaim(claim);
378
+ claim = void 0;
379
+ staging = null;
380
+ } catch (error) {
381
+ if (staging) try {
382
+ rmSync(staging, {
383
+ recursive: true,
384
+ force: true
385
+ });
386
+ } catch {}
387
+ console.error(`could not archive: ${String(error?.message ?? error).split("\n")[0]}`);
388
+ } finally {
389
+ releaseClaim(claim);
390
+ }
391
+ }
392
+ function removeKind(record, kind) {
393
+ rmSync(join(archiveDir(record.id), KIND_PATH[kind]), {
394
+ recursive: true,
395
+ force: true
396
+ });
397
+ record.bytes[kind] = 0;
398
+ record.expires[kind] = null;
399
+ writeRecord(archiveDir(record.id), record);
400
+ }
401
+ function logRemovalOrder(path) {
402
+ return /\.ndjson\.\d+$/.test(path) ? 0 : /^build-.*\.ndjson$/.test(basename(path)) ? 1 : 2;
403
+ }
404
+ function enforceArchiveRetention(now = Date.now()) {
405
+ if (!existsSync(archiveRoot())) return;
406
+ let limits;
407
+ try {
408
+ limits = archiveSettings();
409
+ } catch (error) {
410
+ console.error(`could not enforce archive retention: ${String(error.message).split("\n")[0]}`);
411
+ return;
412
+ }
413
+ withDirLock(archiveLock(), () => {
414
+ const listed = readArchives().toReversed();
415
+ const records = listed.filter((record, index) => limits.maxAgeDays > 0 && now - Date.parse(record.removedAt) < limits.maxAgeDays * DAY && index >= listed.length - limits.maxCount);
416
+ for (const record of listed) if (!records.includes(record)) removeRecord(record.id);
417
+ const recordings = recordingEnabled(process.env, [loadConfig()]);
418
+ for (const record of records) {
419
+ for (const kind of [
420
+ "recordings",
421
+ "agentActions",
422
+ "logs"
423
+ ]) {
424
+ const age = limits[`${kind}.maxAgeDays`];
425
+ if (record.bytes[kind] && (!age || now - Date.parse(record.removedAt) >= age * DAY || kind === "recordings" && !recordings)) removeKind(record, kind);
426
+ }
427
+ const cap = limits["logs.maxMbPerWorkspace"] * 1024 ** 2;
428
+ if (record.bytes.logs > cap) {
429
+ const entries = files(join(archiveDir(record.id), "logs")).toSorted((a, b) => logRemovalOrder(a.path) - logRemovalOrder(b.path) || (logRemovalOrder(a.path) === 0 ? Number(b.path.split(".").at(-1)) - Number(a.path.split(".").at(-1)) : 0) || a.at - b.at);
430
+ for (const file of entries) {
431
+ if (record.bytes.logs <= cap) break;
432
+ rmSync(file.path);
433
+ record.bytes.logs = Math.max(0, record.bytes.logs - file.bytes);
434
+ }
435
+ if (!record.bytes.logs) record.expires.logs = null;
436
+ writeRecord(archiveDir(record.id), record);
437
+ }
438
+ }
439
+ const totalCap = limits.maxTotalGb * 1024 ** 3;
440
+ const recordingCap = limits["recordings.maxTotalGb"] * 1024 ** 3;
441
+ for (const record of records) for (;;) {
442
+ const usage = archivedUsage(records);
443
+ const totalOver = usage.bytes > totalCap;
444
+ const recordingsOver = usage.byKind.recordings > recordingCap;
445
+ if (!totalOver && !recordingsOver) break;
446
+ const candidates = KINDS.filter((kind) => record.bytes[kind] > 0 && (totalOver || kind === "recordings")).toSorted((a, b) => record.bytes[b] - record.bytes[a]);
447
+ if (!candidates.length) break;
448
+ removeKind(record, candidates[0]);
449
+ }
450
+ for (const record of records) {
451
+ const before = JSON.stringify(record.expires);
452
+ for (const kind of KINDS) record.expires[kind] = record.bytes[kind] ? new Date(Date.parse(record.removedAt) + limits[`${kind}.maxAgeDays`] * DAY).toISOString() : null;
453
+ record.expires.record = new Date(Date.parse(record.removedAt) + limits.maxAgeDays * DAY).toISOString();
454
+ if (JSON.stringify(record.expires) !== before) writeRecord(archiveDir(record.id), record);
455
+ }
456
+ }, { waitMs: 3e4 });
457
+ }
458
+ function deleteSelectedArchives(ids, kind) {
459
+ withDirLock(archiveLock(), () => {
460
+ for (const record of readArchives().filter((entry) => ids.includes(entry.id))) if (kind) removeKind(record, kind);
461
+ else removeRecord(record.id);
462
+ }, { waitMs: 3e4 });
463
+ }
464
+ //#endregion
36
465
  //#region src/devices/reclaim.ts
37
466
  async function reapCollectors(root, { verify = verifyCollectorOwnership, collectors }) {
38
467
  const skippedDevices = [];
@@ -288,6 +717,7 @@ async function reclaimProject(path, options = {}) {
288
717
  const existed = existsSync(dir);
289
718
  const run = await withIdleWorkspace(path, () => reclaimIdleProject(path, existed, options), {
290
719
  purpose: "workspace removal",
720
+ hosted: false,
291
721
  supervisor: false,
292
722
  managedLocks: false
293
723
  });
@@ -316,7 +746,7 @@ async function reclaimProject(path, options = {}) {
316
746
  failedWorkspaceDirs: []
317
747
  };
318
748
  }
319
- async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices = false, parkOwnedDevices = false, preserveProjectRecord = false, stopSession = defaultStopSession, stopMetroTunnel = defaultStopMetroTunnel, releaseLeases = releaseWorkspaceLeases, verifyCollector = verifyCollectorOwnership, teardownBrowser = teardownOwnedBrowser, stopRequester = { by: "stim gc --delete" } }) {
749
+ async function reclaimIdleProject(path, workspaceExisted, { archive, deleteOwnedDevices = false, parkOwnedDevices = false, preserveProjectRecord = false, stopSession = defaultStopSession, stopMetroTunnel = defaultStopMetroTunnel, releaseLeases = releaseWorkspaceLeases, verifyCollector = verifyCollectorOwnership, teardownBrowser = teardownOwnedBrowser, stopRequester = { by: "stim gc --delete" } }) {
320
750
  await clearNamedPorts(path, { stop: true });
321
751
  const project = getProject(path);
322
752
  for (const { platforms } of projectDeviceSlots(project)) if (platforms.android?.owned && platforms.android.avdName) (0, ownership_claim_exports.releaseClaim)(acquireAvdClaim(platforms.android.avdName));
@@ -395,6 +825,17 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
395
825
  };
396
826
  skippedDevices.push(...skippedCollectors);
397
827
  failedDevices.push(...failedCollectors);
828
+ try {
829
+ await stopHostedIos(path);
830
+ } catch (error) {
831
+ const failed = {
832
+ platform: "ios",
833
+ name: "hosted iOS simulator",
834
+ reason: error.message
835
+ };
836
+ failedDevices.push(failed);
837
+ skippedDevices.push(failed);
838
+ }
398
839
  const remote = reclaimRemoteSession(path, { stopSession });
399
840
  if (remote.failed) {
400
841
  skippedDevices.push(remote.failed);
@@ -428,6 +869,28 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
428
869
  if (failedDevices.length === 0 && !supervisorHeld) {
429
870
  const dir = workspaceDir(path);
430
871
  if (existsSync(dir)) try {
872
+ const stateDir = workspaceAgentDeviceDir(path);
873
+ if (!["none", "dead"].includes(agentDeviceDaemon(stateDir))) {
874
+ const exec = getExecutor();
875
+ try {
876
+ if (!exec.findExecutable("agent-device")) throw new Error("agent-device is not on PATH");
877
+ exec.runFile("agent-device", [
878
+ "daemon",
879
+ "stop",
880
+ "--state-dir",
881
+ stateDir,
882
+ "--clean"
883
+ ], {
884
+ timeoutMs: 2e4,
885
+ killSignal: "SIGKILL",
886
+ env: { AGENT_DEVICE_NO_UPDATE_NOTIFIER: "1" }
887
+ });
888
+ } catch (error) {
889
+ process.stderr.write(`${phaseLine("agent-device", `daemon could not be stopped: ${error.message}; run agent-device daemon stop --state-dir "${stateDir}" --clean`)}\n`);
890
+ throw error;
891
+ }
892
+ }
893
+ if (archive) archiveWorkspace(path, archive.removedBy, initialState);
431
894
  emptyWorkspaceDir(dir);
432
895
  if (workspaceExisted) removedWorkspaceDirs.push(dir);
433
896
  } catch {
@@ -1492,7 +1955,7 @@ async function withReclaimLocks(rootPath, fn) {
1492
1955
  const acquire = (index) => index === keys.length ? fn(keys) : withManagedTunnelRemovalLock(keys[index], () => acquire(index + 1));
1493
1956
  return acquire(0);
1494
1957
  }
1495
- async function reclaimAll(rootPath, keys = reclaimKeys(rootPath), { preserveRootProject = false } = {}) {
1958
+ async function reclaimAll(rootPath, keys = reclaimKeys(rootPath), { preserveRootProject = false, removedBy } = {}) {
1496
1959
  const dereferenced = [];
1497
1960
  const killedPids = [];
1498
1961
  const deletedDevices = [];
@@ -1511,6 +1974,7 @@ async function reclaimAll(rootPath, keys = reclaimKeys(rootPath), { preserveRoot
1511
1974
  let r;
1512
1975
  try {
1513
1976
  r = await reclaimProject(key, {
1977
+ archive: removedBy ? { removedBy } : void 0,
1514
1978
  deleteOwnedDevices: true,
1515
1979
  parkOwnedDevices: true,
1516
1980
  preserveProjectRecord: preserveRootProject && key === rootPath,
@@ -1835,7 +2299,10 @@ async function runRemove(target, opts, onRemoved) {
1835
2299
  printRemovalRefusal(path, current);
1836
2300
  return;
1837
2301
  }
1838
- const result = await reclaimAll(path, lockedKeys, { preserveRootProject: true });
2302
+ const result = await reclaimAll(path, lockedKeys, {
2303
+ preserveRootProject: true,
2304
+ removedBy: opts.removedBy ?? "worktree-remove"
2305
+ });
1839
2306
  if (result.keptEntries.length) {
1840
2307
  reportRetainedResources(path, result);
1841
2308
  return;
@@ -1914,4 +2381,4 @@ function worktreeCommand(program) {
1914
2381
  registerRemove(worktree);
1915
2382
  }
1916
2383
  //#endregion
1917
- export { porcelainPath as a, registerWarm as c, removalRemedy as d, removeWorktreeTarget as f, reclaimProject as g, describeDereferenced as h, matchWorktreeEntry as i, removalBlockers as l, worktreeCommand as m, excludePodChurn as n, reclaimKeys as o, warmedWorkspace as p, excludeWatchmanCookies as r, registerRemove as s, dependencyInstallCommand as t, removalPath as u };
2384
+ export { deleteSelectedArchives as _, porcelainPath as a, registerWarm as c, removalRemedy as d, removeWorktreeTarget as f, reclaimProject as g, describeDereferenced as h, matchWorktreeEntry as i, removalBlockers as l, worktreeCommand as m, excludePodChurn as n, reclaimKeys as o, warmedWorkspace as p, excludeWatchmanCookies as r, registerRemove as s, dependencyInstallCommand as t, removalPath as u, sweepArchiveStaging as v };
@@ -1,2 +1,2 @@
1
- import { a as porcelainPath, c as registerWarm, d as removalRemedy, f as removeWorktreeTarget, i as matchWorktreeEntry, l as removalBlockers, m as worktreeCommand, n as excludePodChurn, o as reclaimKeys, p as warmedWorkspace, r as excludeWatchmanCookies, s as registerRemove, t as dependencyInstallCommand, u as removalPath } from "./worktree-qXKzADSC.mjs";
1
+ import { a as porcelainPath, c as registerWarm, d as removalRemedy, f as removeWorktreeTarget, i as matchWorktreeEntry, l as removalBlockers, m as worktreeCommand, n as excludePodChurn, o as reclaimKeys, p as warmedWorkspace, r as excludeWatchmanCookies, s as registerRemove, t as dependencyInstallCommand, u as removalPath } from "./worktree-K-xj9Db0.mjs";
2
2
  export { worktreeCommand as default, dependencyInstallCommand, excludePodChurn, excludeWatchmanCookies, matchWorktreeEntry, porcelainPath, reclaimKeys, registerRemove, registerWarm, removalBlockers, removalPath, removalRemedy, removeWorktreeTarget, warmedWorkspace };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stim",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Stim: fast, isolated React Native environments for coding agents",
5
5
  "keywords": [
6
6
  "agent",
@@ -48,9 +48,9 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@expo/fingerprint": "^0.20.10",
51
- "@stim-cli/cache": "^1.15.0",
52
- "@stim-cli/core": "^1.15.0",
53
- "@stim-cli/metro": "^1.15.0",
51
+ "@stim-cli/cache": "^1.16.0",
52
+ "@stim-cli/core": "^1.16.0",
53
+ "@stim-cli/metro": "^1.16.0",
54
54
  "chalk": "^6.0.0",
55
55
  "commander": "^15.0.0",
56
56
  "cross-spawn": "^7.0.6",
@@ -58,7 +58,7 @@
58
58
  "ws": "^8.21.3"
59
59
  },
60
60
  "devDependencies": {
61
- "@stim-cli/expo-build-cache": "1.15.0",
61
+ "@stim-cli/expo-build-cache": "1.16.0",
62
62
  "@types/cross-spawn": "^6.0.6",
63
63
  "@types/which": "^3.0.4",
64
64
  "@types/ws": "^8.18.1",