stim 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/{activity-B2-kb7k0.mjs → activity-IRvJY_ru.mjs} +49 -16
  2. package/dist/agent-device-usage-output-BK41vnzN.mjs +411 -0
  3. package/dist/{android-Cu_L4RpL.mjs → android--GorydsK.mjs} +692 -321
  4. package/dist/{android-Db2eEMaf.mjs → android-B_YNZy61.mjs} +25 -60
  5. package/dist/{android-CYmq0jNi.mjs → android-DE4v59Mb.mjs} +12 -3
  6. package/dist/{android-cas-PnKfAdB2.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
  7. package/dist/{app-install-CAAplXAt.mjs → app-install-VPkI5WEg.mjs} +52 -8
  8. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  9. package/dist/{budget-CMiqghig.mjs → budget-DM984mnd.mjs} +372 -366
  10. package/dist/{build-plan-aeGMFX7e.mjs → build-plan-CmQCoyT-.mjs} +346 -88
  11. package/dist/{build-slots-Df1ApSuH.mjs → build-slots-41v_d2x9.mjs} +36 -21
  12. package/dist/{cli-0cX5s4NM.mjs → cli-Daxug5-K.mjs} +23 -17
  13. package/dist/cli.mjs +1 -1
  14. package/dist/collector-run.mjs +5 -192
  15. package/dist/{command-output-CliHHn7a.mjs → command-output-CDAIFD-8.mjs} +3 -3
  16. package/dist/{created-devices-BwVb3liH.mjs → created-devices-CpvzBL25.mjs} +1 -1
  17. package/dist/{dependency-state-BmsjKZ2H.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
  18. package/dist/{deps-BFlKbOIp.mjs → deps-DNWaQs4V.mjs} +2 -2
  19. package/dist/{dev-client-CjzLoNs0.mjs → dev-client-B-XC4uRH.mjs} +3 -40
  20. package/dist/{device-BZ1IyS5A.mjs → device-C8IOoJPv.mjs} +7 -6
  21. package/dist/device-capacity-D0GWHnTM.mjs +580 -0
  22. package/dist/device-host-worker.mjs +587 -110
  23. package/dist/{device-ios-DMQBUPus.mjs → device-ios-Bc7vbGwl.mjs} +75 -44
  24. package/dist/{device-lease-W5Pi1q38.mjs → device-lease-DpoS36_u.mjs} +2 -2
  25. package/dist/{device-pool-B2r38YjS.mjs → device-lease-run-B8YltIH_.mjs} +5 -125
  26. package/dist/device-pool-3o7s5lo4.mjs +125 -0
  27. package/dist/{device-remote-9ulPsdhY.mjs → device-remote-hFSkpfQu.mjs} +13 -13
  28. package/dist/{doctor-BcE5xsBn.mjs → doctor-Cnk9d7Hm.mjs} +86 -59
  29. package/dist/{error-diagnostics-Cb0P1WLS.mjs → error-diagnostics-C-NMnhrk.mjs} +11 -15
  30. package/dist/{gc-BuL1Genr.mjs → gc-DxCL2A46.mjs} +478 -33
  31. package/dist/{gradle-CmdOBq1d.mjs → gradle-D7-L2Th3.mjs} +4 -4
  32. package/dist/{guide-cCSGRFd3.mjs → guide-DjrTQGik.mjs} +2198 -566
  33. package/dist/{guide-status-CIoWK6mG.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
  34. package/dist/hosted-android-DwLS4Mse.mjs +32 -0
  35. package/dist/hosted-client-wk1W_CNW.mjs +196 -0
  36. package/dist/hosted-ios-aMo7Cljf.mjs +15 -0
  37. package/dist/hosted-logs-DqYmWtXc.mjs +187 -0
  38. package/dist/hosted-macos-dlHXEeF5.mjs +135 -0
  39. package/dist/hosted-native-C9xJ9rpA.mjs +255 -0
  40. package/dist/{build-progress-C2Jv1AtA.mjs → idle-B2m-Ovod.mjs} +300 -9
  41. package/dist/{idle-shutdown-B5LPaNyY.mjs → idle-shutdown-_3oOV1V_.mjs} +25 -17
  42. package/dist/{in-use-CndPMilN.mjs → in-use-CvGOUn-x.mjs} +30 -10
  43. package/dist/ios-Cr1h5gnO.mjs +189 -0
  44. package/dist/{ios-Chob8ny7.mjs → ios-DE6duQoz.mjs} +819 -460
  45. package/dist/{ios-3CtCRhwr.mjs → ios-DSfnPfse.mjs} +4 -4
  46. package/dist/{ios-device-DERmMmk-.mjs → ios-device-pAN4t54-.mjs} +1 -1
  47. package/dist/ios-state-8L3lgum6.mjs +89 -0
  48. package/dist/{launch-verify-CLlofxRt.mjs → launch-verify-lTnWZikR.mjs} +3 -3
  49. package/dist/logs-DUoKlDoS.mjs +384 -0
  50. package/dist/{client-C2IS36Qf.mjs → machines-C2XiNwh9.mjs} +330 -37
  51. package/dist/{macos-BwtbUtTN.mjs → macos-32po3kUq.mjs} +23 -18
  52. package/dist/macos-run.mjs +1 -1
  53. package/dist/maintenance-run.d.mts +4 -0
  54. package/dist/maintenance-run.mjs +254 -0
  55. package/dist/{metro-Di8EN2Tb.mjs → metro-g8NqiJp-.mjs} +1 -1
  56. package/dist/metro-gateway-DmslUM30.mjs +103 -0
  57. package/dist/{named-ports-Dj_XBNaL.mjs → named-ports-C8m8EQs2.mjs} +2 -2
  58. package/dist/{native-run-DFDoIRbY.mjs → native-run-D1Q56rAD.mjs} +17 -6
  59. package/dist/{native-runtime-i6OkVg-w.mjs → native-runtime-DhJI3IVQ.mjs} +6 -6
  60. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  61. package/dist/offload-worker.d.mts +1 -0
  62. package/dist/offload-worker.mjs +13 -8
  63. package/dist/{teardown-BYuQafiA.mjs → ownership-BMLqyBgJ.mjs} +639 -53
  64. package/dist/{ownership-BkYxUtuj.mjs → ownership-DyRQru0q.mjs} +2 -2
  65. package/dist/{ports-DddwttKM.mjs → ports-4nYqxPWC.mjs} +2 -2
  66. package/dist/{prebuild-BvxDlD6C.mjs → prebuild-D7RUMkwN.mjs} +4 -4
  67. package/dist/preview-CxPRIEht.mjs +318 -0
  68. package/dist/{project-CV3tDFHl.mjs → project-D47Rh0ly.mjs} +72 -3
  69. package/dist/{recordings-GEXp4iBT.mjs → recordings-BNgsD1Tr.mjs} +1 -1
  70. package/dist/{reload-ChPiwvot.mjs → reload-qZei6iWn.mjs} +77 -13
  71. package/dist/{remote-cache-CO2elm_b.mjs → remote-cache-Ci4DQYYj.mjs} +4 -4
  72. package/dist/{run-B-xqRrsg.mjs → run-CkE9y0nc.mjs} +2 -2
  73. package/dist/{server-bare-X7PUgizL.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
  74. package/dist/{server-expo-DhtX6bom.mjs → server-expo-CHTyXD0G.mjs} +4 -4
  75. package/dist/server-expo-CeakhI8P.mjs +2 -0
  76. package/dist/settings-BBpMcDbD.mjs +25 -0
  77. package/dist/settings-Cfd-0r2d.mjs +1440 -0
  78. package/dist/{settings-DbL6uhH8.mjs → settings-SR9OmW9h.mjs} +10 -4
  79. package/dist/settings.schema.json +472 -50
  80. package/dist/{simslim-Ciluxlq4.mjs → simslim-BWNrc3M3.mjs} +3 -3
  81. package/dist/{slot-launch-BY0BrOLh.mjs → slot-launch-DmjBmpQV.mjs} +10 -7
  82. package/dist/{spawn-entry-DncpJvWL.mjs → spawn-entry-jw19m8Nl.mjs} +1 -0
  83. package/dist/{start-CPO4wRg7.mjs → start-BopZcr4n.mjs} +1 -1
  84. package/dist/{start-CUD9nasA.mjs → start-uee1wWjY.mjs} +21 -17
  85. package/dist/{state-CaeaHbDA.mjs → state-BKa8wNN5.mjs} +1 -1
  86. package/dist/{state-BxCgeTQu.mjs → state-DyaNTgfR.mjs} +2 -2
  87. package/dist/{state-DFm77wRn.mjs → state-PeQiJuIu.mjs} +5 -5
  88. package/dist/{stats-DBLLFmKd.mjs → stats-Bq4fJOdB.mjs} +19 -5
  89. package/dist/{status-l84Gbvp2.mjs → status-CzS4bPO9.mjs} +5 -3
  90. package/dist/status-DlKvVJBE.mjs +49 -0
  91. package/dist/{status-DYXxNOT0.mjs → status-dXX2JX1C.mjs} +244 -50
  92. package/dist/{stop-CceYh1yG.mjs → stop-BCwvvs7F.mjs} +2 -2
  93. package/dist/{stop-Bad61z_2.mjs → stop-BjizrQwv.mjs} +83 -21
  94. package/dist/{stop-Bvf1MflS.mjs → stop-Cr1d8DSE.mjs} +4 -4
  95. package/dist/stop-cause-rhmGfwvt.mjs +97 -0
  96. package/dist/supervisor-run.d.mts +7 -4
  97. package/dist/supervisor-run.mjs +156 -27
  98. package/dist/{support-WLOzVKUv.mjs → support-BJPgYCu-.mjs} +61 -11
  99. package/dist/{support-CDQbDkL5.mjs → support-F9-sr0Oi.mjs} +6 -5
  100. package/dist/{toolchain-DmeYjXD9.mjs → toolchain-CNIBTIOv.mjs} +11 -11
  101. package/dist/trigger-Bemu8ytw.mjs +65 -0
  102. package/dist/trigger-CXts6qt1.mjs +2 -0
  103. package/dist/{warm-progress-DVHLN4uC.mjs → warm-progress-DF4JRvYI.mjs} +274 -5
  104. package/dist/watchman-APYjXSQa.mjs +48 -0
  105. package/dist/{web-k6w8-HnE.mjs → web-exQtGkoc.mjs} +14 -16
  106. package/dist/web-run.mjs +5 -5
  107. package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
  108. package/dist/{workspace-state-CL2V2Ons.mjs → workspace-state-DqcCbpE7.mjs} +34 -34
  109. package/dist/workspaces-Bcib97-6.mjs +306 -0
  110. package/dist/{worktree-BT_uULFx.mjs → worktree-Dc98cE6_.mjs} +1 -1
  111. package/dist/{worktree-qXKzADSC.mjs → worktree-ZpZR3S1k.mjs} +601 -33
  112. package/package.json +5 -5
  113. package/dist/device-capacity-DMuKC7nx.mjs +0 -136
  114. package/dist/hosted-macos-DwowNU7W.mjs +0 -447
  115. package/dist/logs-h9h6MUvE.mjs +0 -568
  116. package/dist/machine-usage-D_YYMzfR.mjs +0 -283
  117. package/dist/machines-CYtOCqcj.mjs +0 -259
  118. package/dist/merge-state-BnxLNQiM.mjs +0 -272
  119. package/dist/server-expo-KJeD109I.mjs +0 -2
  120. package/dist/settings-CduVcoxj.mjs +0 -680
  121. package/dist/stop-cause-CZ_Mr-v9.mjs +0 -665
  122. package/dist/watchman-Bx0wceoF.mjs +0 -238
  123. package/dist/worktree-C0c5Udwa.mjs +0 -753
@@ -1,50 +1,434 @@
1
1
  import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  import { C as saveConfig, D as withConfigLock$1, E as upsertProject, L as deviceSlotFileKey, R as deviceSlotKey, V as parseDeviceSlotOption, W as validateDeviceSlot, _ as loadConfig$1, j as formatBytes, p as getProject, u as getConcurrencyLimits, w as setDevice, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
3
3
  import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
4
- import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
5
- import { C as workspaceLogsDir, b as workspaceDir, c as writeWorkspaceState, f as emulatorLogFile, i as recordWorkspaceUse, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
6
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-C0c5Udwa.mjs";
7
- import { a as findProjectRoot$1, l as projectShortcut, n as appProjectProblem, r as detectIsExpo, t as NO_PROJECT_REFUSAL } from "./project-CV3tDFHl.mjs";
8
- import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-CaeaHbDA.mjs";
9
4
  import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
10
- import { _ as stepTimer, c as launchErrorReport, f as refuseNoProject, g as stepClock, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-CliHHn7a.mjs";
11
- import { A as providerDownloadPath, B as captureAssetManifest, D as fingerprintProject, E as fingerprintDiffRecord, F as storedAssetManifest, H as readAssetManifest, L as untrackedMissLine, M as refingerprintAfterMutation, N as resolveBuild, O as inputsChangedDuringBuild, P as storeBuild, R as untrackedNativeFiles, S as configInputsChanged, T as filesystemBuildCapability, V as compareAssetManifests, _ as waitForBuild, b as buildCacheKey, g as releaseBuildLock, j as providerUploadOutcome, k as prepareProviderDownloadDir, m as acquireBuildLock, v as waitForSharedBuild, x as changedDuringBuildLine, z as assetDiffReason } from "./device-lease-W5Pi1q38.mjs";
5
+ import { _ as stepTimer, c as launchErrorReport, f as refuseNoProject, g as stepClock, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-CDAIFD-8.mjs";
6
+ import { B as settingsLayers, C as metroWarmupUrlSetting, F as settingFile, G as detectProviders, H as unknownSettingKeys, J as artifactCachePolicy, L as settingOriginScope, N as resolveCacheProviderConfig, P as resolveSettings, R as settingShapeErrors, T as parseIosRemote, Tt as removeTemporaryEntry, V as tunnelModeSetting, X as optimizationBuildProfile, Y as compilerCacheFallbackMessage, Z as resolveOptimizations, gt as repoRoot, i as SETTING_SHAPE_REMEDY, it as gitCommonDir, j as remoteAndroidSetting, k as publicUrlSetting, l as androidDataPartitionSizeGbSettingError, o as androidAvdConfigSettingError, u as cacheProviderSettingError, wt as makeTemporaryDirectory, x as metroPortSetting } from "./settings-Cfd-0r2d.mjs";
7
+ import { d as projectShortcut, n as appProjectProblem, r as detectIsExpo, s as findProjectRoot$1, t as NO_PROJECT_REFUSAL } from "./project-D47Rh0ly.mjs";
8
+ import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-g8NqiJp-.mjs";
9
+ import { c as writeWorkspaceState, g as workspaceAgentDeviceDir, i as recordWorkspaceUse, l as emulatorLogFile, r as readWorkspaceState$1, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-DqcCbpE7.mjs";
10
+ import { t as budgetGate } from "./budget-DM984mnd.mjs";
11
+ import { A as providerDownloadPath, B as captureAssetManifest, D as fingerprintProject, E as fingerprintDiffRecord, F as storedAssetManifest, H as readAssetManifest, L as untrackedMissLine, M as refingerprintAfterMutation, N as resolveBuild, O as inputsChangedDuringBuild, P as storeBuild, R as untrackedNativeFiles, S as configInputsChanged, T as filesystemBuildCapability, V as compareAssetManifests, _ as waitForBuild, b as buildCacheKey, g as releaseBuildLock, j as providerUploadOutcome, k as prepareProviderDownloadDir, m as acquireBuildLock, v as waitForSharedBuild, x as changedDuringBuildLine, z as assetDiffReason } from "./device-lease-DpoS36_u.mjs";
12
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-41v_d2x9.mjs";
13
+ import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-BKa8wNN5.mjs";
14
+ import { $ as trimAndroidCaches, A as listAvds, B as ownedAvdSystemImage, D as hostSystemImageArch, F as ownedAvdDeviceProfile, G as probeEmulatorSerial, I as ownedAvdDirectory, J as resolveOwnedAvdSerial, K as profileNeedsFoldFeature, O as listAdbDevices, Q as systemImageSupportsFold, T as findBuildTool, U as physicalDeviceModel, W as pickDefaultSystemImage, Y as resolvePhysicalDevice, a as androidDeviceAbi, ft as parkedMaxSetting, i as androidDataFreeBytes, j as listInstalledSystemImages, k as listAvdDeviceProfiles, q as resetAdoptedAvd, r as androidBuildToolName, rt as configuredAndroidEmulatorApp, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, u as androidToolCwd } from "./android-B_YNZy61.mjs";
12
15
  import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
13
- import { $ as trimAndroidCaches, A as listAvds, B as ownedAvdSystemImage, D as hostSystemImageArch, F as ownedAvdDeviceProfile, G as probeEmulatorSerial, I as ownedAvdDirectory, J as resolveOwnedAvdSerial, K as profileNeedsFoldFeature, O as listAdbDevices, Q as systemImageSupportsFold, T as findBuildTool, U as physicalDeviceModel, W as pickDefaultSystemImage, Y as resolvePhysicalDevice, a as androidDeviceAbi, dt as parkedMaxSetting, ht as configuredAndroidEmulatorApp, i as androidDataFreeBytes, j as listInstalledSystemImages, k as listAvdDeviceProfiles, q as resetAdoptedAvd, r as androidBuildToolName, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, u as androidToolCwd } from "./android-Db2eEMaf.mjs";
14
- import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-Di8EN2Tb.mjs";
15
- import { A as remoteAndroidSetting, B as unknownSettingKeys, I as settingOriginScope, J as optimizationBuildProfile, K as artifactCachePolicy, L as settingShapeErrors, M as resolveCacheProviderConfig, N as resolveSettings, O as publicUrlSetting, P as settingFile, R as settingsLayers, S as metroPortSetting, U as detectProviders, Y as resolveOptimizations, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, q as compilerCacheFallbackMessage, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, w as metroWarmupUrlSetting, z as tunnelModeSetting } from "./settings-CduVcoxj.mjs";
16
- import { i as teardownOwnedAvd } from "./teardown-BYuQafiA.mjs";
17
- import { c as recordFinishedBuild, d as createRunRecorder, f as readRunEstimates, h as statsProjectKey$1, l as startBuildProgress, m as recordRunStats, n as NO_BUILD_PROGRESS, u as tapBuildLog } from "./build-progress-C2Jv1AtA.mjs";
18
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-Df1ApSuH.mjs";
19
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-BkYxUtuj.mjs";
20
- import { S as restartedAppNote, b as launchAndroidReleaseApp, c as androidDevClientUrl, g as installConflictKind, h as installAndroidApp, r as DEFAULT_METRO_PORT, s as androidAppProcess, t as ADB_INSTALL_TIMEOUT_MS, y as launchAndroidApp } from "./app-install-CAAplXAt.mjs";
21
- import { t as checkDeviceCapacity } from "./device-capacity-DMuKC7nx.mjs";
22
- import { C as resolveSystemImage, S as resolveDeviceProfile, T as systemImageRefusal, _ as isReleaseVariant, a as PLATFORM, b as pooledAndroidDevice, c as androidSystemImageSetting, d as apkPackage, f as deviceProfileRefusal, g as foldableImageRefusal, h as findAapt, i as NO_FINGERPRINT, l as androidVariantSetting, m as dumpApkManifest, n as LAUNCH_FAILED, o as androidBuildOptions, p as displayPath, r as NO_DEVICE, s as androidDevClientScheme, t as INSTALL_FAILED, u as apkDevClientFacts, v as noDeviceDiagnostic, w as resolveVariant, x as remoteAvdFlagRefusal, y as parseXmltree } from "./support-CDQbDkL5.mjs";
23
- import { E as ccacheActivityLine, T as CCACHE_UNAVAILABLE, b as tailLines, k as resolveCcache, n as androidToolchain, t as androidRequirements, u as HEARTBEAT_INTERVAL_MS, w as CCACHE_NOT_RUN, y as startBuildHeartbeat } from "./toolchain-DmeYjXD9.mjs";
24
- import { t as cleanLine } from "./server-expo-DhtX6bom.mjs";
25
- import { a as offloadBuild, c as placementLoad, d as OffloadRefusal, f as namedBuildMachine, i as closeOffload, l as remotePhaseText, m as resolveBuildPlacement, n as buildPlacementCandidates, p as parseBuildMachineOption, r as chooseBuildMachine, s as offloadPlacement } from "./client-C2IS36Qf.mjs";
26
- import { t as spawnEntry } from "./spawn-entry-DncpJvWL.mjs";
27
- import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-BvxDlD6C.mjs";
28
- import { c as formatDiagnostic, i as buildAndroid, o as productFlavorRefusal, s as readProductFlavors } from "./gradle-CmdOBq1d.mjs";
29
- import { a as easAuthNote, c as loadProjectProvider, f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as resolveRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS } from "./remote-cache-CO2elm_b.mjs";
30
- import { a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, w as ownedSessionName } from "./device-remote-9ulPsdhY.mjs";
31
- import { a as VERIFY_TIMEOUT_MS, d as verifyAndroidReleaseLaunch, f as verifyLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unattributedLaunchLines, n as LAUNCH_FATAL, r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING, u as unverifiedLaunchLines } from "./launch-verify-CLlofxRt.mjs";
32
- import { d as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-DFDoIRbY.mjs";
33
- import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-Ciluxlq4.mjs";
34
- import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-CUD9nasA.mjs";
35
- import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-B2r38YjS.mjs";
36
- import { _ as AvdRecoveryError, a as refusePlan, c as skippedMissReason, d as refreshUpdatesManifestFile, g as AvdBootError, h as ensureOwnedDevice, i as printPlan, l as UPDATES_MANIFEST_NAME, m as ensureBooted, n as planFlagRefusal, o as explainBuildMiss, p as didSetUpDevice, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, u as detectEntryFile, v as warmMetro } from "./build-plan-aeGMFX7e.mjs";
37
- import { t as budgetGate } from "./budget-CMiqghig.mjs";
38
- import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-i6OkVg-w.mjs";
39
- import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-BY0BrOLh.mjs";
40
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-Cb0P1WLS.mjs";
41
- import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-PnKfAdB2.mjs";
16
+ import { m as teardownOwnedAvd } from "./ownership-BMLqyBgJ.mjs";
17
+ import { C as recordRunStats, _ as tapBuildLog, g as startBuildProgress, h as recordFinishedBuild, l as NO_BUILD_PROGRESS, v as createRunRecorder, w as statsProjectKey$1, x as recordCapacityRefusal, y as readRunEstimates } from "./idle-B2m-Ovod.mjs";
18
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
19
+ import { E as ccacheActivityLine, T as CCACHE_UNAVAILABLE, b as tailLines, k as resolveCcache, n as androidToolchain, t as androidRequirements, u as HEARTBEAT_INTERVAL_MS, w as CCACHE_NOT_RUN, y as startBuildHeartbeat } from "./toolchain-CNIBTIOv.mjs";
20
+ import { t as cleanLine } from "./server-expo-CHTyXD0G.mjs";
21
+ import { _ as resolveBuildPlacement, a as chooseBuildMachine, d as remotePhaseText, h as parseBuildMachineOption, i as buildPlacementCandidates, l as offloadPlacement, m as namedBuildMachine, o as closeOffload, p as OffloadRefusal, s as offloadBuild, u as placementLoad } from "./machines-C2XiNwh9.mjs";
22
+ import { r as hostedMetroSettings } from "./metro-gateway-DmslUM30.mjs";
23
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-DyRQru0q.mjs";
24
+ import { n as DeviceAdmissionRefusal, r as checkDeviceCapacity, t as DEFAULT_DEVICE_SLOT_WAIT_MS } from "./device-capacity-D0GWHnTM.mjs";
25
+ import { S as restartedAppNote, b as launchAndroidReleaseApp, c as androidDevClientUrl, g as installConflictKind, h as installAndroidApp, r as DEFAULT_METRO_PORT, s as androidAppProcess, t as ADB_INSTALL_TIMEOUT_MS, y as launchAndroidApp } from "./app-install-VPkI5WEg.mjs";
26
+ import { C as resolveSystemImage, S as resolveDeviceProfile, T as systemImageRefusal, _ as isReleaseVariant, a as PLATFORM, b as pooledAndroidDevice, c as androidSystemImageSetting, d as apkPackage, f as deviceProfileRefusal, g as foldableImageRefusal, h as findAapt, i as NO_FINGERPRINT, l as androidVariantSetting, m as dumpApkManifest, n as LAUNCH_FAILED, o as androidBuildOptions, p as displayPath, r as NO_DEVICE, s as androidDevClientScheme, t as INSTALL_FAILED, u as apkDevClientFacts, v as noDeviceDiagnostic, w as resolveVariant, x as remoteAvdFlagRefusal, y as parseXmltree } from "./support-F9-sr0Oi.mjs";
27
+ import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
28
+ import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-D7RUMkwN.mjs";
29
+ import { c as formatDiagnostic, i as buildAndroid, o as productFlavorRefusal, s as readProductFlavors } from "./gradle-D7-L2Th3.mjs";
30
+ import { a as VERIFY_TIMEOUT_MS, d as verifyAndroidReleaseLaunch, f as verifyLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unattributedLaunchLines, n as LAUNCH_FATAL, r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING, u as unverifiedLaunchLines } from "./launch-verify-lTnWZikR.mjs";
31
+ import { a as acquireRunLease, c as lostRefusal, d as runLease, f as waitFlagConflict, l as parseDeviceWait, o as leaseExpiryText, s as lostLine, t as DEBUG_VERIFY_STEP_MS, u as releaseLeaseOnSignal } from "./device-lease-run-B8YltIH_.mjs";
32
+ import { a as writeHostedAndroid, i as writeDevicePlacement, t as readHostedAndroid } from "./ios-state-8L3lgum6.mjs";
33
+ import { _ as AvdRecoveryError, a as refusePlan, b as devicePlacementLine, c as skippedMissReason, d as refreshUpdatesManifestFile, g as AvdBootError, h as ensureOwnedDevice, i as printPlan, l as UPDATES_MANIFEST_NAME, m as ensureBooted, n as planFlagRefusal, o as explainBuildMiss, p as didSetUpDevice, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, u as detectEntryFile, v as warmMetro, y as automaticDevicePlacement } from "./build-plan-CmQCoyT-.mjs";
34
+ import { n as prepareHostedAndroid, t as placeHostedAndroid } from "./hosted-android-DwLS4Mse.mjs";
35
+ import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-DmjBmpQV.mjs";
36
+ import { a as easAuthNote, c as loadProjectProvider, f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as resolveRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS } from "./remote-cache-Ci4DQYYj.mjs";
37
+ import { a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, w as ownedSessionName } from "./device-remote-hFSkpfQu.mjs";
38
+ import { d as runCancellationSignal, f as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-D1Q56rAD.mjs";
39
+ import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-BWNrc3M3.mjs";
40
+ import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-uee1wWjY.mjs";
41
+ import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-DhJI3IVQ.mjs";
42
+ import { t as selectFromPool } from "./device-pool-3o7s5lo4.mjs";
43
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-C-NMnhrk.mjs";
44
+ import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-ZFpjJBL_.mjs";
42
45
  import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
43
46
  import { basename, dirname, isAbsolute, join } from "node:path";
47
+ import { workspaceId } from "@stim-cli/core";
44
48
  import { InvalidArgumentError } from "commander";
45
- import { buildDiagnostics, machineCapacity } from "@stim-cli/core/state";
49
+ import { buildDiagnostics, hostedAndroidStatus, machineCapacity, parseMachine } from "@stim-cli/core/state";
46
50
  import chalk from "chalk";
47
51
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
52
+ //#region src/commands/android/remote.ts
53
+ async function connectAndroidHosting({ root, slot, machine, release, metroCheck, selectors, read = readHostedAndroid, resolveSerial = resolveOwnedAvdSerial, prepare = prepareHostedAndroid }) {
54
+ let recorded;
55
+ try {
56
+ recorded = read(root, slot)[slot];
57
+ } catch (error) {
58
+ return { failure: {
59
+ code: "STIM_HOSTING_REFUSED",
60
+ message: error.message,
61
+ remedy: `Restore that slot's recorded machine and session from the host, then run stim stop${slot === "default" ? "" : ` --slot ${slot}`} to reconcile it.`
62
+ } };
63
+ }
64
+ if (recorded && recorded.machine !== machine) return { failure: {
65
+ code: "STIM_BAD_ARG",
66
+ message: `This workspace's Android emulator runs on ${recorded.machine}; run stim stop first.`
67
+ } };
68
+ if (!machine) return { target: null };
69
+ if (!release && !metroCheck) return { failure: {
70
+ code: "STIM_BAD_ARG",
71
+ message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
72
+ remedy: "Run stim stop; stim start, then retry without --no-metro-check."
73
+ } };
74
+ const local = deviceSlotPlatforms(getProject(root), slot)?.android;
75
+ if (local?.owned && local.avdName) {
76
+ let running = true;
77
+ try {
78
+ const resolved = resolveSerial(local.avdName, { timeoutMs: 5e3 });
79
+ running = Boolean(resolved.serial) || !resolved.notRunning && !resolved.missing;
80
+ } catch {}
81
+ if (running) return { failure: {
82
+ code: "STIM_BAD_ARG",
83
+ message: `This workspace's Android emulator for slot ${slot} runs on this Mac; run stim stop first.`
84
+ } };
85
+ }
86
+ try {
87
+ return { target: await prepare(machine, selectors, recorded) };
88
+ } catch (error) {
89
+ return { failure: {
90
+ code: error.code ?? "STIM_HOSTING_REFUSED",
91
+ message: error.message,
92
+ ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
93
+ } };
94
+ }
95
+ }
96
+ async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, noWait, buildMachine, localSelectors, ...args }) {
97
+ const selectors = selected.kind === "hosted" ? {
98
+ ...selected.systemImage ? { systemImage: selected.systemImage } : {},
99
+ ...selected.deviceProfile ? { deviceProfile: selected.deviceProfile } : {}
100
+ } : {};
101
+ if (selected.kind !== "hosted" || selected.machine !== "auto") {
102
+ const connected = await connectAndroidHosting({
103
+ ...args,
104
+ selectors,
105
+ machine: selected.kind === "hosted" ? selected.machine : null
106
+ });
107
+ if ("failure" in connected) return connected;
108
+ return {
109
+ target: selected,
110
+ hostedTarget: connected.target,
111
+ selectors,
112
+ budget: connected.target ? {
113
+ reclaimed: [],
114
+ refusal: null
115
+ } : await checkBudget({
116
+ root: args.root,
117
+ note
118
+ })
119
+ };
120
+ }
121
+ const refusal = localSelectors(selected);
122
+ if (refusal) return { failure: refusal };
123
+ try {
124
+ const placed = await automatic({
125
+ root: args.root,
126
+ slot: args.slot,
127
+ platform: "android",
128
+ selectors,
129
+ buildMachine,
130
+ noWait
131
+ });
132
+ phase("placement:", devicePlacementLine(placed.placement, placed.skipped));
133
+ if (placed.target && !args.release && !args.metroCheck) return { failure: {
134
+ code: "STIM_BAD_ARG",
135
+ message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
136
+ remedy: "Run stim stop; stim start, then retry without --no-metro-check."
137
+ } };
138
+ return {
139
+ target: placed.target ? {
140
+ ...selected,
141
+ machine: placed.target.host.machine
142
+ } : {
143
+ kind: "emulator",
144
+ systemImage: selected.systemImage,
145
+ deviceProfile: selected.deviceProfile
146
+ },
147
+ hostedTarget: placed.target,
148
+ selectors,
149
+ budget: placed.target ? {
150
+ reclaimed: [],
151
+ refusal: null
152
+ } : await checkBudget({
153
+ root: args.root,
154
+ note
155
+ }),
156
+ devicePlacement: placed.placement
157
+ };
158
+ } catch (error) {
159
+ return { failure: {
160
+ code: error.code ?? "STIM_HOSTING_REFUSED",
161
+ message: error.message,
162
+ ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
163
+ } };
164
+ }
165
+ }
166
+ //#endregion
167
+ //#region src/commands/android/result.ts
168
+ function androidFacts({ root, slot, serial, avdName = null, deviceName = null, systemImage = null, deviceProfile = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease, devServer = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null, host }) {
169
+ return {
170
+ platform: PLATFORM,
171
+ ...host ? { host } : {},
172
+ ...slot && slot !== "default" ? { slot } : {},
173
+ serial: serial ?? null,
174
+ avdName: avdName ?? null,
175
+ deviceName: deviceName ?? avdName ?? null,
176
+ systemImage: systemImage ?? null,
177
+ deviceProfile: deviceProfile ?? null,
178
+ fingerprint: fingerprint ?? null,
179
+ cacheKey: cacheKey ?? null,
180
+ variant: variant ?? null,
181
+ metroPort: metroPort ?? null,
182
+ cacheHit: cacheLevel(cacheHit),
183
+ cacheSkipped: Boolean(cacheSkipped),
184
+ buildMachine,
185
+ ...builtOn ? { builtOn } : {},
186
+ ...offloadedTo ? { offloadedTo } : {},
187
+ ...offloadFallback ? { offloadFallback } : {},
188
+ waitedForBuild: waitedForBuild ? {
189
+ pid: waitedForBuild.pid ?? null,
190
+ ms: waitedForBuild.ms ?? 0
191
+ } : null,
192
+ appPath: appPath ?? null,
193
+ bundleId: bundleId ?? null,
194
+ installSkipped: Boolean(installSkipped),
195
+ launched: launched === "unverified" || launched === "bundling" ? launched : Boolean(launched),
196
+ ccache,
197
+ debugHttpHost: debugHttpHost ?? null,
198
+ debugHttpHostNote: debugHttpHostNote ?? null,
199
+ devClientUrl: devClientUrl ?? null,
200
+ logs: logs ?? null,
201
+ ...root && !host ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
202
+ durationMs: typeof durationMs === "number" && Number.isFinite(durationMs) ? durationMs : null,
203
+ ...lease === void 0 ? {} : { lease },
204
+ ...devServer ? { devServer } : {}
205
+ };
206
+ }
207
+ function lastBuildRecord({ devicePlacement, fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null, missReason = null, diagnostics = null, configuration = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
208
+ const record = {
209
+ platform: PLATFORM,
210
+ avdName: avdName ?? null,
211
+ deviceName: deviceName ?? avdName ?? null,
212
+ fingerprint: fingerprint ?? null,
213
+ cacheKey: cacheKey ?? null,
214
+ cacheHit: cacheLevel(cacheHit),
215
+ cacheSkipped: Boolean(cacheSkipped),
216
+ durationMs: Number.isFinite(durationMs) ? durationMs : null,
217
+ appPath: appPath ?? null,
218
+ bundleId: bundleId ?? null,
219
+ startedAt,
220
+ status,
221
+ configuration: configuration ?? null
222
+ };
223
+ if (errorCode) record.errorCode = errorCode;
224
+ if (devicePlacement) record.devicePlacement = devicePlacement;
225
+ record.buildMachine = buildMachine;
226
+ if (builtOn) record.builtOn = builtOn;
227
+ if (offloadedTo) record.offloadedTo = offloadedTo;
228
+ if (offloadFallback) record.offloadFallback = offloadFallback;
229
+ if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
230
+ const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
231
+ if (recorded.length) record.diagnostics = recorded;
232
+ return record;
233
+ }
234
+ async function finishAndroidUpload(uploadPending, remote, phase) {
235
+ if (!uploadPending) return false;
236
+ const upload = await uploadPending;
237
+ if (upload?.uploaded) phase("cache", `uploaded (${remote?.name})`);
238
+ else if (upload?.timedOut) {
239
+ phase("cache", chalk.yellow(`${remote?.name} upload still running after ${formatDuration(UPLOAD_TIMEOUT_MS)}; not waiting`));
240
+ return true;
241
+ } else if (upload?.failed) {
242
+ const authNote = remote?.name === "eas" && isEasAuthFailureText(upload.failed) ? easAuthNote({
243
+ code: "logged-out",
244
+ reason: upload.failed,
245
+ phase: "upload"
246
+ }) : null;
247
+ phase("cache", chalk.yellow(authNote || `${remote?.name} upload failed: ${upload.failed}`));
248
+ }
249
+ return false;
250
+ }
251
+ function reportAndroidResult({ host, root, slot, json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun, reclaimed = [], devServer = null, links }) {
252
+ const offloadedTo = record.offloadedTo ?? null;
253
+ recordRun({
254
+ failed: false,
255
+ cacheHit: cacheLevel(record.cacheHit),
256
+ waited: waitedForBuild,
257
+ durationMs,
258
+ offloadedTo
259
+ });
260
+ const facts = androidFacts({
261
+ host,
262
+ root,
263
+ slot,
264
+ serial,
265
+ avdName: record.avdName,
266
+ deviceName: record.deviceName,
267
+ systemImage: record.systemImage,
268
+ deviceProfile: record.deviceProfile,
269
+ debugHttpHost: launched.debugHttpHost ?? null,
270
+ debugHttpHostNote: launched.debugHttpHostNote ?? null,
271
+ devClientUrl: launched.devClientUrl ?? null,
272
+ fingerprint: record.fingerprint,
273
+ cacheKey: record.cacheKey,
274
+ variant,
275
+ metroPort,
276
+ cacheHit: record.cacheHit,
277
+ cacheSkipped: !useBuildCache,
278
+ waitedForBuild,
279
+ appPath: apkPath,
280
+ bundleId: androidPackage,
281
+ installSkipped,
282
+ launched: launchState,
283
+ ccache,
284
+ logs: logsDir,
285
+ durationMs,
286
+ lease,
287
+ devServer,
288
+ buildMachine: record.buildMachine,
289
+ builtOn: record.builtOn ?? void 0,
290
+ offloadedTo,
291
+ offloadFallback: record.offloadFallback ?? null
292
+ });
293
+ writer.close();
294
+ if (record.devicePlacement) facts.devicePlacement = record.devicePlacement;
295
+ if (json) emit(JSON.stringify({
296
+ ...facts,
297
+ ...links ? { links } : {},
298
+ ...reclaimed.length ? { reclaimed } : {}
299
+ }));
300
+ else {
301
+ const summary = `${launchWarning ? "WARNING" : "OK"}: ${androidPackage} launched on ${host ? `${host.device?.name ?? "Android emulator"} on ${host.machine}` : serial}, ${release ? `${variant} (embedded JS, no Metro)` : `Metro port ${metroPort}`} (${cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo)})`;
302
+ const outcome = launchWarning ? chalk.yellow(`${summary} -- ${launchWarning}`) : launchState === "unverified" ? chalk.yellow(`${summary} -- launch UNVERIFIED`) : launchState === "bundling" ? chalk.green(`${summary} -- bundle requested, still building`) : chalk.green(summary);
303
+ const deviceName = record.avdName || record.deviceName || serial;
304
+ const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo) : `bypassed; ${cacheOutcome(false, null, offloadedTo)}`;
305
+ const metroResult = release ? `embedded (${variant})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}${devServer ? ` (started: ${devServer.reason})` : ""}`;
306
+ emit([
307
+ outcome,
308
+ phaseLine("device", host ? `${deviceName} on ${host.machine}` : `${deviceName} (${serial})`),
309
+ phaseLine("app", androidPackage),
310
+ phaseLine("metro", metroResult),
311
+ phaseLine("cache", cacheResult),
312
+ ...ccache.status === "not-run" ? [phaseLine("compilation cache", ccacheActivityLine(ccache))] : [],
313
+ phaseLine("logs", logsDir || "unavailable (remote device)"),
314
+ ...!host ? [phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${workspaceAgentDeviceDir(root)}`)] : []
315
+ ].join("\n"));
316
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
317
+ }
318
+ return facts;
319
+ }
320
+ function cacheOutcome(cacheHit, providerName = null, offloadedTo = null) {
321
+ if (cacheHit === "remote") return `cache hit from ${providerName || "the remote cache"}`;
322
+ if (cacheHit === "local") return "cache hit";
323
+ return offloadedTo ? `built on ${offloadedTo}` : "built";
324
+ }
325
+ function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, startedAt, durationMs, status, errorCode = null, diagnostics = null, out }) {
326
+ const lastBuild = lastBuildRecord({
327
+ ...record,
328
+ startedAt,
329
+ durationMs,
330
+ status,
331
+ errorCode,
332
+ diagnostics
333
+ });
334
+ try {
335
+ recordBuild(root, lastBuild);
336
+ } catch (err) {
337
+ out(phaseLine("state", chalk.yellow(`could not record lastBuild: ${err?.message || err}`)));
338
+ }
339
+ return lastBuild;
340
+ }
341
+ //#endregion
342
+ //#region src/commands/android/hosted.ts
343
+ async function finishHostedAndroidRun({ target, artifact, root, slot = "default", release, isExpo, metroPort, logsDir, selectors, place, writePlacement, verifyLaunch: verify, writeLaunch, readApkPackage, resolveDevClientScheme, now, started, startedAt, fail, phase, out, recordBuild, ...report }) {
344
+ const packageName = artifact.androidPackage ?? readApkPackage(artifact.apkPath);
345
+ if (!artifact.apkPath || !packageName) return fail("STIM_INSTALL_FAILED", "The built APK has no package identity.");
346
+ const launchedAt = now();
347
+ try {
348
+ const run = await place(target, {
349
+ root,
350
+ slot,
351
+ bundle: artifact.apkPath,
352
+ handoff: artifact.handoff,
353
+ bundleId: packageName,
354
+ release,
355
+ metroPort,
356
+ selectors,
357
+ ...!release && isExpo ? { devClientScheme: resolveDevClientScheme(root, artifact.apkPath) ?? void 0 } : {},
358
+ reserved: (placement) => writePlacement(root, slot, placement),
359
+ note: out
360
+ });
361
+ writePlacement(root, slot, run.placement);
362
+ writeLaunch(root, "android", {
363
+ appId: packageName,
364
+ deviceId: run.placement.session,
365
+ metroPort,
366
+ release,
367
+ launchedAt: new Date(launchedAt).toISOString()
368
+ });
369
+ let launched = run.launched;
370
+ if (!release) {
371
+ const evidence = await verify({
372
+ requireBundleResponse: true,
373
+ platform: "android",
374
+ logsDir: logsDir ?? void 0,
375
+ since: launchedAt,
376
+ metroPort,
377
+ mode: isExpo ? MODE_EXPO : MODE_BARE,
378
+ slot: launchSlotScope(root, slot),
379
+ platformShared: siblingPlatformSlots(root, "android", slot).length > 0
380
+ });
381
+ if (evidence.fatal) return fail("STIM_LAUNCH_FAILED", "Metro could not deliver the hosted app bundle.", "Run stim logs --errors and fix the bundle error.");
382
+ launched = evidence.verified ? true : evidence.requested ? "bundling" : "unverified";
383
+ }
384
+ if (launched === "unverified") out(`Launch on ${target.host.machine} is unverified; check the hosted app, then retry stim android --remote ${target.host.machine}.`);
385
+ const host = hostedAndroidStatus(run.placement);
386
+ report.record.deviceName = host.device?.name ?? null;
387
+ report.record.bundleId = packageName;
388
+ persistLastBuild({
389
+ recordBuild,
390
+ root,
391
+ record: report.record,
392
+ startedAt,
393
+ durationMs: now() - started,
394
+ status: "success",
395
+ out
396
+ });
397
+ await finishAndroidUpload(artifact.uploadPending, artifact.remote, phase);
398
+ await artifact.providerUpload;
399
+ return {
400
+ ok: true,
401
+ facts: reportAndroidResult({
402
+ ...report,
403
+ root,
404
+ slot,
405
+ release,
406
+ metroPort,
407
+ logsDir,
408
+ host,
409
+ serial: null,
410
+ apkPath: artifact.apkPath,
411
+ androidPackage: packageName,
412
+ installSkipped: false,
413
+ waitedForBuild: artifact.waitedForBuild,
414
+ remote: artifact.remote,
415
+ providerName: artifact.providerName,
416
+ launchState: launched,
417
+ launched: {},
418
+ ccache: artifact.ccache,
419
+ durationMs: now() - started
420
+ })
421
+ };
422
+ } catch (error) {
423
+ return fail(error.code ?? "STIM_HOSTING_REFUSED", error.message, `Retry stim android --remote ${target.host.machine}, or run stim stop to reconcile the recorded placement.`);
424
+ } finally {
425
+ if (artifact.swapDir) rmSync(artifact.swapDir, {
426
+ recursive: true,
427
+ force: true
428
+ });
429
+ }
430
+ }
431
+ //#endregion
48
432
  //#region src/engine/apk-swap.ts
49
433
  const ANDROID_BUNDLE_NAME = "index.android.bundle";
50
434
  const LAST_LINES = 5;
@@ -419,10 +803,11 @@ function fail$1(code, message, remedy, extra = {}) {
419
803
  extra
420
804
  };
421
805
  }
422
- async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo, device, physical, remote: remoteTarget, buildPlan, cacheProviderConfig, requestedBuildCache, easBuild, androidPackage: initialPackage, record, maxBuilds, progress }, { deviceAbi, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, loadCacheProviderModule, acquireSlot, releaseSlot, planPrebuildFor, prebuild, build, ccacheFor, swapApk, readState, now }) {
423
- const { phase, out, estimates, stats, step, miss, hit: lateHit, place, waitingOn } = progress;
806
+ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo, device, physical, hostedAbi, remote: remoteTarget, buildPlan, cacheProviderConfig, requestedBuildCache, easBuild, androidPackage: initialPackage, record, maxBuilds, progress }, { deviceAbi, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, loadCacheProviderModule, acquireSlot, releaseSlot, planPrebuildFor, prebuild, build, ccacheFor, swapApk, readState, now }) {
807
+ const { phase, out, estimates, stats, step, miss, hit: lateHit, place, waitingOn, waitingFor } = progress;
424
808
  let fallbackMachine = null;
425
- let hereReason = null;
809
+ let hereReason = "no build machine is paired";
810
+ let slotWaitMs;
426
811
  const buildMachine = record.buildMachine ?? "auto";
427
812
  const fallBack = (reason, line = reason) => {
428
813
  if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
@@ -469,7 +854,8 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
469
854
  deviceAbi,
470
855
  compiler: cas?.id,
471
856
  buildProfile,
472
- targetAbiOnly: buildPlan.targetAbiOnly
857
+ targetAbiOnly: buildPlan.targetAbiOnly,
858
+ hostedAbi
473
859
  });
474
860
  let hash = "";
475
861
  let providerUpload = null;
@@ -486,6 +872,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
486
872
  let storeKey = "";
487
873
  let storeSources = [];
488
874
  let apkPath = null;
875
+ let handoff = null;
489
876
  let swapFellBack = false;
490
877
  async function resolveInitialFingerprint() {
491
878
  if (easBuild?.ok) {
@@ -500,7 +887,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
500
887
  stats.setCacheKey(cacheKey);
501
888
  return true;
502
889
  }
503
- step("cache-lookup");
890
+ if (useBuildCache) step("cache-lookup");
504
891
  const fingerprintTimer = stepTimer(now);
505
892
  try {
506
893
  const computed = await fingerprint(root, { platform: PLATFORM });
@@ -726,8 +1113,10 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
726
1113
  max: maxBuilds,
727
1114
  root,
728
1115
  logFile: buildLog,
729
- out
1116
+ out,
1117
+ waitingFor: (info) => waitingFor?.(info, "build-slot")
730
1118
  });
1119
+ slotWaitMs = buildSlot.slotWaitMs;
731
1120
  } catch (err) {
732
1121
  const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
733
1122
  if (refusal) {
@@ -868,6 +1257,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
868
1257
  }
869
1258
  const { timings } = outcome;
870
1259
  apkPath = stored;
1260
+ handoff = outcome.handoff ?? null;
871
1261
  record.builtOn = outcome.machine;
872
1262
  record.offloadedTo = outcome.machine;
873
1263
  stats.setPlacement({
@@ -974,12 +1364,16 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
974
1364
  }
975
1365
  if (offload) stats.setPlacement({
976
1366
  decision: "fell-back",
1367
+ slotWaitMs,
1368
+ deviceSlotWaitMs: stats.deviceSlotWaitMs(),
977
1369
  reason: record.offloadFallback ?? "offload failed",
978
1370
  ...fallbackMachine ? { machine: fallbackMachine } : {}
979
1371
  });
980
- else if (hereReason) stats.setPlacement({
1372
+ else stats.setPlacement({
981
1373
  decision: "here",
982
- reason: hereReason
1374
+ reason: hereReason,
1375
+ slotWaitMs,
1376
+ deviceSlotWaitMs: stats.deviceSlotWaitMs()
983
1377
  });
984
1378
  record.builtOn = "here";
985
1379
  phase("build", `compiling ${variant || "debug"} with Gradle`);
@@ -1078,221 +1472,54 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1078
1472
  phase("cache", chalk.yellow(`could not store the build: ${err?.message || err}`));
1079
1473
  }
1080
1474
  }
1081
- if (remote) uploadPending = uploadRemoteBuild({
1082
- logWriter: writer,
1083
- provider: remote.provider,
1084
- platform: PLATFORM,
1085
- projectRoot: root,
1086
- fingerprintHash: storeHash,
1087
- buildPath: apkPath,
1088
- runOptions: remoteRunOptions
1089
- });
1090
- }
1091
- }
1092
- } catch (error) {
1093
- if (!(error instanceof OffloadRefusal)) throw error;
1094
- const refusal = error;
1095
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1096
- return false;
1097
- } finally {
1098
- if (openOffload.choice) closeOffload(openOffload.choice);
1099
- releaseHeldLock();
1100
- releaseHeldSlot();
1101
- }
1102
- return true;
1103
- }
1104
- if (!easBuild) {
1105
- await prepareCachedArtifact();
1106
- if (apkPath) lateHit();
1107
- else if (swapFellBack) miss(reasonForMiss([]).reason);
1108
- if (!await buildArtifact()) return refused();
1109
- }
1110
- return {
1111
- ok: true,
1112
- artifact: {
1113
- apkPath,
1114
- androidPackage,
1115
- swapDir,
1116
- waitedForBuild,
1117
- ccache: ccacheActivity,
1118
- uploadPending,
1119
- providerUpload,
1120
- providerName,
1121
- remote,
1122
- abandonedRemote
1123
- }
1124
- };
1125
- }
1126
- function tail(lines, n = FALLBACK_LINES) {
1127
- return (Array.isArray(lines) ? lines : []).slice(-n);
1128
- }
1129
- //#endregion
1130
- //#region src/commands/android/result.ts
1131
- function androidFacts({ slot, serial, avdName = null, deviceName = null, systemImage = null, deviceProfile = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease, devServer = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
1132
- return {
1133
- platform: PLATFORM,
1134
- ...slot && slot !== "default" ? { slot } : {},
1135
- serial: serial ?? null,
1136
- avdName: avdName ?? null,
1137
- deviceName: deviceName ?? avdName ?? null,
1138
- systemImage: systemImage ?? null,
1139
- deviceProfile: deviceProfile ?? null,
1140
- fingerprint: fingerprint ?? null,
1141
- cacheKey: cacheKey ?? null,
1142
- variant: variant ?? null,
1143
- metroPort: metroPort ?? null,
1144
- cacheHit: cacheLevel(cacheHit),
1145
- cacheSkipped: Boolean(cacheSkipped),
1146
- buildMachine,
1147
- ...builtOn ? { builtOn } : {},
1148
- ...offloadedTo ? { offloadedTo } : {},
1149
- ...offloadFallback ? { offloadFallback } : {},
1150
- waitedForBuild: waitedForBuild ? {
1151
- pid: waitedForBuild.pid ?? null,
1152
- ms: waitedForBuild.ms ?? 0
1153
- } : null,
1154
- appPath: appPath ?? null,
1155
- bundleId: bundleId ?? null,
1156
- installSkipped: Boolean(installSkipped),
1157
- launched: launched === "unverified" || launched === "bundling" ? launched : Boolean(launched),
1158
- ccache,
1159
- debugHttpHost: debugHttpHost ?? null,
1160
- debugHttpHostNote: debugHttpHostNote ?? null,
1161
- devClientUrl: devClientUrl ?? null,
1162
- logs: logs ?? null,
1163
- durationMs: typeof durationMs === "number" && Number.isFinite(durationMs) ? durationMs : null,
1164
- ...lease === void 0 ? {} : { lease },
1165
- ...devServer ? { devServer } : {}
1166
- };
1167
- }
1168
- function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null, missReason = null, diagnostics = null, configuration = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
1169
- const record = {
1170
- platform: PLATFORM,
1171
- avdName: avdName ?? null,
1172
- deviceName: deviceName ?? avdName ?? null,
1173
- fingerprint: fingerprint ?? null,
1174
- cacheKey: cacheKey ?? null,
1175
- cacheHit: cacheLevel(cacheHit),
1176
- cacheSkipped: Boolean(cacheSkipped),
1177
- durationMs: Number.isFinite(durationMs) ? durationMs : null,
1178
- appPath: appPath ?? null,
1179
- bundleId: bundleId ?? null,
1180
- startedAt,
1181
- status,
1182
- configuration: configuration ?? null
1183
- };
1184
- if (errorCode) record.errorCode = errorCode;
1185
- record.buildMachine = buildMachine;
1186
- if (builtOn) record.builtOn = builtOn;
1187
- if (offloadedTo) record.offloadedTo = offloadedTo;
1188
- if (offloadFallback) record.offloadFallback = offloadFallback;
1189
- if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
1190
- const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
1191
- if (recorded.length) record.diagnostics = recorded;
1192
- return record;
1193
- }
1194
- async function finishAndroidUpload(uploadPending, remote, phase) {
1195
- if (!uploadPending) return false;
1196
- const upload = await uploadPending;
1197
- if (upload?.uploaded) phase("cache", `uploaded (${remote?.name})`);
1198
- else if (upload?.timedOut) {
1199
- phase("cache", chalk.yellow(`${remote?.name} upload still running after ${formatDuration(UPLOAD_TIMEOUT_MS)}; not waiting`));
1475
+ if (remote) uploadPending = uploadRemoteBuild({
1476
+ logWriter: writer,
1477
+ provider: remote.provider,
1478
+ platform: PLATFORM,
1479
+ projectRoot: root,
1480
+ fingerprintHash: storeHash,
1481
+ buildPath: apkPath,
1482
+ runOptions: remoteRunOptions
1483
+ });
1484
+ }
1485
+ }
1486
+ } catch (error) {
1487
+ if (!(error instanceof OffloadRefusal)) throw error;
1488
+ const refusal = error;
1489
+ phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1490
+ return false;
1491
+ } finally {
1492
+ if (openOffload.choice) closeOffload(openOffload.choice);
1493
+ releaseHeldLock();
1494
+ releaseHeldSlot();
1495
+ }
1200
1496
  return true;
1201
- } else if (upload?.failed) {
1202
- const authNote = remote?.name === "eas" && isEasAuthFailureText(upload.failed) ? easAuthNote({
1203
- code: "logged-out",
1204
- reason: upload.failed,
1205
- phase: "upload"
1206
- }) : null;
1207
- phase("cache", chalk.yellow(authNote || `${remote?.name} upload failed: ${upload.failed}`));
1208
1497
  }
1209
- return false;
1210
- }
1211
- function reportAndroidResult({ slot, json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun, reclaimed = [], devServer = null, links }) {
1212
- const offloadedTo = record.offloadedTo ?? null;
1213
- recordRun({
1214
- failed: false,
1215
- cacheHit: cacheLevel(record.cacheHit),
1216
- waited: waitedForBuild,
1217
- durationMs,
1218
- offloadedTo
1219
- });
1220
- const facts = androidFacts({
1221
- slot,
1222
- serial,
1223
- avdName: record.avdName,
1224
- deviceName: record.deviceName,
1225
- systemImage: record.systemImage,
1226
- deviceProfile: record.deviceProfile,
1227
- debugHttpHost: launched.debugHttpHost ?? null,
1228
- debugHttpHostNote: launched.debugHttpHostNote ?? null,
1229
- devClientUrl: launched.devClientUrl ?? null,
1230
- fingerprint: record.fingerprint,
1231
- cacheKey: record.cacheKey,
1232
- variant,
1233
- metroPort,
1234
- cacheHit: record.cacheHit,
1235
- cacheSkipped: !useBuildCache,
1236
- waitedForBuild,
1237
- appPath: apkPath,
1238
- bundleId: androidPackage,
1239
- installSkipped,
1240
- launched: launchState,
1241
- ccache,
1242
- logs: logsDir,
1243
- durationMs,
1244
- lease,
1245
- devServer,
1246
- buildMachine: record.buildMachine,
1247
- builtOn: record.builtOn ?? void 0,
1248
- offloadedTo,
1249
- offloadFallback: record.offloadFallback ?? null
1250
- });
1251
- writer.close();
1252
- if (json) emit(JSON.stringify({
1253
- ...facts,
1254
- ...links ? { links } : {},
1255
- ...reclaimed.length ? { reclaimed } : {}
1256
- }));
1257
- else {
1258
- const summary = `${launchWarning ? "WARNING" : "OK"}: ${androidPackage} launched on ${serial}, ${release ? `${variant} (embedded JS, no Metro)` : `Metro port ${metroPort}`} (${cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo)})`;
1259
- const outcome = launchWarning ? chalk.yellow(`${summary} -- ${launchWarning}`) : launchState === "unverified" ? chalk.yellow(`${summary} -- launch UNVERIFIED`) : launchState === "bundling" ? chalk.green(`${summary} -- bundle requested, still building`) : chalk.green(summary);
1260
- const deviceName = record.avdName || record.deviceName || serial;
1261
- const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo) : `bypassed; ${cacheOutcome(false, null, offloadedTo)}`;
1262
- const metroResult = release ? `embedded (${variant})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}${devServer ? ` (started: ${devServer.reason})` : ""}`;
1263
- emit([
1264
- outcome,
1265
- phaseLine("device", `${deviceName} (${serial})`),
1266
- phaseLine("app", androidPackage),
1267
- phaseLine("metro", metroResult),
1268
- phaseLine("cache", cacheResult),
1269
- ...ccache.status === "not-run" ? [phaseLine("compilation cache", ccacheActivityLine(ccache))] : [],
1270
- phaseLine("logs", logsDir || "unavailable (remote device)")
1271
- ].join("\n"));
1272
- if (links) console.error(chalk.dim(workspaceLinkLine(links)));
1498
+ if (!easBuild) {
1499
+ await prepareCachedArtifact();
1500
+ if (apkPath) lateHit();
1501
+ else if (swapFellBack) miss(reasonForMiss([]).reason);
1502
+ if (!await buildArtifact()) return refused();
1273
1503
  }
1274
- return facts;
1275
- }
1276
- function cacheOutcome(cacheHit, providerName = null, offloadedTo = null) {
1277
- if (cacheHit === "remote") return `cache hit from ${providerName || "the remote cache"}`;
1278
- if (cacheHit === "local") return "cache hit";
1279
- return offloadedTo ? `built on ${offloadedTo}` : "built";
1504
+ return {
1505
+ ok: true,
1506
+ artifact: {
1507
+ apkPath,
1508
+ handoff,
1509
+ androidPackage,
1510
+ swapDir,
1511
+ waitedForBuild,
1512
+ ccache: ccacheActivity,
1513
+ uploadPending,
1514
+ providerUpload,
1515
+ providerName,
1516
+ remote,
1517
+ abandonedRemote
1518
+ }
1519
+ };
1280
1520
  }
1281
- function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, startedAt, durationMs, status, errorCode = null, diagnostics = null, out }) {
1282
- const lastBuild = lastBuildRecord({
1283
- ...record,
1284
- startedAt,
1285
- durationMs,
1286
- status,
1287
- errorCode,
1288
- diagnostics
1289
- });
1290
- try {
1291
- recordBuild(root, lastBuild);
1292
- } catch (err) {
1293
- out(phaseLine("state", chalk.yellow(`could not record lastBuild: ${err?.message || err}`)));
1294
- }
1295
- return lastBuild;
1521
+ function tail(lines, n = FALLBACK_LINES) {
1522
+ return (Array.isArray(lines) ? lines : []).slice(-n);
1296
1523
  }
1297
1524
  //#endregion
1298
1525
  //#region src/commands/android/collector.ts
@@ -1300,7 +1527,7 @@ function collectorEntry() {
1300
1527
  return spawnEntry("collector-run");
1301
1528
  }
1302
1529
  function collectorLogFile(root, slot = "default") {
1303
- return join(workspaceLogsDir(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
1530
+ return join(workspaceLogsDir$1(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
1304
1531
  }
1305
1532
  const COLLECTOR_EXIT_WAIT_MS = 2e3;
1306
1533
  const COLLECTOR_POLL_MS = 25;
@@ -1341,7 +1568,7 @@ async function startCollector({ root, slot = "default", serial, packageName, spa
1341
1568
  }
1342
1569
  let stdio = "ignore";
1343
1570
  try {
1344
- mkdirSync(workspaceLogsDir(root), { recursive: true });
1571
+ mkdirSync(workspaceLogsDir$1(root), { recursive: true });
1345
1572
  const fd = openSync(collectorLogFile(root, slot), "a");
1346
1573
  stdio = [
1347
1574
  "ignore",
@@ -1611,7 +1838,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1611
1838
  });
1612
1839
  if (booted.failed) {
1613
1840
  const diag = diagnoseBootFailure(booted, emuLog, runCommand, physical);
1614
- return fail(NO_DEVICE, diag.message, diag.remedy, {
1841
+ return fail(diag.code, diag.message, diag.remedy, {
1615
1842
  lines: diag.lines,
1616
1843
  logPath: diag.logPath ? displayPath(root, diag.logPath) : null
1617
1844
  });
@@ -1877,6 +2104,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1877
2104
  const leaseFacts = lease?.facts() ?? null;
1878
2105
  releaseLease();
1879
2106
  const facts = reportAndroidResult({
2107
+ root,
1880
2108
  slot,
1881
2109
  json,
1882
2110
  useBuildCache,
@@ -1943,12 +2171,23 @@ function reportMetroRoute({ launched, remote, metroPort, androidPackage, serial,
1943
2171
  });
1944
2172
  }
1945
2173
  function diagnoseBootFailure(booted, logFile, runCommand, physical) {
1946
- return noDeviceDiagnostic({
2174
+ if (booted.code) return {
2175
+ code: booted.code,
2176
+ message: booted.reason ?? "The emulator did not boot.",
2177
+ remedy: booted.remedy ?? null,
2178
+ lines: [],
2179
+ logPath: null
2180
+ };
2181
+ const diag = noDeviceDiagnostic({
1947
2182
  reason: booted.reason ?? "The emulator did not boot.",
1948
2183
  logFile,
1949
2184
  remedy: booted.remedy ?? `Run \`stim status\` to see what Stim thinks it owns; re-running \`${runCommand}\` creates a fresh owned AVD.`,
1950
2185
  localEmulator: !physical
1951
2186
  });
2187
+ return {
2188
+ code: NO_DEVICE,
2189
+ ...diag
2190
+ };
1952
2191
  }
1953
2192
  //#endregion
1954
2193
  //#region src/commands/android/plan.ts
@@ -2037,13 +2276,78 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2037
2276
  if (physical && deviceFlag === "") return fail("STIM_BAD_ARG", "--device was given an empty serial.", "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <serial>` to name one.");
2038
2277
  if (physical && commandRemoteBackend) return fail("STIM_BAD_ARG", "--device installs on a device connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote.");
2039
2278
  const noWait = waitFlag === false;
2040
- const waitFlagged = waitFlag !== void 0;
2041
- if (waitConflict) return fail("STIM_BAD_ARG", "--wait and --no-wait ask for opposite things.", "Pass `--wait <seconds>` to wait for the lease, or `--no-wait` to install without one.");
2042
- if (waitFlagged && !physical) return fail("STIM_BAD_ARG", "--wait and --no-wait only apply to a `--device` run.", "This workspace owns its emulator, so nothing contends for it. Drop the flag, or pass `--device`.");
2279
+ if (waitConflict) return fail("STIM_BAD_ARG", "--wait and --no-wait ask for opposite things.", "Pass only one of `--wait <seconds>` and `--no-wait`.");
2043
2280
  const waitParsed = parseDeviceWait(noWait ? void 0 : waitFlag);
2044
- if ("error" in waitParsed) return fail("STIM_BAD_ARG", waitParsed.error, "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a leased device at once.");
2281
+ if ("error" in waitParsed) return fail("STIM_BAD_ARG", waitParsed.error, "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a busy lease or device slot at once.");
2045
2282
  const waitSeconds = waitParsed.seconds;
2046
- const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
2283
+ const remoteTarget = commandRemoteBackend !== null ? parseIosRemote(commandRemoteBackend) : remoteAndroidSetting(settings);
2284
+ if (physical && remoteTarget?.kind === "machine") return fail("STIM_BAD_ARG", "--device installs on a phone connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote; unset android.remote for a local phone.");
2285
+ const machine = remoteTarget?.kind === "machine" ? remoteTarget.machine : null;
2286
+ const remoteBackend = !physical && remoteTarget?.kind === "backend" ? remoteTarget.backend : null;
2287
+ if (!machine) {
2288
+ const refusal = androidDeviceSelectorRefusal({
2289
+ settingsContext,
2290
+ slot,
2291
+ systemImageFlag,
2292
+ deviceProfileFlag,
2293
+ systemImage,
2294
+ deviceProfile,
2295
+ physical,
2296
+ remoteBackend
2297
+ }, {
2298
+ listSystemImages,
2299
+ listDeviceProfiles,
2300
+ supportsFold,
2301
+ ownedAvd
2302
+ });
2303
+ if (refusal) return refusal;
2304
+ }
2305
+ const target = physical ? {
2306
+ kind: "physical",
2307
+ serial: typeof deviceFlag === "string" ? deviceFlag : null,
2308
+ lease: {
2309
+ waitSeconds,
2310
+ noWait
2311
+ }
2312
+ } : machine ? {
2313
+ kind: "hosted",
2314
+ machine,
2315
+ systemImage,
2316
+ deviceProfile
2317
+ } : remoteBackend ? {
2318
+ kind: "remote",
2319
+ backend: remoteBackend,
2320
+ systemImage,
2321
+ deviceProfile
2322
+ } : {
2323
+ kind: "emulator",
2324
+ systemImage,
2325
+ deviceProfile
2326
+ };
2327
+ return {
2328
+ ok: true,
2329
+ plan: {
2330
+ build: {
2331
+ variant,
2332
+ release,
2333
+ profile: buildProfile,
2334
+ cas,
2335
+ cache: cachePolicy,
2336
+ compilerCache: optimizations.android.compilerCache,
2337
+ gradleBuildCache: optimizations.android.gradleBuildCache,
2338
+ pch: optimizations.android.pch,
2339
+ targetAbiOnly: optimizations.android.targetAbiOnly
2340
+ },
2341
+ target,
2342
+ deviceSlotWaitMs: noWait ? 0 : waitFlag === void 0 && !physical ? DEFAULT_DEVICE_SLOT_WAIT_MS : waitSeconds * 1e3,
2343
+ isExpo,
2344
+ metroWarmup: optimizations.metroWarmup,
2345
+ cacheProviderConfig
2346
+ }
2347
+ };
2348
+ }
2349
+ function androidDeviceSelectorRefusal({ settingsContext, slot, systemImageFlag, deviceProfileFlag, systemImage, deviceProfile, physical, remoteBackend }, { listSystemImages = listInstalledSystemImages, listDeviceProfiles = listAvdDeviceProfiles, supportsFold = systemImageSupportsFold, ownedAvd = slotOwnedAvd } = {}) {
2350
+ const root = settingsContext.projectPath;
2047
2351
  const settingsLayersForOrigin = settingsLayers(settingsContext);
2048
2352
  const imageRefusal = systemImageRefusal({
2049
2353
  slot,
@@ -2085,43 +2389,7 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2085
2389
  });
2086
2390
  if (foldRefusal) return fail(foldRefusal.code, foldRefusal.message, foldRefusal.remedy);
2087
2391
  }
2088
- const target = physical ? {
2089
- kind: "physical",
2090
- serial: typeof deviceFlag === "string" ? deviceFlag : null,
2091
- lease: {
2092
- waitSeconds,
2093
- noWait
2094
- }
2095
- } : remoteBackend ? {
2096
- kind: "remote",
2097
- backend: remoteBackend,
2098
- systemImage,
2099
- deviceProfile
2100
- } : {
2101
- kind: "emulator",
2102
- systemImage,
2103
- deviceProfile
2104
- };
2105
- return {
2106
- ok: true,
2107
- plan: {
2108
- build: {
2109
- variant,
2110
- release,
2111
- profile: buildProfile,
2112
- cas,
2113
- cache: cachePolicy,
2114
- compilerCache: optimizations.android.compilerCache,
2115
- gradleBuildCache: optimizations.android.gradleBuildCache,
2116
- pch: optimizations.android.pch,
2117
- targetAbiOnly: optimizations.android.targetAbiOnly
2118
- },
2119
- target,
2120
- isExpo,
2121
- metroWarmup: optimizations.metroWarmup,
2122
- cacheProviderConfig
2123
- }
2124
- };
2392
+ return null;
2125
2393
  }
2126
2394
  //#endregion
2127
2395
  //#region src/commands/android/next-build.ts
@@ -2260,7 +2528,7 @@ async function planAndroid(opts, overrides = {}) {
2260
2528
  if (target.kind === "physical") return refuse(planFlagRefusal("--device"));
2261
2529
  return refuse({
2262
2530
  code: "STIM_BAD_ARG",
2263
- message: `android.remote routes this workspace's runs to a ${target.backend} device, whose ABI --plan cannot read without a session.`,
2531
+ message: `android.remote routes this workspace's runs to a ${target.kind === "hosted" ? target.machine : target.backend} device, whose ABI --plan cannot read without a session.`,
2264
2532
  remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
2265
2533
  });
2266
2534
  }
@@ -2316,10 +2584,10 @@ function androidCommand(program) {
2316
2584
  registerAndroid(program);
2317
2585
  }
2318
2586
  function registerAndroid(program) {
2319
- program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. A Debug run starts the dev server when it is not running.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--build-machine <value>", "Build on auto, local, or one named machine; a name refuses without fallback", parseBuildMachineOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--plan", "Predict the next build without building, booting or installing: resolve the fingerprint, check the local then remote cache, and print the expected cache result and duration. Writes no Stim state.").option("--no-metro-check", "Skip the reserved-port Metro health check and do not start it (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device-profile <id>", "Hardware profile to create this workspace's owned AVD with, as an `avdmanager list device -c` id (e.g. \"pixel_fold\", \"pixel_tablet\"). Overrides the android.deviceProfile setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the available ids.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <backend>", "Install and launch on a remote device with proxy or EAS. Builds are local unless --eas-profile selects an existing EAS build.", (value) => {
2320
- if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
2321
- throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
2322
- }).option("--wait <seconds>", "How long to wait for another workspace to release the device it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Only with --device.").option("--no-wait", "Install on a device another workspace leases instead of waiting: this run takes no lease and, when both workspaces build the same app id, the install terminates the holder's running app. Only with --device.").action(async (opts) => {
2587
+ program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. A Debug run starts the dev server when it is not running.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--build-machine <value>", "Build on auto, local, or one named machine; a name refuses without fallback", parseBuildMachineOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--plan", "Predict the next build without building, booting or installing: resolve the fingerprint, check the local then remote cache, and print the expected cache result and duration. Writes no Stim state.").option("--no-metro-check", "Skip the reserved-port Metro health check and do not start it (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device-profile <id>", "Hardware profile to create this workspace's owned AVD with, as an `avdmanager list device -c` id (e.g. \"pixel_fold\", \"pixel_tablet\"). Overrides the android.deviceProfile setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the available ids.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <target>", "Run on eas, proxy, or a named approved Mac from hosting.machines; auto places on an approved Mac when this Mac is full or busy", (value) => {
2588
+ if (!parseMachine(value)) throw new InvalidArgumentError("expected eas, proxy, auto, or a tailnet machine name");
2589
+ return value;
2590
+ }).option("--wait <seconds>", "How long to wait for another workspace to release the device it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Without --device, wait for an owned-device slot (default 600 seconds; --no-wait refuses immediately).").option("--no-wait", "Install on a device another workspace leases instead of waiting: this run takes no lease and, when both workspaces build the same app id, the install terminates the holder's running app. Without --device, refuse a full device cap or existing slot queue immediately.").action(async (opts) => {
2323
2591
  if (opts.plan) return planAndroid(opts);
2324
2592
  const root = findProjectRoot$1(process.cwd());
2325
2593
  if (!root) {
@@ -2363,7 +2631,7 @@ function registerAndroid(program) {
2363
2631
  }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) })).ok) process.exit(runCancellation() ? 130 : 1);
2364
2632
  });
2365
2633
  }
2366
- function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRemoteBackend = null, resolveSettingsFor = resolveSettings, resolveRemoteDeviceContext = resolveRemoteContext, remoteDeviceDeps: makeRemoteDeviceDeps = remoteAndroidDeps, resolveEasBin = resolveEasCliBin, ensureMetroReachable: ensureRemoteMetro = ensureMetroReachable, ensureRemoteBootOwned: ensureRemoteOwned = ensureRemoteBootOwned, detectRemoteProviders = detectProviders, metroCheck = true, useBuildCache = true, easProfile, resolveEasDevelopmentBuild: resolveEasBuild = resolveEasDevelopmentBuild, variant: variantFlag = null, systemImage: systemImageFlag = null, deviceProfile: deviceProfileFlag, device: deviceFlag = null, wait: waitFlag = void 0, waitConflict = false, acquireLease = acquireRunLease, makeRunLease = runLease, selectPool = selectFromPool, onLeaseSignal = releaseLeaseOnSignal, listDevices = listAdbDevices, deviceModel = physicalDeviceModel, deviceAbi = androidDeviceAbi, isEmulatorDevice = probeEmulatorSerial, readApkPackage = (apkPath) => apkPackage(dumpApkManifest(apkPath)), getLimits = getConcurrencyLimits, checkCapacity = checkDeviceCapacity, acquireSlot = acquireBuildSlot, releaseSlot = releaseBuildSlot, ensureDevice = ensureOwnedDevice, listSystemImages = listInstalledSystemImages, listDeviceProfiles, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, startServer = startDevServer, readState = readWorkspaceState$1, pidAlive = pidExists, verifyCollector = verifyCollectorOwnership, verifyLaunched = verifyLaunch, ensureStorage = ensureWorkspaceStorageSafely, fingerprint = fingerprintProject, untracked = untrackedNativeFiles, resolveCached = resolveBuild, storeCached = storeBuild, storedAssets = storedAssetManifest, captureAssets = captureAssetManifest, acquireLock = acquireBuildLock, releaseLock = releaseBuildLock, waitForBuild: waitForBuild$1 = waitForBuild, loadProvider = loadProjectProvider, easAuth = checkEasAuth, resolveRemoteBuild = resolveRemote, uploadRemoteBuild = uploadRemote, resolveCacheProvider = resolveCacheProviderConfig, loadCacheProviderModule = loadCacheProvider, planPrebuildFor = planPrebuild, prebuild = runPrebuild, build = buildAndroid, ccacheFor = resolveCcache, install = installAndroidApp, launch = launchAndroidApp, launchRelease = launchAndroidReleaseApp, verifyReleaseLaunched = verifyAndroidReleaseLaunch, swapApk = swapApkBundle, resolveDevClientScheme = androidDevClientScheme, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), createWriter = createNdjsonWriter, writeLaunch = writeWorkspaceLaunch, writeState = writeWorkspaceState, recordBuild, recordStats = recordRunStats, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
2634
+ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRemoteBackend = null, resolveSettingsFor = resolveSettings, resolveRemoteDeviceContext = resolveRemoteContext, remoteDeviceDeps: makeRemoteDeviceDeps = remoteAndroidDeps, resolveEasBin = resolveEasCliBin, ensureMetroReachable: ensureRemoteMetro = ensureMetroReachable, ensureRemoteBootOwned: ensureRemoteOwned = ensureRemoteBootOwned, detectRemoteProviders = detectProviders, metroCheck = true, useBuildCache = true, easProfile, resolveEasDevelopmentBuild: resolveEasBuild = resolveEasDevelopmentBuild, variant: variantFlag = null, systemImage: systemImageFlag = null, deviceProfile: deviceProfileFlag, device: deviceFlag = null, wait: waitFlag = void 0, waitConflict = false, acquireLease = acquireRunLease, makeRunLease = runLease, selectPool = selectFromPool, onLeaseSignal = releaseLeaseOnSignal, listDevices = listAdbDevices, deviceModel = physicalDeviceModel, deviceAbi = androidDeviceAbi, isEmulatorDevice = probeEmulatorSerial, readApkPackage = (apkPath) => apkPackage(dumpApkManifest(apkPath)), getLimits = getConcurrencyLimits, checkCapacity = checkDeviceCapacity, acquireSlot = acquireBuildSlot, releaseSlot = releaseBuildSlot, ensureDevice = ensureOwnedDevice, listSystemImages = listInstalledSystemImages, listDeviceProfiles, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, startServer = startDevServer, readState = readWorkspaceState$1, pidAlive = pidExists, verifyCollector = verifyCollectorOwnership, verifyLaunched = verifyLaunch, ensureStorage = ensureWorkspaceStorageSafely, fingerprint = fingerprintProject, untracked = untrackedNativeFiles, resolveCached = resolveBuild, storeCached = storeBuild, storedAssets = storedAssetManifest, captureAssets = captureAssetManifest, acquireLock = acquireBuildLock, releaseLock = releaseBuildLock, waitForBuild: waitForBuild$1 = waitForBuild, loadProvider = loadProjectProvider, easAuth = checkEasAuth, resolveRemoteBuild = resolveRemote, uploadRemoteBuild = uploadRemote, resolveCacheProvider = resolveCacheProviderConfig, loadCacheProviderModule = loadCacheProvider, planPrebuildFor = planPrebuild, prebuild = runPrebuild, build = buildAndroid, ccacheFor = resolveCcache, install = installAndroidApp, launch = launchAndroidApp, launchRelease = launchAndroidReleaseApp, verifyReleaseLaunched = verifyAndroidReleaseLaunch, swapApk = swapApkBundle, resolveDevClientScheme = androidDevClientScheme, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), createWriter = createNdjsonWriter, writeLaunch = writeWorkspaceLaunch, writeState = writeWorkspaceState, recordBuild, recordStats = recordRunStats, recordCapacityRefusal: recordRefusal, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
2367
2635
  return {
2368
2636
  root,
2369
2637
  json,
@@ -2447,6 +2715,7 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2447
2715
  writeState,
2448
2716
  recordBuild,
2449
2717
  recordStats,
2718
+ recordCapacityRefusal: recordRefusal,
2450
2719
  readEstimates,
2451
2720
  now,
2452
2721
  out,
@@ -2489,6 +2758,11 @@ function androidSlotOptions(options) {
2489
2758
  function avdSetupFailure(error, root, logFile) {
2490
2759
  const refusal = (0, ownership_claim_exports.claimFailure)(error, "stim android");
2491
2760
  if (refusal) return refusal;
2761
+ if (error instanceof DeviceAdmissionRefusal) return {
2762
+ code: error.code,
2763
+ message: error.message,
2764
+ remedy: error.remedy
2765
+ };
2492
2766
  const diag = noDeviceDiagnostic({
2493
2767
  reason: `Could not ensure an owned Android emulator: ${error?.message || error}`,
2494
2768
  logFile,
@@ -2506,7 +2780,7 @@ function avdSetupFailure(error, root, logFile) {
2506
2780
  };
2507
2781
  }
2508
2782
  async function runAndroid(options = {}) {
2509
- let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceProfileFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, listDeviceProfiles, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro, prewarmMetro, startServer, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, planPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordBuild, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
2783
+ let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceProfileFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, listDeviceProfiles, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro, prewarmMetro, startServer, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, planPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordBuild, recordStats, recordCapacityRefusal: recordRefusal = recordCapacityRefusal, readEstimates, now, out, emit } = androidSlotOptions(options);
2510
2784
  const progress = options.progress ?? NO_BUILD_PROGRESS;
2511
2785
  const slot = validateDeviceSlot(options.slot);
2512
2786
  const started = now();
@@ -2552,7 +2826,7 @@ async function runAndroid(options = {}) {
2552
2826
  }
2553
2827
  };
2554
2828
  }
2555
- const logsDir = workspaceLogsDir(root);
2829
+ const logsDir = workspaceLogsDir$1(root);
2556
2830
  const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
2557
2831
  const writer = tapBuildLog(createWriter(buildLog, {
2558
2832
  truncate: true,
@@ -2612,6 +2886,7 @@ async function runAndroid(options = {}) {
2612
2886
  code,
2613
2887
  message,
2614
2888
  remedy: remedy ?? null,
2889
+ ...record.devicePlacement ? { devicePlacement: record.devicePlacement } : {},
2615
2890
  ...ccacheActivity.status === "not-run" ? {} : { ccache: ccacheActivity },
2616
2891
  ...lease === void 0 ? {} : { lease },
2617
2892
  ...reclaimed.length ? { reclaimed } : {}
@@ -2673,16 +2948,59 @@ async function runAndroid(options = {}) {
2673
2948
  });
2674
2949
  if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
2675
2950
  const { plan } = planned;
2676
- const { build: buildPlan, target, isExpo, cacheProviderConfig } = plan;
2951
+ const deviceSlotWait = {
2952
+ signal: runCancellationSignal(),
2953
+ waitMs: plan.deviceSlotWaitMs,
2954
+ displayName: basename(root),
2955
+ waitingFor: (info) => progress.waitingFor(info, "device-slot"),
2956
+ onWait: (ms) => {
2957
+ stats.addDeviceSlotWaitMs(ms);
2958
+ if (record.devicePlacement?.decision === "local" && ms > 0) record.devicePlacement.decision = "waited-locally";
2959
+ }
2960
+ };
2961
+ const { build: buildPlan, isExpo, cacheProviderConfig } = plan;
2677
2962
  const { variant, release, cache: cachePolicy } = buildPlan;
2678
2963
  record.configuration = variant ?? "debug";
2679
2964
  const useBuildCache = cachePolicy.read;
2680
- const physical = target.kind === "physical";
2681
- const remoteBackend = target.kind === "remote" ? target.backend : null;
2682
- const budget = await (options.checkBudget ?? budgetGate)({
2965
+ const hosting = await selectAndroidPlacement({
2683
2966
  root,
2684
- note: out
2967
+ slot,
2968
+ target: plan.target,
2969
+ release,
2970
+ metroCheck,
2971
+ noWait: plan.deviceSlotWaitMs === 0,
2972
+ buildMachine: options.buildMachine,
2973
+ read: options.readHostedAndroid,
2974
+ resolveSerial: resolveAvdSerial,
2975
+ prepare: options.prepareHostedAndroid,
2976
+ automatic: options.automaticDevicePlacement,
2977
+ localSelectors: (target) => androidDeviceSelectorRefusal({
2978
+ settingsContext,
2979
+ slot,
2980
+ systemImageFlag,
2981
+ deviceProfileFlag,
2982
+ systemImage: target.systemImage,
2983
+ deviceProfile: target.deviceProfile,
2984
+ physical: false,
2985
+ remoteBackend: null
2986
+ }, {
2987
+ listSystemImages,
2988
+ listDeviceProfiles
2989
+ }),
2990
+ checkBudget: options.checkBudget,
2991
+ note: out,
2992
+ phase
2685
2993
  });
2994
+ if ("failure" in hosting) return fail(hosting.failure.code, hosting.failure.message, hosting.failure.remedy);
2995
+ const { target, hostedTarget, budget, selectors } = hosting;
2996
+ record.devicePlacement = hosting.devicePlacement;
2997
+ writeDevicePlacement(root, slot, PLATFORM, record.devicePlacement);
2998
+ const physical = target.kind === "physical";
2999
+ const remoteBackend = target.kind === "remote" ? target.backend : null;
3000
+ if (hostedTarget) {
3001
+ if (settings.androidEmulatorApp !== void 0) phase("device", "androidEmulatorApp is ignored on a hosting Mac; the emulator boots headless.");
3002
+ if (publicUrlSetting(settings) || tunnelModeSetting(settings)) phase("metro", "metro.publicUrl and metro.tunnel are ignored on a hosting Mac; Metro uses the private tailnet bridge.");
3003
+ }
2686
3004
  reclaimed = budget.reclaimed;
2687
3005
  if (budget.refusal) return fail(budget.refusal.code, budget.refusal.message, budget.refusal.remedy);
2688
3006
  const remoteContext = remoteBackend ? await resolveRemoteDeviceContext({
@@ -2725,7 +3043,7 @@ async function runAndroid(options = {}) {
2725
3043
  const gate = await ensureDevServer({
2726
3044
  root,
2727
3045
  port: reservedPort,
2728
- settings,
3046
+ settings: hostedMetroSettings(settings, Boolean(hostedTarget)),
2729
3047
  remote: remoteContext !== null,
2730
3048
  note: out,
2731
3049
  resolve: resolveMetro,
@@ -2779,7 +3097,14 @@ async function runAndroid(options = {}) {
2779
3097
  let bootDuration = "";
2780
3098
  let bootPromise;
2781
3099
  let startRemoteBoot = null;
2782
- if (target.kind === "physical" && !target.serial) {
3100
+ if (hostedTarget) {
3101
+ device = {
3102
+ owned: false,
3103
+ systemImage: hostedTarget.choice.systemImage,
3104
+ deviceProfile: hostedTarget.choice.deviceProfile
3105
+ };
3106
+ bootPromise = Promise.resolve({ ok: true });
3107
+ } else if (target.kind === "physical" && !target.serial) {
2783
3108
  const pooled = await pooledAndroidDevice({
2784
3109
  root,
2785
3110
  selectPool,
@@ -2815,12 +3140,19 @@ async function runAndroid(options = {}) {
2815
3140
  } catch (err) {
2816
3141
  return fail("STIM_BAD_ARG", err.message, "Run `stim settings set androidEmulatorApp emulator` or `stim settings set androidEmulatorApp stim-desktop`.");
2817
3142
  }
2818
- const capacity = checkCapacity({
3143
+ const capacity = plan.deviceSlotWaitMs === 0 ? checkCapacity({
2819
3144
  platform: PLATFORM,
2820
3145
  project,
2821
3146
  max: limits.maxDevices
2822
- });
2823
- if (capacity) return fail(capacity.code, capacity.message, capacity.remedy);
3147
+ }) : null;
3148
+ if (capacity) {
3149
+ if (capacity.code === "STIM_AT_CAPACITY") recordRefusal({
3150
+ platform: PLATFORM,
3151
+ max: limits.maxDevices,
3152
+ workspace: workspaceId(root)
3153
+ }, now());
3154
+ return fail(capacity.code, capacity.message, capacity.remedy);
3155
+ }
2824
3156
  const prepare = stepClock(now);
2825
3157
  try {
2826
3158
  device = await ensureDevice({
@@ -2830,6 +3162,7 @@ async function runAndroid(options = {}) {
2830
3162
  settingsRoot,
2831
3163
  settings,
2832
3164
  flags: {
3165
+ deviceSlotWait,
2833
3166
  systemImage: target.systemImage,
2834
3167
  systemImageFlag: systemImageFlag?.trim() || null,
2835
3168
  deviceProfile: target.deviceProfile
@@ -2849,6 +3182,7 @@ async function runAndroid(options = {}) {
2849
3182
  platform: PLATFORM,
2850
3183
  device,
2851
3184
  projectPath: root,
3185
+ deviceSlotWait,
2852
3186
  out,
2853
3187
  logFile: emuLog
2854
3188
  })).catch((e) => ({
@@ -2898,6 +3232,7 @@ async function runAndroid(options = {}) {
2898
3232
  isExpo,
2899
3233
  device,
2900
3234
  physical,
3235
+ hostedAbi: hostedTarget?.choice.architecture,
2901
3236
  remote: Boolean(remoteDevice),
2902
3237
  buildPlan,
2903
3238
  cacheProviderConfig,
@@ -2915,7 +3250,8 @@ async function runAndroid(options = {}) {
2915
3250
  miss: progress.miss,
2916
3251
  hit: progress.hit,
2917
3252
  place: progress.place,
2918
- waitingOn: progress.waitingOn
3253
+ waitingOn: progress.waitingOn,
3254
+ waitingFor: progress.waitingFor
2919
3255
  }
2920
3256
  }, {
2921
3257
  deviceAbi,
@@ -2954,6 +3290,40 @@ async function runAndroid(options = {}) {
2954
3290
  androidPackage = artifact.androidPackage;
2955
3291
  record.appPath = apkPath;
2956
3292
  if (runCancellation()) return fail("STIM_CANCELLED", "before install");
3293
+ if (hostedTarget) return finishHostedAndroidRun({
3294
+ target: hostedTarget,
3295
+ artifact,
3296
+ root,
3297
+ slot,
3298
+ release,
3299
+ isExpo,
3300
+ metroPort,
3301
+ logsDir,
3302
+ selectors,
3303
+ place: options.placeHostedAndroid ?? placeHostedAndroid,
3304
+ writePlacement: options.writeHostedAndroid ?? writeHostedAndroid,
3305
+ verifyLaunch: verifyLaunched,
3306
+ writeLaunch,
3307
+ readApkPackage,
3308
+ resolveDevClientScheme,
3309
+ now,
3310
+ started,
3311
+ startedAt,
3312
+ record,
3313
+ fail,
3314
+ phase,
3315
+ out,
3316
+ json,
3317
+ useBuildCache,
3318
+ variant,
3319
+ metroCheck,
3320
+ writer,
3321
+ emit,
3322
+ recordRun,
3323
+ reclaimed,
3324
+ devServer,
3325
+ recordBuild
3326
+ });
2957
3327
  if (startRemoteBoot) {
2958
3328
  progress.step("device");
2959
3329
  bootPromise = startRemoteBoot();
@@ -3082,6 +3452,7 @@ async function runAndroid(options = {}) {
3082
3452
  serial: void 0
3083
3453
  },
3084
3454
  projectPath: root,
3455
+ deviceSlotWait,
3085
3456
  out,
3086
3457
  logFile: emuLog
3087
3458
  })).catch((e) => ({