stim 1.1.0 → 1.2.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 (59) hide show
  1. package/README.md +7 -0
  2. package/dist/{android-CC5bUer7.mjs → android-BW8i_YxU.mjs} +1 -1
  3. package/dist/{android-DnZlT3aU.mjs → android-Bcmev2s2.mjs} +86 -31
  4. package/dist/{android-xmyGBNy5.mjs → android-K63_0UfF.mjs} +77 -15
  5. package/dist/{android-cas-DGlTStWu.mjs → android-cas-D6HPZ6UQ.mjs} +6 -6
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install-oZIQRbdD.mjs → app-install-NFxUahnH.mjs} +25 -17
  8. package/dist/{build-lock-vE45ZzJZ.mjs → build-lock-BsVoLDaq.mjs} +6 -6
  9. package/dist/{build-slots-CgacA9zQ.mjs → build-slots-D0b6sJyo.mjs} +3 -3
  10. package/dist/{cache-manifest-oeH18ERr.mjs → cache-manifest-Cu4_Znw9.mjs} +1 -1
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +13 -13
  13. package/dist/collector-run.d.mts +1 -0
  14. package/dist/collector-run.mjs +6 -6
  15. package/dist/{config-D7uu8Gbq.mjs → config-CMtIaTk9.mjs} +1 -1
  16. package/dist/{deps-64wVnpZc.mjs → deps-BLBsTNDS.mjs} +2 -2
  17. package/dist/{dev-client-Bvky1hya.mjs → dev-client-RpvXxCXV.mjs} +72 -9
  18. package/dist/{device-DJKv5Qej.mjs → device-CoCPmazS.mjs} +5 -5
  19. package/dist/{device-lease-D-G3SgVe.mjs → device-lease-Dpik41RY.mjs} +3 -3
  20. package/dist/{device-pool-BlHXnYYs.mjs → device-pool-B5pEVa3t.mjs} +3 -3
  21. package/dist/{device-remote-CCgkG82J.mjs → device-remote-E81KAdXf.mjs} +8 -8
  22. package/dist/{doctor-DlVxTtMq.mjs → doctor-CqPeMsSm.mjs} +19 -26
  23. package/dist/{doctor-BRF9aAJ3.mjs → doctor-D_CX7ib5.mjs} +10 -10
  24. package/dist/{error-diagnostics-B5IJwv3-.mjs → error-diagnostics-Dw8Gn3yw.mjs} +8 -8
  25. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  26. package/dist/{gc-DhXdgSdt.mjs → gc-B7KkDinF.mjs} +39 -25
  27. package/dist/{guide-DrhrDBo5.mjs → guide-BS2Xc24o.mjs} +220 -36
  28. package/dist/{ios-CK1Vrk6Q.mjs → ios-B7KLgIp5.mjs} +38 -26
  29. package/dist/{ios-BZxKPuqP.mjs → ios-D1h1Di39.mjs} +79 -10
  30. package/dist/{ios-device-CYbuRNXR.mjs → ios-device-D3pzTKQE.mjs} +1 -1
  31. package/dist/{ios-device-CdSm0GDw.mjs → ios-device-xNt0Lg6v.mjs} +2 -2
  32. package/dist/{logs-Dxoy16Qv.mjs → logs-DcX9AvRF.mjs} +3 -3
  33. package/dist/{metro-B01H3EuD.mjs → metro-COslRK_F.mjs} +3 -3
  34. package/dist/{ownership-D8HmSv2E.mjs → ownership-CGPart6T.mjs} +3 -3
  35. package/dist/{ownership-B9QIdQOw.mjs → ownership-CYOyqxUo.mjs} +4 -4
  36. package/dist/{ownership-claim-CeJLEpjD.mjs → ownership-claim-7zRxxTfZ.mjs} +1 -1
  37. package/dist/{project-COj6nzzh.mjs → project-BBTVABUz.mjs} +2 -2
  38. package/dist/{reclaim-BPvN2HlP.mjs → reclaim-C5Jmuahx.mjs} +9 -8
  39. package/dist/{reload-DYZEBbZy.mjs → reload-Z8nKifAk.mjs} +7 -7
  40. package/dist/{remote-cache-DOi5-zLj.mjs → remote-cache-DDmJ1Roq.mjs} +73 -13
  41. package/dist/{server-bare-BYHqEeyw.mjs → server-bare-DEk37Mz2.mjs} +7 -2
  42. package/dist/{server-expo-BEMr_m6p.mjs → server-expo-C3ktAwUG.mjs} +4 -4
  43. package/dist/server-expo-Drd7ksHu.mjs +2 -0
  44. package/dist/{settings-BSwrY4FZ.mjs → settings-DwU1_U_q.mjs} +26 -5
  45. package/dist/{start-ByCylYtB.mjs → start-BrY4EuIg.mjs} +10 -10
  46. package/dist/{state-IYEY-zi8.mjs → state-CZ-ER-8d.mjs} +1 -1
  47. package/dist/{stats-wqxPm7Tv.mjs → stats-BCWEVLrM.mjs} +1 -1
  48. package/dist/{stats-DZ4Q5J-7.mjs → stats-BsF15Bti.mjs} +3 -3
  49. package/dist/{status-Dia0XTqG.mjs → status-DFI1Hfpg.mjs} +46 -16
  50. package/dist/{stop-D6o5zAdm.mjs → stop-B7DR4awK.mjs} +2 -2
  51. package/dist/{stop-BApyaF21.mjs → stop-Btg770vV.mjs} +9 -9
  52. package/dist/supervisor-run.mjs +4 -4
  53. package/dist/{workspace-process-lock-CxOO9-pf.mjs → workspace-process-lock-BKfdys1q.mjs} +1 -1
  54. package/dist/{worktree-DxEmUI_n.mjs → worktree-BPTbI-LN.mjs} +16 -16
  55. package/dist/{worktree-Dx3ZC5lS.mjs → worktree-C79xx_kM.mjs} +7 -3
  56. package/dist/{xcode-CDB6fytw.mjs → xcode-DeKCribl.mjs} +190 -63
  57. package/package.json +5 -5
  58. package/shim/bundle-response.cjs +5 -0
  59. package/dist/server-expo-rb0k-0w7.mjs +0 -2
@@ -1,16 +1,17 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { P as volumeRootFor, g as loadConfig, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
3
- import { D as workspaceLogsDir } from "./state-IYEY-zi8.mjs";
4
- import { f as listWorktrees } from "./worktree-Dx3ZC5lS.mjs";
5
- import { a as findProjectRoot, c as projectShortcut } from "./project-COj6nzzh.mjs";
6
- import { a as isMetroRunning, i as resolveProjectMetro } from "./metro-B01H3EuD.mjs";
7
- import { S as readParked, v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DOi5-zLj.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { P as volumeRootFor, g as loadConfig, u as getConfigDir } from "./config-CMtIaTk9.mjs";
3
+ import { D as workspaceLogsDir } from "./state-CZ-ER-8d.mjs";
4
+ import { f as listWorktrees } from "./worktree-C79xx_kM.mjs";
5
+ import { a as findProjectRoot, c as projectShortcut } from "./project-BBTVABUz.mjs";
6
+ import { a as isMetroRunning, i as resolveProjectMetro } from "./metro-COslRK_F.mjs";
7
+ import { S as readParked, v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DDmJ1Roq.mjs";
8
8
  import { a as formatElapsed, r as clockTime, u as plural } from "./command-output-8wHMEde0.mjs";
9
- import { l as listAllIosSims } from "./ios-BZxKPuqP.mjs";
10
- import { i as listLeaseFiles } from "./device-lease-D-G3SgVe.mjs";
9
+ import { u as listAllIosSims } from "./ios-D1h1Di39.mjs";
10
+ import { i as listLeaseFiles } from "./device-lease-Dpik41RY.mjs";
11
+ import { N as resolveOwnedAvdSerial } from "./android-K63_0UfF.mjs";
11
12
  import { s as queryLogs } from "./logs-query-oqn47Ffx.mjs";
12
- import { t as resolveSupervisorTarget } from "./ownership-B9QIdQOw.mjs";
13
- import { i as readSupervisorState } from "./stop-BApyaF21.mjs";
13
+ import { t as resolveSupervisorTarget } from "./ownership-CYOyqxUo.mjs";
14
+ import { i as readSupervisorState } from "./stop-Btg770vV.mjs";
14
15
  import { existsSync } from "fs";
15
16
  import { totalmem } from "os";
16
17
  import chalk from "chalk";
@@ -23,21 +24,29 @@ function poolLine({ platform, parked, max }) {
23
24
  const what = plural(parked, `parked ${platform === "ios" ? "iOS simulator" : "Android emulator"}`);
24
25
  return max > 0 ? `pool: ${what} (max ${max})` : `pool: ${what} (parking off; gc --delete removes them)`;
25
26
  }
26
- function environmentState(project, { simsByUdid = {}, metro = null, worktrees = [], simsAvailable = true, supervisor = null, logs = null } = {}) {
27
+ function environmentState(project, { simsByUdid = {}, metro = null, worktrees = [], simsAvailable = true, androidRuntime = null, supervisor = null, logs = null } = {}) {
27
28
  const ios = project.platforms?.ios;
28
29
  const android = project.platforms?.android;
29
30
  const sim = ios ? simsByUdid[ios.deviceUdid] : null;
30
31
  const simBooted = Boolean(sim && sim.state === "Booted");
31
32
  const metroRunning = Boolean(metro?.metro);
32
- const live = simBooted || metroRunning || Boolean(android?.serial);
33
+ const androidDetected = androidRuntime ? Boolean(androidRuntime.serial) : Boolean(android?.serial);
34
+ const live = simBooted || metroRunning || androidDetected;
33
35
  let memoryMb = 0;
34
36
  if (simBooted) memoryMb += IOS_SIM_MB;
35
- if (android?.serial) memoryMb += ANDROID_EMULATOR_MB;
37
+ if (androidDetected) memoryMb += ANDROID_EMULATOR_MB;
36
38
  if (metroRunning) memoryMb += METRO_MB;
37
39
  const warnings = [];
38
40
  if (metro?.notOurs) warnings.push(`port ${project.metroPort}: ${metro.notOurs}`);
39
41
  if (ios && !sim && simsAvailable) warnings.push(`recorded sim ${ios.deviceUdid} no longer exists`);
40
42
  if (simBooted && project.metroPort && !metroRunning) warnings.push("simulator is booted with no Metro serving it");
43
+ if (androidRuntime && android?.avdName) {
44
+ const recordedSerial = android.consolePort ? `emulator-${android.consolePort}` : android.serial;
45
+ if (androidRuntime.serial && recordedSerial && androidRuntime.serial !== recordedSerial) warnings.push(`owned AVD ${android.avdName} changed serial (${recordedSerial} -> ${androidRuntime.serial}); rerun your \`stim android\` command in this workspace to restore Metro forwarding, then reopen agent-device on ${androidRuntime.serial}`);
46
+ if (androidRuntime.state === "missing") warnings.push(`recorded AVD ${android.avdName} no longer exists`);
47
+ if (androidRuntime.state === "not-detected") warnings.push(`owned AVD ${android.avdName} is not detected by adb; rerun your \`stim android\` command in this workspace to reconnect`);
48
+ if (androidRuntime.error) warnings.push(`could not check owned AVD ${android.avdName}: ${androidRuntime.error}`);
49
+ }
41
50
  if (supervisor && supervisor.alive === false) warnings.push(`stale supervisor record for ${project.__path}`);
42
51
  return {
43
52
  path: project.__path,
@@ -53,7 +62,11 @@ function environmentState(project, { simsByUdid = {}, metro = null, worktrees =
53
62
  android: android ? {
54
63
  name: android.avdName ?? android.serial,
55
64
  owned: Boolean(android.owned),
56
- physical: Boolean(android.serial && !android.avdName)
65
+ physical: Boolean(android.serial && !android.avdName),
66
+ ...androidRuntime ? {
67
+ serial: androidRuntime.serial,
68
+ state: androidRuntime.state
69
+ } : {}
57
70
  } : null,
58
71
  metro: project.metroPort ? {
59
72
  port: project.metroPort,
@@ -191,6 +204,7 @@ function statusCommand(program) {
191
204
  metro,
192
205
  worktrees,
193
206
  simsAvailable,
207
+ androidRuntime: proj.platforms?.android?.owned && proj.platforms.android.avdName ? readAndroidRuntime(proj.platforms.android.avdName) : null,
194
208
  supervisor,
195
209
  logs: logFacts(path)
196
210
  }));
@@ -267,7 +281,8 @@ function statusCommand(program) {
267
281
  }
268
282
  if (state.android) {
269
283
  const kind = state.android.physical ? chalk.dim("(physical)") : chalk.dim("(emulator)");
270
- console.log(` android: ${chalk.cyan(state.android.name)} ${kind}${state.android.owned ? chalk.dim(" (owned)") : ""}`);
284
+ const observed = state.android.state ? ` ${state.android.state}${state.android.serial ? ` (${state.android.serial})` : ""}` : "";
285
+ console.log(` android: ${chalk.cyan(state.android.name)} ${kind}${observed}${state.android.owned ? chalk.dim(" (owned)") : ""}`);
271
286
  }
272
287
  for (const w of state.warnings) console.log(chalk.yellow(` ! ${w}`));
273
288
  }
@@ -297,6 +312,21 @@ function statusCommand(program) {
297
312
  if (cap.overCapacity) console.log(chalk.yellow("Over comfortable capacity. A machine that swaps is slower than one working in sequence -- release one before starting another."));
298
313
  });
299
314
  }
315
+ function readAndroidRuntime(avdName) {
316
+ try {
317
+ const resolved = resolveOwnedAvdSerial(avdName, { timeoutMs: 5e3 });
318
+ return {
319
+ serial: resolved.serial ?? null,
320
+ state: resolved.serial ? "detected" : resolved.missing ? "missing" : resolved.notRunning ? "not-detected" : "unknown"
321
+ };
322
+ } catch (error) {
323
+ return {
324
+ serial: null,
325
+ state: "unknown",
326
+ error: String(error?.message || error).split("\n")[0]
327
+ };
328
+ }
329
+ }
300
330
  function readVolumes(projectPath) {
301
331
  const roots = [.../* @__PURE__ */ new Set([
302
332
  "/",
@@ -1,3 +1,3 @@
1
- import { t as resolveSupervisorTarget } from "./ownership-B9QIdQOw.mjs";
2
- import { a as resolveCollectorTargets, i as readSupervisorState, n as clearSupervisorState, o as runStop, r as readCollectorState, s as stopCommand, t as clearCollectorState } from "./stop-BApyaF21.mjs";
1
+ import { t as resolveSupervisorTarget } from "./ownership-CYOyqxUo.mjs";
2
+ import { a as resolveCollectorTargets, i as readSupervisorState, n as clearSupervisorState, o as runStop, r as readCollectorState, s as stopCommand, t as clearCollectorState } from "./stop-Btg770vV.mjs";
3
3
  export { clearCollectorState, clearSupervisorState, stopCommand as default, readCollectorState, readSupervisorState, resolveCollectorTargets, resolveSupervisorTarget, runStop };
@@ -1,13 +1,13 @@
1
- import { C as withConfigLock, D as sameProcessRecord, E as inspectProcessIdentity, O as waitForProcessExit, S as upsertProject, a as clearSupervisor, f as getProject } from "./config-D7uu8Gbq.mjs";
2
- import { _ as writeWorkspaceState, a as clearRemoteSession, c as readMetroTunnel, i as clearManagedMetroTunnel, m as withWorkspaceStateLock, o as clearWorkspaceStateKeys, p as readWorkspaceState, s as clearWorkspaceSupervisor, u as readRemoteSession } from "./state-IYEY-zi8.mjs";
3
- import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
4
- import { i as resolveProjectMetro, n as killMetroTree, r as pidExists } from "./metro-B01H3EuD.mjs";
5
- import { n as verifyCollectorOwnership } from "./ownership-D8HmSv2E.mjs";
6
- import { d as resolveEasCliBin, h as teardownOwnedIosSim, m as teardownOwnedAvd } from "./remote-cache-DOi5-zLj.mjs";
1
+ import { C as withConfigLock, D as sameProcessRecord, E as inspectProcessIdentity, O as waitForProcessExit, S as upsertProject, a as clearSupervisor, f as getProject } from "./config-CMtIaTk9.mjs";
2
+ import { _ as writeWorkspaceState, a as clearRemoteSession, c as readMetroTunnel, i as clearManagedMetroTunnel, m as withWorkspaceStateLock, o as clearWorkspaceStateKeys, p as readWorkspaceState, s as clearWorkspaceSupervisor, u as readRemoteSession } from "./state-CZ-ER-8d.mjs";
3
+ import { a as findProjectRoot } from "./project-BBTVABUz.mjs";
4
+ import { i as resolveProjectMetro, n as killMetroTree, r as pidExists } from "./metro-COslRK_F.mjs";
5
+ import { n as verifyCollectorOwnership } from "./ownership-CGPart6T.mjs";
6
+ import { d as resolveEasCliBin, h as teardownOwnedIosSim, m as teardownOwnedAvd } from "./remote-cache-DDmJ1Roq.mjs";
7
7
  import { f as releasedLeaseFact, l as phaseLine, u as plural } from "./command-output-8wHMEde0.mjs";
8
- import { l as releaseWorkspaceLeases } from "./device-lease-D-G3SgVe.mjs";
9
- import { i as endRecordedSession } from "./device-remote-CCgkG82J.mjs";
10
- import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-B9QIdQOw.mjs";
8
+ import { l as releaseWorkspaceLeases } from "./device-lease-Dpik41RY.mjs";
9
+ import { i as endRecordedSession } from "./device-remote-E81KAdXf.mjs";
10
+ import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-CYOyqxUo.mjs";
11
11
  import chalk from "chalk";
12
12
  //#region src/commands/stop.ts
13
13
  const DEFAULT_WAIT_MS = 1e4;
@@ -1,7 +1,7 @@
1
- import { T as captureProcessToken, a as clearSupervisor, x as setSupervisor } from "./config-D7uu8Gbq.mjs";
1
+ import { T as captureProcessToken, a as clearSupervisor, x as setSupervisor } from "./config-CMtIaTk9.mjs";
2
2
  import { r as createNdjsonWriter } from "./ndjson-DcAtEx_K.mjs";
3
- import { D as workspaceLogsDir, _ as writeWorkspaceState, h as writePidFile, l as readPidFile, m as withWorkspaceStateLock, n as MODE_EXPO, p as readWorkspaceState, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-IYEY-zi8.mjs";
4
- import { i as detectIsExpo } from "./project-COj6nzzh.mjs";
3
+ import { D as workspaceLogsDir, _ as writeWorkspaceState, h as writePidFile, l as readPidFile, m as withWorkspaceStateLock, n as MODE_EXPO, p as readWorkspaceState, r as clearExpoMetroTunnel, s as clearWorkspaceSupervisor, t as MODE_BARE } from "./state-CZ-ER-8d.mjs";
4
+ import { i as detectIsExpo } from "./project-BBTVABUz.mjs";
5
5
  import { t as describeError } from "./errors-B6W7FkxQ.mjs";
6
6
  import { existsSync, realpathSync } from "node:fs";
7
7
  import { isAbsolute, join, resolve } from "node:path";
@@ -97,7 +97,7 @@ async function runSupervisor({ root, port, tunnel = false, isExpo = detectIsExpo
97
97
  };
98
98
  let server;
99
99
  try {
100
- server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-rb0k-0w7.mjs")).startExpoServer : startBare || (await import("./server-bare-BYHqEeyw.mjs")).startBareServer)({
100
+ server = await (mode === "expo-child" ? startExpo || (await import("./server-expo-Drd7ksHu.mjs")).startExpoServer : startBare || (await import("./server-bare-DEk37Mz2.mjs")).startBareServer)({
101
101
  root,
102
102
  port,
103
103
  logsDir,
@@ -1,4 +1,4 @@
1
- import { f as releaseClaim, h as tryAcquireClaim } from "./ownership-claim-CeJLEpjD.mjs";
1
+ import { f as releaseClaim, h as tryAcquireClaim } from "./ownership-claim-7zRxxTfZ.mjs";
2
2
  import { join } from "node:path";
3
3
  //#region src/engine/workspace-process-lock.ts
4
4
  const DEFAULT_WAIT_MS = 6e4;
@@ -1,17 +1,17 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as captureProcessIdentity } from "./config-D7uu8Gbq.mjs";
3
- import { c as readMetroTunnel, u as readRemoteSession } from "./state-IYEY-zi8.mjs";
4
- import { _ as resolveFullRef, b as warmWorktreePaths, c as hasRemote, d as isPodInstallChurn, f as listWorktrees, h as removeWorktree, i as depsOutOfSync, l as hasUncommittedWork, m as readWorktreeExclude, n as cloneIgnoredEntries, o as dirtyPaths, p as podsOutOfSync, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isMainWorkingTree, v as restoreFile, y as unpushedCommits } from "./worktree-Dx3ZC5lS.mjs";
5
- import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
6
- import { v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DOi5-zLj.mjs";
7
- import { F as unknownSettingKeys, M as settingShapeErrors, a as SETTING_SHAPE_REMEDY, k as resolveSettings } from "./settings-BSwrY4FZ.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as captureProcessIdentity } from "./config-CMtIaTk9.mjs";
3
+ import { c as readMetroTunnel, u as readRemoteSession } from "./state-CZ-ER-8d.mjs";
4
+ import { _ as resolveFullRef, b as warmWorktreePaths, c as hasRemote, d as isPodInstallChurn, f as listWorktrees, h as removeWorktree, i as depsOutOfSync, l as hasUncommittedWork, m as readWorktreeExclude, n as cloneIgnoredEntries, o as dirtyPaths, p as podsOutOfSync, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isMainWorkingTree, v as restoreFile, y as unpushedCommits } from "./worktree-C79xx_kM.mjs";
5
+ import { a as findProjectRoot } from "./project-BBTVABUz.mjs";
6
+ import { v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DDmJ1Roq.mjs";
7
+ import { A as resolveSettings, I as unknownSettingKeys, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-DwU1_U_q.mjs";
8
8
  import { a as formatElapsed, f as releasedLeaseFact, i as formatDuration, l as phaseLine, m as shortUdid, u as plural } from "./command-output-8wHMEde0.mjs";
9
- import { a as clearClaimChild, c as isClaimUnavailable, d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, m as settleClaim, p as setClaimChild, r as claimFailure, s as isClaimRefusal, u as processGroupAlive } from "./ownership-claim-CeJLEpjD.mjs";
10
- import { g as podInstallCommand, r as HEARTBEAT_INTERVAL_MS } from "./xcode-CDB6fytw.mjs";
11
- import { a as installedNpmTreeIsValid, i as hasInstalledDependencies, o as locallyKnownUpstream, t as dependencyState } from "./doctor-DlVxTtMq.mjs";
12
- import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-B9QIdQOw.mjs";
13
- import { n as reclaimProject } from "./reclaim-BPvN2HlP.mjs";
14
- import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-64wVnpZc.mjs";
9
+ import { a as clearClaimChild, c as isClaimUnavailable, d as readClaimSet, f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, m as settleClaim, p as setClaimChild, r as claimFailure, s as isClaimRefusal, u as processGroupAlive } from "./ownership-claim-7zRxxTfZ.mjs";
10
+ import { g as podInstallCommand, r as HEARTBEAT_INTERVAL_MS } from "./xcode-DeKCribl.mjs";
11
+ import { a as installedNpmTreeIsValid, i as hasInstalledDependencies, o as locallyKnownUpstream, t as dependencyState } from "./doctor-CqPeMsSm.mjs";
12
+ import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-CYOyqxUo.mjs";
13
+ import { n as reclaimProject } from "./reclaim-C5Jmuahx.mjs";
14
+ import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-BLBsTNDS.mjs";
15
15
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
16
16
  import { createHash, randomUUID } from "node:crypto";
17
17
  import { join, relative } from "node:path";
@@ -54,11 +54,11 @@ function asHolder(holder) {
54
54
  };
55
55
  }
56
56
  function waitingLine(holder, elapsedMs) {
57
- return phaseLine("lock", `waiting ${formatElapsed(elapsedMs)} for ${warmCommand(holder.phase)} (pid ${holder.pid})`);
57
+ return phaseLine("lock", `waiting ${formatElapsed(elapsedMs)} for ${warmCommand(holder.phase)} (pid ${holder.pid}) -- stim guide lifecycle options`);
58
58
  }
59
59
  function warmClaimAcquiredLine(wait) {
60
60
  if (!wait.holder || wait.waitedMs <= 0) return phaseLine("lock", "acquired");
61
- return phaseLine("lock", `acquired (waited ${formatElapsed(wait.waitedMs)} for ${warmCommand(wait.holder.phase)} pid ${wait.holder.pid})`);
61
+ return phaseLine("lock", `acquired (waited ${formatElapsed(wait.waitedMs)} for ${warmCommand(wait.holder.phase)} pid ${wait.holder.pid}) -- stim guide lifecycle options`);
62
62
  }
63
63
  function warmClaimUnavailableLine(reason) {
64
64
  return phaseLine("lock", `unavailable (${reason}); copying without it`);
@@ -109,7 +109,7 @@ function warmClaimBlockedRefusal(blocker) {
109
109
  return `Refusing to copy past a claim Stim cannot resolve. If nothing is warming, remove it and run warm again:\n ${claimRemoveCommand(blocker.path)}`;
110
110
  }
111
111
  function installerWaitingLine(pid, elapsedMs) {
112
- return phaseLine("lock", `waiting on the install this refresh spawned (pid ${pid}, ${formatElapsed(elapsedMs)} elapsed)`);
112
+ return phaseLine("lock", `waiting on the install this refresh spawned (pid ${pid}, ${formatElapsed(elapsedMs)} elapsed) -- stim guide lifecycle options`);
113
113
  }
114
114
  function installerUnrecordedLine(reason) {
115
115
  return phaseLine("lock", `could not record the install this refresh spawned (${reason}); holding the claim here until it exits`);
@@ -1,5 +1,5 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { g as loadConfig, u as getConfigDir } from "./config-D7uu8Gbq.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { g as loadConfig, u as getConfigDir } from "./config-CMtIaTk9.mjs";
3
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { chmodSync as chmodSync$1, existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, utimesSync } from "fs";
@@ -85,7 +85,11 @@ function removeTemporaryEntry(path) {
85
85
  }
86
86
  //#endregion
87
87
  //#region src/worktree.ts
88
- const CARRY_SKIP_BASENAMES = /* @__PURE__ */ new Set([".DerivedData"]);
88
+ const CARRY_SKIP_BASENAMES = /* @__PURE__ */ new Set([
89
+ ".DerivedData",
90
+ ".DS_Store",
91
+ ".idea"
92
+ ]);
89
93
  function isCarrySkipped(rel) {
90
94
  return /(^|\/)android\/build\/generated\/autolinking(\/|$)/.test(rel) || String(rel).split("/").some((seg) => CARRY_SKIP_BASENAMES.has(seg));
91
95
  }
@@ -1,23 +1,24 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { C as withConfigLock, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, y as saveConfig } from "./config-D7uu8Gbq.mjs";
3
- import { r as register } from "./cache-manifest-oeH18ERr.mjs";
4
- import { E as workspaceId, S as sharedCompilationCache, w as workspaceDerivedData } from "./state-IYEY-zi8.mjs";
5
- import { s as ownedDeviceLabel } from "./project-COj6nzzh.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
+ import { C as withConfigLock, E as inspectProcessIdentity, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, w as captureProcessIdentity, y as saveConfig } from "./config-CMtIaTk9.mjs";
3
+ import { r as register } from "./cache-manifest-Cu4_Znw9.mjs";
4
+ import { E as workspaceId, S as sharedCompilationCache, w as workspaceDerivedData } from "./state-CZ-ER-8d.mjs";
5
+ import { s as ownedDeviceLabel } from "./project-BBTVABUz.mjs";
6
6
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
7
- import { r as pidExists } from "./metro-B01H3EuD.mjs";
8
- import { C as selectParked, S as readParked, _ as teardownParkedIosSim, b as dropParked, g as teardownParkedAvd, m as teardownOwnedAvd, x as parkedMaxSetting, y as adoptParked } from "./remote-cache-DOi5-zLj.mjs";
9
- import { H as resolveOptimizations, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-BSwrY4FZ.mjs";
7
+ import { r as pidExists } from "./metro-COslRK_F.mjs";
8
+ import { C as selectParked, S as readParked, _ as teardownParkedIosSim, b as dropParked, g as teardownParkedAvd, m as teardownOwnedAvd, x as parkedMaxSetting, y as adoptParked } from "./remote-cache-DDmJ1Roq.mjs";
9
+ import { U as resolveOptimizations, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-DwU1_U_q.mjs";
10
10
  import { a as formatElapsed, l as phaseLine } from "./command-output-8wHMEde0.mjs";
11
- import { _ as resetIosKeychain, b as resolveOwnedIosSim, c as iosRuntimeMatches, g as renameIosSim, h as parseRuntimeVersion, i as createOwnedIosSim, l as listAllIosSims, n as bootIosSim, p as ownedSimName, u as listIosDeviceTypes, v as resetIosPrivacy, y as resolveIosCreation } from "./ios-BZxKPuqP.mjs";
12
- import { A as resolveOwnedAvdSerial, C as ownedAvdMatchesConfiguration, D as pickDefaultSystemImage, P as waitForBoot, T as ownedAvdSystemImage, _ as listAdbDevices, c as bootAndroidEmulator, g as getAvdNameForSerial, l as configureNewOwnedAvd, o as assertOwnedAvdStopped, s as avdPoolConfiguration, u as createOwnedAvd, v as listAvds, w as ownedAvdName, x as nextConsolePort, y as listInstalledSystemImages } from "./android-xmyGBNy5.mjs";
13
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-CxOO9-pf.mjs";
14
- import { t as cleanLine } from "./server-expo-BEMr_m6p.mjs";
11
+ import { E as readHostMemoryPressure, T as hostMemoryPressureAdvice, _ as renameIosSim, b as resolveIosCreation, c as iosRuntimeMatches, d as listIosDeviceTypes, g as parseRuntimeVersion, i as createOwnedIosSim, l as iosSimulatorFailureAdvice, m as ownedSimName, n as bootIosSim, u as listAllIosSims, v as resetIosKeychain, x as resolveOwnedIosSim, y as resetIosPrivacy } from "./ios-D1h1Di39.mjs";
12
+ import { f as releaseClaim, h as tryAcquireClaim, i as claimRemoveCommand, l as markClaimChildPending, n as ClaimRefusedError, p as setClaimChild, u as processGroupAlive } from "./ownership-claim-7zRxxTfZ.mjs";
13
+ import { A as pickDefaultSystemImage, D as ownedAvdName, E as ownedAvdMatchesConfiguration, L as waitForBoot, N as resolveOwnedAvdSerial, O as ownedAvdSystemImage, b as listAvds, c as avdPoolConfiguration, d as configureNewOwnedAvd, f as createOwnedAvd, o as assertOwnedAvdStopped, u as bootAndroidEmulator, v as getAvdNameForSerial, w as nextConsolePort, x as listInstalledSystemImages, y as listAdbDevices } from "./android-K63_0UfF.mjs";
14
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BKfdys1q.mjs";
15
+ import { t as cleanLine } from "./server-expo-C3ktAwUG.mjs";
15
16
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
16
17
  import { createHash, randomUUID } from "node:crypto";
17
18
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
18
19
  import chalk from "chalk";
19
20
  //#region src/engine/simslim.ts
20
- async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out = () => {}, spawn }) {
21
+ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out = () => {}, spawn, timeoutMs = 72e4, cleanupMs = 1e4 }) {
21
22
  if (!profile && !previouslyManaged) return {
22
23
  managed: false,
23
24
  profile: null
@@ -39,25 +40,107 @@ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out
39
40
  };
40
41
  const stdout = createLineReader(onLine);
41
42
  const stderr = createLineReader(onLine);
43
+ const root = join(getConfigDir(), "simslim-locks", `${encodeURIComponent(udid.toLowerCase())}.lock`);
44
+ const label = `SimSlim for ${udid}`;
45
+ const attempt = tryAcquireClaim({
46
+ root,
47
+ mode: "exclusive",
48
+ label
49
+ });
50
+ if (attempt.pending) releaseClaim(attempt.pending);
51
+ const claim = attempt.acquired;
52
+ if (!claim) throw new ClaimRefusedError({
53
+ root,
54
+ claimPath: attempt.held?.path ?? root,
55
+ label,
56
+ reason: "another SimSlim operation is still using this simulator"
57
+ });
42
58
  let child;
59
+ let result;
60
+ let identity;
61
+ let cancellation;
62
+ const stopGroup = () => {
63
+ if (child?.pid !== void 0 && identity && inspectProcessIdentity(identity) === "same") try {
64
+ process.kill(-child.pid, "SIGKILL");
65
+ } catch {}
66
+ };
67
+ const cancel = (signal) => {
68
+ cancellation ??= signal;
69
+ stopGroup();
70
+ };
71
+ const onInterrupt = () => cancel("SIGINT");
72
+ const onTerminate = () => cancel("SIGTERM");
73
+ const groupAlive = () => child?.pid !== void 0 && processGroupAlive(child.pid);
74
+ const settled = () => result !== void 0 && !groupAlive();
75
+ const waitUntil = async (deadline, cancellable = false) => {
76
+ while (!settled() && Date.now() < deadline) {
77
+ if (cancellable && cancellation) break;
78
+ await new Promise((resolve) => setTimeout(resolve, Math.min(50, deadline - Date.now())));
79
+ }
80
+ return settled();
81
+ };
82
+ process.on("SIGINT", onInterrupt);
83
+ process.on("SIGTERM", onTerminate);
84
+ process.on("exit", stopGroup);
43
85
  try {
44
- child = (spawn ?? ((cmd, childArgs, opts) => getExecutor().spawn(cmd, childArgs, opts)))("simslim", args, { stdio: [
45
- "ignore",
46
- "pipe",
47
- "pipe"
48
- ] });
49
- } catch (error) {
50
- throw simslimLaunchError(error);
86
+ markClaimChildPending(claim);
87
+ try {
88
+ child = (spawn ?? ((cmd, childArgs, opts) => getExecutor().spawn(cmd, childArgs, opts)))("simslim", args, {
89
+ stdio: [
90
+ "ignore",
91
+ "pipe",
92
+ "pipe"
93
+ ],
94
+ detached: true
95
+ });
96
+ } catch (error) {
97
+ throw simslimLaunchError(error);
98
+ }
99
+ child.stdout?.on("data", (chunk) => stdout.push(chunk));
100
+ child.stderr?.on("data", (chunk) => stderr.push(chunk));
101
+ waitForChild(child).then((value) => {
102
+ result = value;
103
+ });
104
+ if (child.pid !== void 0) {
105
+ const captured = captureProcessIdentity(child.pid);
106
+ if (captured.ok) {
107
+ identity = {
108
+ pid: child.pid,
109
+ processToken: captured.token
110
+ };
111
+ try {
112
+ setClaimChild(claim, identity);
113
+ } catch {}
114
+ }
115
+ }
116
+ const completed = await waitUntil(Date.now() + timeoutMs, true);
117
+ if (cancellation || !completed) {
118
+ stopGroup();
119
+ const stopped = await waitUntil(Date.now() + cleanupMs);
120
+ const detail = lines.length ? ` ${lines.join(" | ")}` : "";
121
+ const recovery = stopped ? "Its process group has stopped. Retry stim ios to reconcile the retained simulator settings." : `Its process group could not be confirmed stopped. The claim remains at ${claim.path}. Inspect the SimSlim process group ${child.pid ?? "unknown"} before retrying. Only when nothing is using it, remove the claim: ${claimRemoveCommand(claim.path)}`;
122
+ const message = `SimSlim ${action} ${cancellation ? `interrupted by ${cancellation}` : `exceeded ${Math.round(timeoutMs / 1e3)}s`}. ${recovery}${detail}`;
123
+ if (cancellation) out(message);
124
+ throw Object.assign(new Error(message), { code: cancellation ? "EINTR" : "ETIMEDOUT" });
125
+ }
126
+ } finally {
127
+ if (!child || settled()) releaseClaim(claim);
128
+ else {
129
+ child.stdout?.destroy?.();
130
+ child.stderr?.destroy?.();
131
+ child.unref();
132
+ }
133
+ process.off("SIGINT", onInterrupt);
134
+ process.off("SIGTERM", onTerminate);
135
+ process.off("exit", stopGroup);
136
+ if (cancellation) process.exit(cancellation === "SIGINT" ? 130 : 143);
51
137
  }
52
- child.stdout?.on("data", (chunk) => stdout.push(chunk));
53
- child.stderr?.on("data", (chunk) => stderr.push(chunk));
54
- const result = await waitForChild(child);
55
138
  stdout.flush();
56
139
  stderr.flush();
57
- if (result.error) throw simslimLaunchError(result.error);
58
- if (result.code !== 0) {
140
+ if (result?.error) throw simslimLaunchError(result.error);
141
+ if (result?.code !== 0) {
59
142
  const detail = lines.length ? ` ${lines.join(" | ")}` : "";
60
- throw new Error(`SimSlim ${action} failed with exit code ${result.code ?? "unknown"}.${detail}`);
143
+ throw new Error(`SimSlim ${action} failed with exit code ${result?.code ?? "unknown"}.${detail}`);
61
144
  }
62
145
  return {
63
146
  managed: Boolean(profile),
@@ -118,6 +201,8 @@ async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot
118
201
  });
119
202
  }
120
203
  async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label, settings, flags, note, out, reconcileIosSimulator }) {
204
+ const memoryAdvice = hostMemoryPressureAdvice(readHostMemoryPressure());
205
+ if (memoryAdvice) out(chalk.yellow(phaseLine("memory", memoryAdvice)));
121
206
  const simslimProfile = iosSimSlimProfileSetting(settings, settingsRoot);
122
207
  if (record?.deviceUdid) {
123
208
  if (record.owned) {
@@ -361,11 +446,21 @@ function findOtherProjectOwningAvd(avdName, projectPath) {
361
446
  }
362
447
  return null;
363
448
  }
449
+ var AvdRecoveryError = class extends Error {
450
+ remedy;
451
+ constructor(message, remedy, cause) {
452
+ super(message, { cause });
453
+ this.remedy = remedy;
454
+ }
455
+ };
364
456
  async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
365
457
  const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
366
458
  const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig);
367
459
  if (record?.setupIncomplete && record.avdName) {
368
- const cleanup = teardownAvd(record.avdName, { del: true });
460
+ const cleanup = teardownAvd(record.avdName, {
461
+ del: true,
462
+ owner: { projectPath }
463
+ });
369
464
  if (cleanup.status === "failed" || cleanup.status === "skipped") throw new Error(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`);
370
465
  clearDevice(projectPath, "android");
371
466
  record = null;
@@ -376,6 +471,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
376
471
  if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned by name -- creating a fresh owned AVD instead of reusing it.`));
377
472
  else if (resolved.serial) {
378
473
  const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
474
+ if (record.consolePort && record.consolePort !== consolePort) out(phaseLine("device", `${record.avdName} changed serial (emulator-${record.consolePort} -> ${resolved.serial}); reconnecting this run, reopen agent-device on ${resolved.serial}`));
379
475
  const updated = {
380
476
  ...record,
381
477
  avdName: record.avdName,
@@ -421,7 +517,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
421
517
  if (parked.deletionClaim !== void 0) continue;
422
518
  const resolved = resolveOwnedAvdSerial(parked.name);
423
519
  if (resolved.missing) {
424
- dropParked("android", parked.udid);
520
+ const result = teardownParkedAvd(parked.name);
521
+ if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
425
522
  continue;
426
523
  }
427
524
  if (resolved.notOwned || resolved.serial) continue;
@@ -484,28 +581,45 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
484
581
  } catch (e) {
485
582
  const message = String(e?.message || e);
486
583
  const avdName = ownedAvdName(label);
487
- if (message.includes("already exists") && listAvds().includes(avdName)) {
488
- created = withConfigLock(() => {
489
- if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`, { cause: e });
490
- const owner = findOtherProjectOwningAvd(avdName, projectPath);
491
- if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`, { cause: e });
492
- const current = loadConfig()?.projects?.[projectPath]?.platforms?.android;
493
- if (current?.avdName) {
494
- const state = current.setupIncomplete ? "has incomplete setup" : "was registered";
495
- throw new Error(`AVD ${current.avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`, { cause: e });
496
- }
497
- const result = {
498
- avdName,
499
- systemImage: ownedAvdSystemImage(avdName)
500
- };
501
- setDevice(projectPath, "android", {
502
- avdName,
503
- owned: true,
504
- deviceName: avdName
584
+ if (message.includes("already exists")) {
585
+ try {
586
+ if (!listAvds().includes(avdName)) throw new AvdRecoveryError(`AVD ${avdName} already exists on disk but is not listed by the emulator. ${message}`, "Run `npx stim gc` to inspect orphaned owned AVDs, then `npx stim gc --delete` to reclaim those safe to delete. Retry `stim android` after cleanup; keep any AVD that GC cannot verify.", e);
587
+ created = withConfigLock(() => {
588
+ if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`, { cause: e });
589
+ const owner = findOtherProjectOwningAvd(avdName, projectPath);
590
+ if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`, { cause: e });
591
+ const current = loadConfig()?.projects?.[projectPath]?.platforms?.android;
592
+ if (current?.avdName) {
593
+ const state = current.setupIncomplete ? "has incomplete setup" : "was registered";
594
+ throw new Error(`AVD ${current.avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`, { cause: e });
595
+ }
596
+ const resolved = resolveOwnedAvdSerial(avdName);
597
+ if (resolved.missing || resolved.notOwned) throw new Error(`AVD ${avdName} could not be verified for recovery. Retry after checking its registration.`, { cause: e });
598
+ if (!resolved.serial) assertOwnedAvdStopped(avdName);
599
+ const recovered = {
600
+ avdName,
601
+ owned: true,
602
+ deviceName: avdName,
603
+ ...resolved.serial ? { consolePort: Number(resolved.serial.replace(/^emulator-/, "")) } : {}
604
+ };
605
+ setDevice(projectPath, "android", recovered);
606
+ return {
607
+ ...recovered,
608
+ systemImage: ownedAvdSystemImage(avdName),
609
+ serial: resolved.serial
610
+ };
505
611
  });
506
- return result;
507
- });
612
+ } catch (error) {
613
+ if (error instanceof AvdRecoveryError) throw error;
614
+ throw new AvdRecoveryError(`Could not recover owned AVD ${avdName}: ${String(error?.message || error)}`, "Inspect `npx stim status` and `adb devices`. Wait for any other Stim run using this AVD to finish, then retry `stim android`. Keep the AVD and its process locks while its state is unverified.", error);
615
+ }
508
616
  out(chalk.dim(phaseLine("device", `recovered ${avdName} (unrecorded from a prior run)`)));
617
+ if (created.serial) return {
618
+ ...created,
619
+ owned: true,
620
+ deviceName: avdName,
621
+ created: false
622
+ };
509
623
  } else throw e;
510
624
  }
511
625
  if (fresh) try {
@@ -514,7 +628,10 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
514
628
  avdConfig
515
629
  });
516
630
  } catch (error) {
517
- const cleanup = teardownAvd(created.avdName, { del: true });
631
+ const cleanup = teardownAvd(created.avdName, {
632
+ del: true,
633
+ owner: { projectPath }
634
+ });
518
635
  const kept = cleanup.status === "failed" || cleanup.status === "skipped";
519
636
  if (!kept) clearDevice(projectPath, "android");
520
637
  const orphan = kept ? ` The owned AVD remains tracked for cleanup (${cleanup.reason || cleanup.status}); fix the cause, then retry or run \`stim gc --delete\`.` : "";
@@ -530,7 +647,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, lab
530
647
  logFile,
531
648
  alive
532
649
  }),
533
- created: true,
650
+ created: fresh,
534
651
  systemImage: created.systemImage
535
652
  };
536
653
  }
@@ -716,6 +833,25 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
716
833
  failed: true,
717
834
  reason: "No iOS simulator is recorded for this project."
718
835
  };
836
+ const ready = () => {
837
+ try {
838
+ getExecutor().runFile("xcrun", [
839
+ "simctl",
840
+ "spawn",
841
+ udid,
842
+ "/usr/bin/true"
843
+ ], { timeoutMs: 3e4 });
844
+ return {
845
+ ok: true,
846
+ udid
847
+ };
848
+ } catch (error) {
849
+ return {
850
+ failed: true,
851
+ reason: `Simulator ${udid} failed its process-spawn readiness check: ${error?.message || error}. ${iosSimulatorFailureAdvice()}`
852
+ };
853
+ }
854
+ };
719
855
  const booting = device?.booting;
720
856
  if (booting?.udid === udid) {
721
857
  try {
@@ -726,10 +862,7 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
726
862
  reason: `Could not boot simulator ${udid}: ${e?.message || e}`
727
863
  };
728
864
  }
729
- return {
730
- ok: true,
731
- udid
732
- };
865
+ return ready();
733
866
  }
734
867
  let resolved;
735
868
  try {
@@ -749,10 +882,7 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
749
882
  reason: `Simulator ${udid} is now named "${resolved.notOwned}" and is not Stim-owned; refusing to boot it.`
750
883
  };
751
884
  const sim = resolved.sim;
752
- if (sim.state === "Booted") return {
753
- ok: true,
754
- udid
755
- };
885
+ if (sim.state === "Booted") return ready();
756
886
  out(chalk.dim(phaseLine("device", `booting ${sim.name} (${udid})`)));
757
887
  const bootDeadline = Date.now() + timeoutMs;
758
888
  try {
@@ -770,10 +900,7 @@ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
770
900
  try {
771
901
  state = listAllIosSims({ timeoutMs: 3e4 }).find((s) => s.udid === udid)?.state ?? null;
772
902
  } catch {}
773
- if (state === "Booted") return {
774
- ok: true,
775
- udid
776
- };
903
+ if (state === "Booted") return ready();
777
904
  }
778
905
  return {
779
906
  failed: true,
@@ -1702,4 +1829,4 @@ async function buildIos({ root, udid = null, logWriter, project = null, scheme =
1702
1829
  };
1703
1830
  }
1704
1831
  //#endregion
1705
- export { unknownIosDeviceTypeRefusal as C, unknownAndroidSystemImageRefusal as S, simslimIsOnPath as T, checkDeviceCapacity as _, ccacheEnabled as a, ensureOwnedDevice as b, discoverXcodeProject as c, resolveScheme as d, startBuildHeartbeat as f, podInstallCommand as g, bundlerPin as h, buildIos as i, readBundleExecutable as l, describeDiagnostic as m, COMPILATION_CACHE_UNAVAILABLE as n, compilationCacheActivityLine as o, tailLines as p, HEARTBEAT_INTERVAL_MS as r, detectXcodeMajor as s, COMPILATION_CACHE_NOT_RUN as t, readBundleId as u, clearIosAdoptionPending as v, unknownIosRuntimeRefusal as w, liveOwnedDeviceCount as x, ensureBooted as y };
1832
+ export { unknownAndroidSystemImageRefusal as C, simslimIsOnPath as E, liveOwnedDeviceCount as S, unknownIosRuntimeRefusal as T, AvdRecoveryError as _, ccacheEnabled as a, ensureBooted as b, discoverXcodeProject as c, resolveScheme as d, startBuildHeartbeat as f, podInstallCommand as g, bundlerPin as h, buildIos as i, readBundleExecutable as l, describeDiagnostic as m, COMPILATION_CACHE_UNAVAILABLE as n, compilationCacheActivityLine as o, tailLines as p, HEARTBEAT_INTERVAL_MS as r, detectXcodeMajor as s, COMPILATION_CACHE_NOT_RUN as t, readBundleId as u, checkDeviceCapacity as v, unknownIosDeviceTypeRefusal as w, ensureOwnedDevice as x, clearIosAdoptionPending as y };