stim 1.25.0 → 1.27.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 (182) hide show
  1. package/README.md +1 -1
  2. package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
  3. package/dist/{act-BbHS3oV8.mjs → act-ROviTF_t.mjs} +415 -118
  4. package/dist/{activity-BpjwrQXU.mjs → activity-Cd-ysFes.mjs} +6 -7
  5. package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
  6. package/dist/{android-CNZgRC-4.mjs → android--HBgpvpW.mjs} +5 -5
  7. package/dist/{android-BGwuK3Mc.mjs → android-Cd6w7_qw.mjs} +134 -57
  8. package/dist/{android-saBeB4lr.mjs → android-Deb9X3_Y.mjs} +37 -15
  9. package/dist/{android-Cjcj8MCw.mjs → android-DriNoAVy.mjs} +99 -47
  10. package/dist/{android-cas-BVqh2Zep.mjs → android-cas-D5e6ecUg.mjs} +4 -4
  11. package/dist/android-cas-compiler.mjs +1 -1
  12. package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
  13. package/dist/api.d.mts +20 -7
  14. package/dist/api.mjs +42 -32
  15. package/dist/apk-signed-tail-BHQHx3Rm.mjs +142 -0
  16. package/dist/{artifact-Co8zixVx.mjs → artifact-0AGJYB9O.mjs} +28 -28
  17. package/dist/{artifact-DN8JNwqS.mjs → artifact-faemIZv5.mjs} +40 -30
  18. package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
  19. package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
  20. package/dist/auto-placement-z-H5pJza.mjs +350 -0
  21. package/dist/{browser-web-CCgZsHmI.mjs → browser-web-slD8AvLp.mjs} +16 -17
  22. package/dist/{build-cache-CKCzxzxA.mjs → build-cache-gBJ-WKzA.mjs} +141 -37
  23. package/dist/{build-CAiw8Ezm.mjs → build-hS0QyFlQ.mjs} +38 -31
  24. package/dist/{build-plan-zCuHGlA8.mjs → build-plan-CBqYbzYM.mjs} +7 -6
  25. package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
  26. package/dist/cache-manifest.mjs +1 -1
  27. package/dist/{budget-HyShrIpL.mjs → ccache-BWTze1Ls.mjs} +634 -1514
  28. package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
  29. package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
  30. package/dist/{cli-C4w607Pz.mjs → cli-ftXDLto1.mjs} +24 -23
  31. package/dist/cli.mjs +1 -1
  32. package/dist/{client-BQsmyp_-.mjs → client-xsdxrhWA.mjs} +77 -116
  33. package/dist/collector-run.d.mts +10 -9
  34. package/dist/collector-run.mjs +42 -27
  35. package/dist/{command-output-BBvS1H6v.mjs → command-output-Dj-WT4BP.mjs} +1 -1
  36. package/dist/{config-BZhXY15H.mjs → config-Cn94Jc0g.mjs} +343 -18
  37. package/dist/{launch-DscF1QfM.mjs → dependencies-BGokZ-Ng.mjs} +330 -279
  38. package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-C3OtgG94.mjs} +14 -7
  39. package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
  40. package/dist/{dev-client-CHoXXZeg.mjs → dev-client-BMca82xo.mjs} +2 -2
  41. package/dist/{device-F5-4z9ef.mjs → device-BJm0irrq.mjs} +16 -16
  42. package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-6LP7QsaA.mjs} +103 -25
  43. package/dist/device-host-worker.mjs +19 -21
  44. package/dist/{device-ios-DkHBrmvH.mjs → device-ios-534c14fz.mjs} +288 -28
  45. package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
  46. package/dist/{device-pool-BIg8MU8l.mjs → device-pool-BnQqvs6o.mjs} +3 -3
  47. package/dist/{device-B9FqTBYW.mjs → device-tM41-TUq.mjs} +25 -24
  48. package/dist/{doctor-A_Nr_fDe.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
  49. package/dist/{doctor-k3P3PizH.mjs → doctor-DyqE9n1J.mjs} +28 -16
  50. package/dist/{doctor-Bm8TVpGn.mjs → doctor-F-3VUUyN.mjs} +128 -35
  51. package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +9 -3
  52. package/dist/doctor-fingerprint.mjs +1 -1
  53. package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
  54. package/dist/{eas-build-CyDKNTBP.mjs → eas-build-CjNSWmjo.mjs} +4 -4
  55. package/dist/eas-fallback-9fqb46kW.mjs +87 -0
  56. package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
  57. package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
  58. package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
  59. package/dist/{gc-eaz403W6.mjs → gc-BRwAY3qT.mjs} +2 -2
  60. package/dist/{gc-B9jcnmIk.mjs → gc-CGZ1Yg88.mjs} +218 -72
  61. package/dist/{guide-DR0K0xW9.mjs → guide-_rfEh_zM.mjs} +839 -222
  62. package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
  63. package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-D7Pk_Oo1.mjs} +13 -9
  64. package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-B1gqQuva.mjs} +18 -12
  65. package/dist/hosted-logs-sync-Dg3qrZmV.mjs +195 -0
  66. package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
  67. package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CdgaiuyP.mjs} +17 -17
  68. package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-Cbvo5KEh.mjs} +21 -9
  69. package/dist/{ios-BRVM8b0P.mjs → ios-B5HlVMhQ.mjs} +138 -55
  70. package/dist/{ios-Dvt97HgQ.mjs → ios-Bk4mWbtS.mjs} +8 -8
  71. package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
  72. package/dist/{ios-BQzvJlzw.mjs → ios-E26qSpkb.mjs} +94 -11
  73. package/dist/{ios-device-CKSvk0bu.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
  74. package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
  75. package/dist/{ios-project-DCUDo06I.mjs → ios-project-D8Jcy_br.mjs} +22 -9
  76. package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
  77. package/dist/{ios-state-CT9nV5SP.mjs → ios-state-BZ03NFgF.mjs} +1 -1
  78. package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-Dg2a945i.mjs} +8 -7
  79. package/dist/{launch-D0lPvFFu.mjs → launch-CRyBeNsA.mjs} +53 -44
  80. package/dist/lightened-BCsdAGj_.mjs +171 -0
  81. package/dist/{logs-BsIRre8x.mjs → logs-CC_-ZzEE.mjs} +10 -174
  82. package/dist/{logs-Crs9y34Z.mjs → logs-aJQb-RDg.mjs} +112 -38
  83. package/dist/{macos-CcUlJPMu.mjs → macos-BGmTpnv9.mjs} +1 -1
  84. package/dist/{macos-D477nfzH.mjs → macos-DlCBhMw5.mjs} +73 -39
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/maintenance-run.mjs +30 -13
  87. package/dist/{metro-1pJgCX4U.mjs → metro-CvyD-xwe.mjs} +4 -4
  88. package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-BnHdqMO_.mjs} +3 -4
  89. package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BCyMWA6b.mjs} +3 -3
  90. package/dist/{native-android-BvMizJn1.mjs → native-android-DlpxCyOP.mjs} +17 -18
  91. package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
  92. package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-DqaIBO5w.mjs} +9 -9
  93. package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
  94. package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
  95. package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -18
  96. package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
  97. package/dist/offload-worker.d.mts +3 -1
  98. package/dist/offload-worker.mjs +34 -25
  99. package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
  100. package/dist/{ownership-jxxz-19m.mjs → ownership-Bx-0cku-.mjs} +3 -3
  101. package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
  102. package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
  103. package/dist/{placement-log-DjOXJRIH.mjs → placement-log-BXUbiZM6.mjs} +3 -3
  104. package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
  105. package/dist/{ports-DtS1Amb0.mjs → ports-uUrghqWN.mjs} +3 -3
  106. package/dist/prebuild-BDpToDgR.mjs +1246 -0
  107. package/dist/{project-BiC68mIp.mjs → project-DYj-HVx6.mjs} +17 -18
  108. package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
  109. package/dist/pull-requests.mjs +1 -1
  110. package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-DDBccgJw.mjs} +56 -36
  111. package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
  112. package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-B-Mw6DcR.mjs} +50 -9
  113. package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-DjYwUF9U.mjs} +45 -25
  114. package/dist/{recordings-BaoQ0t-J.mjs → recordings-C20iUZfp.mjs} +1 -1
  115. package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
  116. package/dist/{reload-Beftw2X3.mjs → reload-DJBeF_rC.mjs} +1 -1
  117. package/dist/{reload-FbSgMF8x.mjs → reload-fUdkH2_p.mjs} +16 -16
  118. package/dist/{remote-BuZj6AyU.mjs → remote-y6n99WMW.mjs} +22 -9
  119. package/dist/{result-CR2vk2cj.mjs → result-CTqBw2Te.mjs} +32 -21
  120. package/dist/{run-BtzE09Dl.mjs → run-DaCeNKRm.mjs} +3 -3
  121. package/dist/{selection-D5cxEl3t.mjs → selection-Bs8phwhN.mjs} +183 -62
  122. package/dist/{server-bare-CH9X0RLn.mjs → server-bare-BOh-iZEj.mjs} +2 -2
  123. package/dist/{server-command-CaBbDNU2.mjs → server-command-8CuA51Cv.mjs} +6 -6
  124. package/dist/{server-expo-Ck8EIidM.mjs → server-expo-ABQs2XFA.mjs} +4 -4
  125. package/dist/server-expo-Ba_Y21vB.mjs +2 -0
  126. package/dist/{settings-B5tCoq9N.mjs → settings-BDVI8Ivc.mjs} +331 -62
  127. package/dist/{settings-DzH9mGQZ.mjs → settings-CMHhQNwI.mjs} +24 -3
  128. package/dist/settings-payload-cm60Zhib.mjs +257 -0
  129. package/dist/settings-write-BdNRLlBn.mjs +185 -0
  130. package/dist/settings.schema.json +84 -39
  131. package/dist/{simslim-BjxszUAb.mjs → simslim-DCQwzoPy.mjs} +4 -3
  132. package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
  133. package/dist/{stage-D8xlRU2D.mjs → stage-F4cAfJGi.mjs} +2 -2
  134. package/dist/{start-C1JLjkNw.mjs → start-DcqnF9p8.mjs} +1 -1
  135. package/dist/{start-YMpNwLhh.mjs → start-DrPwER_2.mjs} +27 -23
  136. package/dist/{state-DMRNP7-4.mjs → state-BX1y5syV.mjs} +4 -5
  137. package/dist/{state-DPaM3u5K.mjs → state-CZxLyetD.mjs} +2 -3
  138. package/dist/{stats-CP7STzZw.mjs → stats-GxuCbles.mjs} +5 -5
  139. package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
  140. package/dist/{status-Fect2w9R.mjs → status-7ULDEJ1e.mjs} +1 -1
  141. package/dist/{status-CRLiJ-O_.mjs → status-BfnK9pUd.mjs} +25 -26
  142. package/dist/{status-cNw6i0_t.mjs → status-Bj7ZYvvc.mjs} +1 -1
  143. package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-BfabQn03.mjs} +5 -5
  144. package/dist/status-worker.mjs +1 -1
  145. package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
  146. package/dist/{stop-COP6PQqw.mjs → stop-CH79S3IZ.mjs} +5 -6
  147. package/dist/{stop-Db9Kh-UA.mjs → stop-D-EAr25B.mjs} +25 -22
  148. package/dist/{stop-Br7LKxBE.mjs → stop-x1Yu3He4.mjs} +2 -2
  149. package/dist/supervisor-run.d.mts +1 -0
  150. package/dist/supervisor-run.mjs +17 -18
  151. package/dist/{support-qKcbgmI5.mjs → support-Be0UeVZU.mjs} +10 -10
  152. package/dist/{support-Cxj9CmII.mjs → support-CNg_YsOk.mjs} +6 -6
  153. package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
  154. package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
  155. package/dist/toolchain-CbCG6C5Z.mjs +2413 -0
  156. package/dist/toolchain-JYAOquPg.mjs +2 -0
  157. package/dist/{trigger-CBMe2YLH.mjs → trigger-DMou-X7j.mjs} +3 -3
  158. package/dist/trigger-qp2ji5wE.mjs +2 -0
  159. package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-C0RsbngN.mjs} +731 -728
  160. package/dist/{web-B4Yjyt1G.mjs → web-Cq_AQ4DK.mjs} +9 -10
  161. package/dist/web-Je6abcpz.mjs +2 -0
  162. package/dist/web-run.mjs +9 -9
  163. package/dist/windows-report.mjs +1 -1
  164. package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-BjqdjnJR.mjs} +46 -8
  165. package/dist/{workspaces-C9w4aeXF.mjs → workspaces-DFbj07jp.mjs} +1801 -76
  166. package/dist/{worktree-CclFIe1Z.mjs → worktree-COAJ3-OD.mjs} +934 -511
  167. package/dist/{worktree-Ct9Gk3KL.mjs → worktree-JGUd_53L.mjs} +1 -1
  168. package/package.json +8 -8
  169. package/dist/ccache-h1FKhZeg.mjs +0 -143
  170. package/dist/created-devices-D2Aca-xu.mjs +0 -52
  171. package/dist/eas-fallback-gaZi-dJL.mjs +0 -381
  172. package/dist/hosted-ios-RtVcJH3u.mjs +0 -15
  173. package/dist/native-run-D5OJsGwA.mjs +0 -276
  174. package/dist/paths-DQtTgsBf.mjs +0 -42
  175. package/dist/prebuild-JUaqOLpc.mjs +0 -501
  176. package/dist/server-expo-C16Zf2k7.mjs +0 -2
  177. package/dist/settings-write-n0zwfohx.mjs +0 -360
  178. package/dist/toolchain-DB1aeqxO.mjs +0 -2
  179. package/dist/toolchain-VzLJzZ7O.mjs +0 -908
  180. package/dist/trigger-BLsHgAGR.mjs +0 -2
  181. package/dist/web-DS4UDWvf.mjs +0 -2
  182. package/dist/xcode-CiIIDnuD.mjs +0 -910
@@ -1,12 +1,12 @@
1
- import { S as process_identity_exports } from "./exec-VFRpu1iA.mjs";
2
- import { m as workspaceLogsDir } from "./paths-DQtTgsBf.mjs";
3
- import { V as deviceSlotKey, q as validateDeviceSlot } from "./config-BZhXY15H.mjs";
1
+ import { S as process_identity_exports } from "./exec-aULwtNly.mjs";
2
+ import { k as workspaceLogsDir } from "./workspace-state-BjqdjnJR.mjs";
3
+ import { V as deviceSlotKey, q as validateDeviceSlot } from "./config-Cn94Jc0g.mjs";
4
4
  import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
5
- import { c as watchAppPid, i as parseLogcatLine, o as startAndroidLogcat, s as waitForAppPid, t as androidClockOffset } from "./android-saBeB4lr.mjs";
6
- import { r as createNdjsonWriter } from "./ndjson-CgE_o3Dl.mjs";
7
- import { a as startIosDeviceConsole, i as parseDeviceConsoleLine } from "./ios-device-B6HK951C.mjs";
8
- import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-jxxz-19m.mjs";
9
- import { n as parseLogStreamLine, r as startIosLogStream, t as appNameFromBundleId } from "./ios-ENC4sRPA.mjs";
5
+ import { c as watchAppPid, i as parseLogcatLine, o as startAndroidLogcat, s as waitForAppPid, t as androidClockOffset } from "./android-Deb9X3_Y.mjs";
6
+ import { i as createNdjsonWriter } from "./ndjson-Dy2W4vRa.mjs";
7
+ import { a as startIosDeviceConsole, i as parseDeviceConsoleLine } from "./ios-device-CiBA-er9.mjs";
8
+ import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-Bx-0cku-.mjs";
9
+ import { n as parseLogStreamLine, r as startIosLogStream, t as appNameFromBundleId } from "./ios-CsRVik64.mjs";
10
10
  import { existsSync } from "node:fs";
11
11
  import { isAbsolute, join, resolve } from "node:path";
12
12
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
@@ -130,11 +130,13 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
130
130
  return null;
131
131
  }
132
132
  let finished = false;
133
+ const shutdown = new AbortController();
133
134
  let captured = 0;
134
135
  let watcher = null;
135
136
  const finish = (code, level, msg, event, { signalled = false } = {}) => {
136
137
  if (finished) return;
137
138
  finished = true;
139
+ shutdown.abort();
138
140
  watcher?.stop();
139
141
  if (physical && captured === 0 && !signalled) writer.write({
140
142
  ...attribution,
@@ -187,7 +189,8 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
187
189
  }) : await waitForAppPid({
188
190
  serial,
189
191
  packageName,
190
- timeoutMs: pidTimeoutMs
192
+ timeoutMs: pidTimeoutMs,
193
+ signal: shutdown.signal
191
194
  });
192
195
  if (!resolved.ok) {
193
196
  finish(1, "error", `device log collector could not attach: ${resolved.reason}`, "collector_failed");
@@ -214,8 +217,11 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
214
217
  })
215
218
  });
216
219
  const parse = platform === "ios" ? physical ? parseDeviceConsoleLine : parseLogStreamLine : parseLogcatLine;
217
- const attach = (streamPid) => {
218
- const clockOffsetMs = platform === "android" ? resolveClockOffset(serial, { now }) : null;
220
+ const readClockOffset = async () => platform === "android" ? await resolveClockOffset(serial, {
221
+ now,
222
+ signal: shutdown.signal
223
+ }) : null;
224
+ const attach = (streamPid, clockOffsetMs) => {
219
225
  if (platform === "android") writer.write({
220
226
  ...attribution,
221
227
  src: "device",
@@ -284,17 +290,21 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
284
290
  spawned.on("exit", (code, signal) => {
285
291
  if (spawned !== child) return;
286
292
  flushReaders();
287
- const next = platform === "android" ? watcher?.probe() : null;
288
- if (next && next !== pid) {
289
- reattach(next, "the log stream ended");
290
- return;
291
- }
292
- const how = signal ? `signal ${signal}` : `exit code ${code}`;
293
- if (physical && code) {
294
- finish(1, "error", `the devicectl console ended with ${how}; treat the run as failed`, "collector_failed");
295
- return;
296
- }
297
- finish(0, "info", `device log stream ended (${how}); the app or device is gone`, "collector_stopped");
293
+ const settle = (next) => {
294
+ if (spawned !== child) return;
295
+ if (next && next !== pid) {
296
+ reattach(next, "the log stream ended");
297
+ return;
298
+ }
299
+ const how = signal ? `signal ${signal}` : `exit code ${code}`;
300
+ if (physical && code) {
301
+ finish(1, "error", `the devicectl console ended with ${how}; treat the run as failed`, "collector_failed");
302
+ return;
303
+ }
304
+ finish(0, "info", `device log stream ended (${how}); the app or device is gone`, "collector_stopped");
305
+ };
306
+ if (platform === "android" && watcher) watcher.probe().then(settle);
307
+ else settle(null);
298
308
  });
299
309
  spawned.on("error", (err) => {
300
310
  if (spawned !== child) return;
@@ -302,12 +312,13 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
302
312
  });
303
313
  return spawned;
304
314
  };
305
- const reattach = (nextPid, why = "the app restarted") => {
306
- if (finished) return;
315
+ const reattach = async (nextPid, why = "the app restarted") => {
316
+ if (finished || nextPid === pid) return;
307
317
  const previous = pid;
308
318
  flushReaders();
309
319
  killChild(child);
310
320
  child = null;
321
+ pid = nextPid;
311
322
  writer.write({
312
323
  ...attribution,
313
324
  src: "device",
@@ -316,15 +327,18 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
316
327
  event: "collector_reattached",
317
328
  msg: `${why}: ${packageName} is now pid ${nextPid} on ${serial} (was ${previous}); reattaching the log stream`
318
329
  });
319
- pid = nextPid;
330
+ const clockOffsetMs = await readClockOffset();
331
+ if (finished || pid !== nextPid) return;
320
332
  try {
321
- child = attach(nextPid);
333
+ child = attach(nextPid, clockOffsetMs);
322
334
  } catch (err) {
323
335
  finish(1, "error", `device log collector could not reattach to pid ${nextPid}: ${describe(err)}`, "collector_failed");
324
336
  }
325
337
  };
338
+ const initialClockOffsetMs = await readClockOffset();
339
+ if (finished) return null;
326
340
  try {
327
- child = attach(pid);
341
+ child = attach(pid, initialClockOffsetMs);
328
342
  } catch (err) {
329
343
  finish(1, "error", `device log collector could not start: ${describe(err)}`, "collector_failed");
330
344
  return null;
@@ -334,6 +348,7 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
334
348
  packageName,
335
349
  pid,
336
350
  intervalMs: pidWatchMs,
351
+ signal: shutdown.signal,
337
352
  resolve: pidOf || void 0,
338
353
  onChange: (nextPid) => reattach(nextPid)
339
354
  });
@@ -1,5 +1,5 @@
1
1
  import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
2
- import { t as NO_PROJECT_REFUSAL } from "./project-BiC68mIp.mjs";
2
+ import { t as NO_PROJECT_REFUSAL } from "./project-DYj-HVx6.mjs";
3
3
  import { quotedPath as quotedPath$1 } from "@stim-cli/core";
4
4
  import chalk from "chalk";
5
5
  //#region src/diagnostics/launch-error-preview.ts
@@ -1,9 +1,10 @@
1
- import { C as reportSignal, D as ownership_claim_exports, S as process_identity_exports, T as trackReportResource, l as WorkspaceProcessCleanupError, t as getExecutor } from "./exec-VFRpu1iA.mjs";
2
- import { t as runOperationFile } from "./operation-exec-CUZifp6Z.mjs";
1
+ import { C as reportSignal, D as ownership_claim_exports, S as process_identity_exports, T as trackReportResource, l as WorkspaceProcessCleanupError, t as getExecutor } from "./exec-aULwtNly.mjs";
2
+ import { t as runOperationFile } from "./operation-exec-Dp4FuIGR.mjs";
3
3
  import { join } from "node:path";
4
4
  import { ambientEnv, configDir, ensurePrivateDir } from "@stim-cli/core";
5
- import { allMetroPorts, cannotHost, clientIdentity, configCorruptRepair, endpoint, findEnclosingWorktreeRoot, findPeer as findPeer$1, getConcurrencyLimits, getConfigPath, getConfigPath as getConfigPath$1, getProject, getProjectSettings, getRepoSettings, isJsonObject, isPathPrefix, loadConfig, loadConfig as loadConfig$1, machineEndpoint, parseMachine, parseMachine as parseMachine$1, validateAutomaticPool, withConfigLock, withConfigLock as withConfigLock$1, withConfigLockAsync, withConfigLockAsync as withConfigLockAsync$1 } from "@stim-cli/core/state";
5
+ import { allMetroPorts, cannotHost, clientIdentity, configCorruptRepair, endpoint, endpoint as endpoint$1, findEnclosingWorktreeRoot, findPeer, findPeer as findPeer$1, getConcurrencyLimits, getConfigPath, getConfigPath as getConfigPath$1, getProject, getProjectSettings, getRepoSettings, isJsonObject, isPathPrefix as isPathPrefix$1, loadConfig, loadConfig as loadConfig$1, machineEndpoint, parseMachine, parseMachine as parseMachine$1, validateAutomaticPool, withConfigLock, withConfigLock as withConfigLock$1, withConfigLockAsync, withConfigLockAsync as withConfigLockAsync$1 } from "@stim-cli/core/state";
6
6
  import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, normalize, parse, sep as sep$1 } from "path";
7
+ import { request } from "node:https";
7
8
  import { WebSocket } from "ws";
8
9
  import { HandshakeRefusal, MAX_SERVER_MESSAGE_BYTES, TRANSPORT_OVERHEAD_BYTES, decodeSecret, initiateNoise, newSecret } from "@stim-cli/core/noise-session";
9
10
  import { existsSync, lstatSync, readdirSync, readlinkSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -23,19 +24,246 @@ function readHostPermissions(value) {
23
24
  } : void 0;
24
25
  }
25
26
  //#endregion
27
+ //#region src/offload/diagnosis.ts
28
+ const SILENT_ERRORS = /* @__PURE__ */ new Set([
29
+ "ETIMEDOUT",
30
+ "EHOSTUNREACH",
31
+ "ENETUNREACH",
32
+ "EHOSTDOWN"
33
+ ]);
34
+ function classifyTransport(error, status) {
35
+ if (status !== void 0) return {
36
+ kind: "http",
37
+ status
38
+ };
39
+ const code = error.code ?? "";
40
+ if (code === "ECONNREFUSED") return { kind: "refused" };
41
+ if (SILENT_ERRORS.has(code) || /handshake has timed out/i.test(error.message)) return { kind: "silent" };
42
+ if (/^(ERR_TLS_|CERT_|UNABLE_TO_|DEPTH_ZERO|SELF_SIGNED|ERR_SSL_)/.test(code) || /certificate|SSL|TLS/i.test(error.message)) return {
43
+ kind: "tls",
44
+ message: error.message
45
+ };
46
+ return {
47
+ kind: "other",
48
+ message: error.message
49
+ };
50
+ }
51
+ /** The `startup` a 503 `/health` body from a peer carries, when it is the shape stim-server sends. */
52
+ function parsePeerStartup(body) {
53
+ const startup = isJsonObject(body) && isJsonObject(body.startup) ? body.startup : null;
54
+ if (!startup || startup.state !== "pending" && startup.state !== "degraded") return null;
55
+ const cause = startup.cause === "home-unreadable" || startup.cause === "start-failed" ? startup.cause : void 0;
56
+ return {
57
+ state: startup.state,
58
+ ...cause ? { cause } : {}
59
+ };
60
+ }
61
+ function readTailscale(status) {
62
+ if (!isJsonObject(status)) return {
63
+ running: false,
64
+ code: "tailscale-unavailable"
65
+ };
66
+ switch (status.BackendState) {
67
+ case "Running": return { running: true };
68
+ case "NeedsLogin":
69
+ case "NeedsMachineAuth": return {
70
+ running: false,
71
+ code: "tailscale-logged-out"
72
+ };
73
+ default: return {
74
+ running: false,
75
+ code: "tailscale-stopped"
76
+ };
77
+ }
78
+ }
79
+ function tailscaleDiagnosis(code) {
80
+ switch (code) {
81
+ case "tailscale-logged-out": return {
82
+ layer: "tailscale",
83
+ code,
84
+ title: "Tailscale signed out",
85
+ message: "Tailscale on this Mac is signed out, so it cannot reach any remote machine.",
86
+ remedy: "Sign in to Tailscale on this Mac (its menu bar app, or `tailscale up`), then run `stim doctor` again."
87
+ };
88
+ case "tailscale-stopped": return {
89
+ layer: "tailscale",
90
+ code,
91
+ title: "Tailscale is off",
92
+ message: "Tailscale is turned off on this Mac, so it cannot reach any remote machine.",
93
+ remedy: "Turn on Tailscale on this Mac (its menu bar app, or `tailscale up`), then run `stim doctor` again."
94
+ };
95
+ case "tailscale-unavailable": return {
96
+ layer: "tailscale",
97
+ code,
98
+ title: "Tailscale is off",
99
+ message: "Tailscale is not running on this Mac or is not installed, so it cannot reach any remote machine.",
100
+ remedy: "Install Tailscale or open it on this Mac and sign in, then run `stim doctor` again."
101
+ };
102
+ }
103
+ }
104
+ function notOnTailnetDiagnosis(entry, ambiguous) {
105
+ return {
106
+ layer: "peer",
107
+ code: ambiguous ? "peer-ambiguous" : "peer-not-found",
108
+ title: ambiguous ? "Name is ambiguous" : "Not on the tailnet",
109
+ message: ambiguous ? `Several peers on this tailnet match ${entry}.` : `No peer on this tailnet is named ${entry}.`,
110
+ remedy: ambiguous ? `Use the full MagicDNS name in remote.machines (see \`tailscale status\`).` : `Check the name in \`tailscale status\`, or sign in to Tailscale on ${entry} with the same account.`
111
+ };
112
+ }
113
+ function ago(iso, now) {
114
+ const then = iso ? Date.parse(iso) : NaN;
115
+ if (!Number.isFinite(then) || then <= 0) return null;
116
+ const minutes = Math.max(0, Math.round((now - then) / 6e4));
117
+ if (minutes < 1) return "less than a minute ago";
118
+ if (minutes < 60) return `${minutes} min ago`;
119
+ const hours = Math.round(minutes / 60);
120
+ if (hours < 48) return `${hours} h ago`;
121
+ return `${Math.round(hours / 24)} days ago`;
122
+ }
123
+ const sentence = (text) => text.replace(/\s+$/, "").replace(/([^.!?])$/, "$1.");
124
+ /**
125
+ * Tailscale's `Online` lags a reachable peer, so `peer-offline` needs a silent connection; any other outcome,
126
+ * including one past the transport (no `transport`), shows the peer answered.
127
+ */
128
+ function diagnoseUnreachable(input) {
129
+ const { entry, peer, port, transport, now = Date.now() } = input;
130
+ const route = `https port ${port}`;
131
+ if (transport?.kind === "silent" && input.pinged === true) return {
132
+ layer: "server",
133
+ code: "server-no-answer",
134
+ title: "Server not answering",
135
+ message: `${entry} answers on Tailscale, but its serve route and stim-server sent no reply in time. stim-server may be starting, stalled or restarting.`,
136
+ remedy: `On ${entry}, run \`stim-server service status\`; it reports a restart loop or a Stim home it is still reading, and the log file to read.`
137
+ };
138
+ if (peer.online === false && transport?.kind === "silent") {
139
+ const seen = ago(peer.lastSeen, now);
140
+ return {
141
+ layer: "peer",
142
+ code: "peer-offline",
143
+ title: "Mac is offline",
144
+ message: `${entry} is offline on Tailscale${seen ? ` (last seen ${seen})` : ""}.`,
145
+ remedy: `Wake ${entry} or check that it is powered on, connected to a network and signed in to Tailscale, then run \`stim doctor\` again.`
146
+ };
147
+ }
148
+ if (transport?.kind === "refused") return {
149
+ layer: "route",
150
+ code: "route-missing",
151
+ title: "No serve route",
152
+ message: `${entry} answers on the tailnet, but nothing accepts connections on ${route}, so it has no tailscale serve route for stim-server.`,
153
+ remedy: `On ${entry}, run \`stim-server service status\`; \`stim-server service install --serve\` creates the route.`
154
+ };
155
+ if (transport?.kind === "silent") return {
156
+ layer: "peer",
157
+ code: "peer-no-answer",
158
+ title: "Mac not answering",
159
+ message: `${entry} did not answer the connection, and Tailscale does not list it as offline.`,
160
+ remedy: `Check that ${entry} is awake and its network is up; \`tailscale ping ${entry}\` on this Mac tests the path.`
161
+ };
162
+ if (transport?.kind === "tls") return {
163
+ layer: "route",
164
+ code: "route-tls",
165
+ title: "Route certificate",
166
+ message: `The tailscale serve route on ${entry} (${route}) did not present a certificate this Mac accepts: ${sentence(transport.message)}`,
167
+ remedy: `On ${entry}, run \`tailscale serve status\`, then \`stim-server service install --serve\` to recreate the route.`
168
+ };
169
+ if (transport?.kind === "http") {
170
+ const { status } = transport;
171
+ if (status === 502) return {
172
+ layer: "server",
173
+ code: "server-not-running",
174
+ title: "Server not running",
175
+ message: `Tailscale and the serve route on ${entry} work, but nothing listens behind the route: stim-server is not running there (HTTP 502).`,
176
+ remedy: `On ${entry}, run \`stim-server service status\`; \`stim-server service install --serve\` starts it and keeps it running.`
177
+ };
178
+ if (status === 503) {
179
+ const startup = input.startup;
180
+ const why = startup?.cause === "home-unreadable" ? " It cannot read its Stim home, usually because the disk holding ~/.stim is disconnected or not responding." : startup?.cause === "start-failed" ? " It failed while starting." : startup?.state === "pending" ? " It is still starting." : "";
181
+ const check = startup?.cause === "home-unreadable" ? `Check the disk that holds the Stim home on ${entry} (reconnect or wake an external drive); stim-server serves again by itself once it answers. \`stim-server service status\` there shows the exact reason.` : startup?.state === "pending" ? `Wait a few seconds and run \`stim doctor\` again.` : `On ${entry}, run \`stim-server service status\` for the reason; the log file it prints has the rest.`;
182
+ return {
183
+ layer: "server",
184
+ code: "server-not-ready",
185
+ title: "Server not ready",
186
+ message: `stim-server on ${entry} is running but is not serving clients (HTTP 503).${why}`,
187
+ remedy: check
188
+ };
189
+ }
190
+ return {
191
+ layer: "route",
192
+ code: "route-http-error",
193
+ title: `Route answers ${status}`,
194
+ message: `The tailscale serve route on ${entry} (${route}) answered HTTP ${status}, which stim-server does not send for a connection.`,
195
+ remedy: `On ${entry}, run \`tailscale serve status\` and check what the route on port ${port} points to.`
196
+ };
197
+ }
198
+ return {
199
+ layer: "server",
200
+ code: "connect-failed",
201
+ title: "Not answering",
202
+ message: `Could not connect to stim-server on ${entry}: ${sentence(input.reason)}`,
203
+ remedy: `On ${entry}, run \`stim-server service status\`. To keep it running there, run \`stim-server service install --serve\`.`
204
+ };
205
+ }
206
+ function diagnosisFinding(code, entry, diagnosis) {
207
+ return {
208
+ code,
209
+ level: "note",
210
+ title: `Remote machine ${entry}: ${diagnosis.title}`,
211
+ detail: diagnosis.message,
212
+ fix: diagnosis.remedy
213
+ };
214
+ }
215
+ //#endregion
26
216
  //#region src/offload/tailnet.ts
27
217
  const MAC_APP_TAILSCALE = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
28
218
  const HELLO_TIMEOUT_MS = 1e4;
219
+ const HEALTH_TIMEOUT_MS = 3e3;
29
220
  /** How long a setup ticket `stim doctor --fix` generates stays valid; `stim-server setup` takes at most 2 hours. */
30
221
  const GENERATED_TICKET_MS = 714e4;
31
- function parseTailscaleStatus(output) {
222
+ function parseJson(output) {
32
223
  try {
33
- const status = JSON.parse(output);
34
- return isJsonObject(status) && status.BackendState === "Running" ? status : null;
224
+ return JSON.parse(output);
35
225
  } catch {
36
226
  return null;
37
227
  }
38
228
  }
229
+ function parseTailscaleStatus(output) {
230
+ const status = parseJson(output);
231
+ return isJsonObject(status) && status.BackendState === "Running" ? status : null;
232
+ }
233
+ function rawTailscaleStatus() {
234
+ for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
235
+ const output = getExecutor().runFileQuiet(binary, ["status", "--json"], { timeoutMs: 5e3 });
236
+ if (output !== null) return parseJson(output);
237
+ }
238
+ return null;
239
+ }
240
+ /** Reads the `startup` of a peer's `/health`, which stim-server answers with 503 while it is not serving clients. */
241
+ function peerStartup({ url, servername, host }) {
242
+ return new Promise((resolve) => {
243
+ const base = new URL(url);
244
+ const request$1 = request({
245
+ host: base.hostname.replace(/^\[|\]$/g, ""),
246
+ port: base.port || 443,
247
+ path: "/health",
248
+ servername,
249
+ headers: { Host: host },
250
+ timeout: HEALTH_TIMEOUT_MS
251
+ }, (response) => {
252
+ const chunks = [];
253
+ let size = 0;
254
+ response.on("data", (chunk) => {
255
+ size += chunk.length;
256
+ if (size > 4096) request$1.destroy();
257
+ else chunks.push(chunk);
258
+ });
259
+ response.on("end", () => resolve(parsePeerStartup(parseJson(Buffer.concat(chunks).toString("utf8")))));
260
+ response.on("error", () => resolve(null));
261
+ });
262
+ request$1.on("timeout", () => request$1.destroy());
263
+ request$1.on("error", () => resolve(null));
264
+ request$1.end();
265
+ });
266
+ }
39
267
  function tailscaleStatus() {
40
268
  for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
41
269
  const output = getExecutor().runFileQuiet(binary, ["status", "--json"], { timeoutMs: 5e3 });
@@ -43,7 +271,7 @@ function tailscaleStatus() {
43
271
  }
44
272
  return null;
45
273
  }
46
- async function tailscaleStatusAsync(signal) {
274
+ async function tailscaleOutputAsync(signal) {
47
275
  for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
48
276
  signal?.throwIfAborted();
49
277
  let output;
@@ -57,10 +285,42 @@ async function tailscaleStatusAsync(signal) {
57
285
  continue;
58
286
  }
59
287
  signal?.throwIfAborted();
60
- return parseTailscaleStatus(output);
288
+ return output;
61
289
  }
62
290
  return null;
63
291
  }
292
+ async function tailscaleStatusAsync(signal) {
293
+ const output = await tailscaleOutputAsync(signal);
294
+ return output === null ? null : parseTailscaleStatus(output);
295
+ }
296
+ async function pingPeer(address, signal) {
297
+ for (const binary of ["tailscale", MAC_APP_TAILSCALE]) {
298
+ signal?.throwIfAborted();
299
+ try {
300
+ await getExecutor().runFileAsync(binary, [
301
+ "ping",
302
+ "--c",
303
+ "1",
304
+ "--until-direct=false",
305
+ "--timeout",
306
+ "4s",
307
+ address
308
+ ], {
309
+ timeoutMs: 6e3,
310
+ signal
311
+ });
312
+ return true;
313
+ } catch (error) {
314
+ signal?.throwIfAborted();
315
+ if (!/ENOENT|not found/i.test(String(error))) return false;
316
+ }
317
+ }
318
+ return false;
319
+ }
320
+ async function rawTailscaleStatusAsync(signal) {
321
+ const output = await tailscaleOutputAsync(signal);
322
+ return output === null ? null : parseJson(output);
323
+ }
64
324
  const TURNED_AWAY = /* @__PURE__ */ new Set([
65
325
  "unauthorized",
66
326
  "forbidden",
@@ -106,6 +366,7 @@ function openAuthenticated(target, auth, timeoutMs, signal, operation) {
106
366
  socket.removeAllListeners("open");
107
367
  socket.removeAllListeners("message");
108
368
  socket.removeAllListeners("error");
369
+ socket.removeAllListeners("unexpected-response");
109
370
  socket.on("error", () => {});
110
371
  socket.terminate();
111
372
  }
@@ -116,18 +377,33 @@ function openAuthenticated(target, auth, timeoutMs, signal, operation) {
116
377
  refused: false,
117
378
  code: "STIM_CANCELLED"
118
379
  });
380
+ let opened = false;
119
381
  const timer = setTimeout(() => finish({
120
382
  failure: "no reply in time",
121
- refused: false
383
+ refused: false,
384
+ transport: opened ? {
385
+ kind: "other",
386
+ message: "the handshake got no reply"
387
+ } : { kind: "silent" }
122
388
  }), timeoutMs);
123
389
  signal?.addEventListener("abort", cancel, { once: true });
124
390
  if (signal?.aborted) return cancel();
125
- const failed = (error) => finish({
126
- failure: /Unexpected server response: 502/.test(error.message) ? "no stim-server answers behind its tailscale serve route (HTTP 502)" : error.message,
127
- refused: false
391
+ const failed = (error, status) => finish({
392
+ failure: status === 502 ? "no stim-server answers behind its tailscale serve route (HTTP 502)" : status === 503 ? "stim-server answers behind its tailscale serve route but is not serving clients (HTTP 503)" : status !== void 0 ? `its tailscale serve route answered HTTP ${status}` : error.message,
393
+ refused: false,
394
+ transport: opened && status === void 0 ? {
395
+ kind: "other",
396
+ message: error.message
397
+ } : classifyTransport(error, status)
398
+ });
399
+ socket.once("unexpected-response", (request, response) => {
400
+ response.resume();
401
+ request.destroy();
402
+ failed(/* @__PURE__ */ new Error(`Unexpected server response: ${response.statusCode}`), response.statusCode);
128
403
  });
129
404
  socket.once("error", failed);
130
405
  socket.once("open", () => {
406
+ opened = true;
131
407
  const secret = "ticket" in auth ? decodeSecret(auth.ticket) : null;
132
408
  let keyPair;
133
409
  try {
@@ -215,7 +491,10 @@ async function hello(target, auth, operation) {
215
491
  if (!("send" in socket)) return socket.code ? { error: {
216
492
  code: socket.code,
217
493
  message: socket.failure
218
- } } : { failed: socket.failure };
494
+ } } : {
495
+ failed: socket.failure,
496
+ ...socket.transport ? { transport: socket.transport } : {}
497
+ };
219
498
  try {
220
499
  if ("onServerKey" in auth) await auth.onServerKey?.(socket.remoteKey.toString("base64url"));
221
500
  } catch (error) {
@@ -241,14 +520,58 @@ async function hello(target, auth, operation) {
241
520
  }
242
521
  const realIo = {
243
522
  status: tailscaleStatus,
244
- hello
523
+ hello,
524
+ rawStatus: rawTailscaleStatus,
525
+ startup: peerStartup,
526
+ ping: (address) => pingPeer(address)
245
527
  };
246
528
  function doctorIo(operation) {
247
529
  return {
248
530
  status: () => tailscaleStatusAsync(operation?.signal),
249
- hello: (target, auth) => hello(target, auth, operation)
531
+ hello: (target, auth) => hello(target, auth, operation),
532
+ rawStatus: () => rawTailscaleStatusAsync(operation?.signal),
533
+ startup: peerStartup,
534
+ ping: (address) => pingPeer(address, operation?.signal)
250
535
  };
251
536
  }
537
+ /**
538
+ * Names the layer an unreachable `entry` stopped at. A 503 reads the peer's `/health` for why the server is not
539
+ * serving; an older stim-server answers without it, and the diagnosis then says only that it is not ready.
540
+ */
541
+ async function diagnoseOutcome(io, input) {
542
+ const { target, ...rest } = input;
543
+ const startup = rest.transport?.kind === "http" && rest.transport.status === 503 ? await io.startup?.(target) : null;
544
+ const pinged = rest.transport?.kind === "silent" ? await io.ping?.(rest.peer.address) : void 0;
545
+ return diagnoseUnreachable({
546
+ ...rest,
547
+ startup,
548
+ pinged
549
+ });
550
+ }
551
+ /**
552
+ * The diagnosis `stim doctor` gives for a machine that resolved to a tailnet peer but did not connect, without
553
+ * doctor's `tailscale ping`: the connection has already waited out its timeout. Reads `tailscale status` only here,
554
+ * after the failure. Null when Tailscale no longer lists the machine.
555
+ */
556
+ async function diagnoseConnectFailure(machine, failure, signal) {
557
+ const parsed = parseMachine(machine);
558
+ if (!parsed) return null;
559
+ const peer = findPeer(await tailscaleStatusAsync(signal), parsed.name);
560
+ if (typeof peer === "string") return null;
561
+ return diagnoseOutcome({ startup: peerStartup }, {
562
+ entry: machine,
563
+ peer,
564
+ port: parsed.port,
565
+ target: endpoint(peer, parsed.port),
566
+ reason: failure.failure,
567
+ ...failure.transport ? { transport: failure.transport } : {}
568
+ });
569
+ }
570
+ /** Why `status` is null, as a diagnosis of the Tailscale layer; falls back to the daemon being unavailable. */
571
+ async function diagnoseTailscale(io) {
572
+ const reading = readTailscale(io.rawStatus ? await io.rawStatus() : null);
573
+ return tailscaleDiagnosis(reading.running ? "tailscale-unavailable" : reading.code);
574
+ }
252
575
  /** Where to reach a paired machine now, or why Stim cannot; the handshake then checks its pinned key. */
253
576
  async function resolveEndpoint(credential, status = () => tailscaleStatusAsync(signal), signal) {
254
577
  return machineEndpoint(credential.machine, parseMachine(credential.machine) ? await status() : null);
@@ -357,7 +680,8 @@ async function reachMachine(input) {
357
680
  return {
358
681
  state: "unreachable",
359
682
  credential: pending,
360
- reason: "failed" in reply ? reply.failed : "error" in reply ? reply.error.message : "it did not grant this access"
683
+ reason: "failed" in reply ? reply.failed : "error" in reply ? reply.error.message : "it did not grant this access",
684
+ ..."failed" in reply && reply.transport ? { transport: reply.transport } : {}
361
685
  };
362
686
  };
363
687
  if (!credential) return input.fix ? ask() : { state: "not-asked" };
@@ -389,7 +713,8 @@ async function reachMachine(input) {
389
713
  return {
390
714
  state: "unreachable",
391
715
  credential,
392
- reason: "failed" in reply ? reply.failed : reply.error.message
716
+ reason: "failed" in reply ? reply.failed : reply.error.message,
717
+ ..."failed" in reply && reply.transport ? { transport: reply.transport } : {}
393
718
  };
394
719
  }
395
720
  if (input.fix && given && given !== credential.ticket) return ask();
@@ -952,4 +1277,4 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
952
1277
  return result;
953
1278
  }
954
1279
  //#endregion
955
- export { outdatedServer as $, writeConfigSettingAsync as A, deviceSlotFileKey as B, saveConfig as C, withConfigLock$1 as D, upsertProject as E, isOnMountedVolume as F, projectDeviceSlots as G, deviceSlotPlatforms as H, listMountedVolumes as I, SERVER_OUTDATED as J, removeSlotDevice as K, measuredDirectorySizeAsync as L, directorySize as M, directorySizeAsync as N, withConfigLockAsync$1 as O, formatBytes as P, openAuthenticated as Q, volumeRootFor as R, removeProject as S, setSupervisor as T, parseDeviceSlotKey as U, deviceSlotKey as V, parseDeviceSlotOption as W, endpoint as X, doctorIo as Y, findPeer$1 as Z, loadConfig$1 as _, clearDevice as a, setupRemedy as at, releaseAndroidConsolePort as b, ensureConfig as c, getConfigDir as d, parseMachine$1 as et, getConfigPath$1 as f, isPathPrefix as g, getRepoSettings as h, clearAndroidBootPending as i, sayHello as it, acquireAvdClaim as j, writeConfigSetting as k, findEnclosingWorktreeRoot as l, getProjectSettings as m, allMetroPorts as n, realIo as nt, clearSupervisor as o, tailscaleStatusAsync as ot, getProject as p, validateDeviceSlot as q, claimMetroPort as r, resolveEndpoint as rt, configCorruptRepair as s, readHostPermissions as st, allConsolePortsAndSerials as t, reachMachine as tt, getConcurrencyLimits as u, recordIosSchemeApprovals as v, setDevice as w, releaseMetroPort as x, refuseRelativeStimPaths as y, assignSlotDevice as z };
1280
+ export { endpoint$1 as $, writeConfigSettingAsync as A, deviceSlotFileKey as B, saveConfig as C, withConfigLock$1 as D, upsertProject as E, isOnMountedVolume as F, projectDeviceSlots as G, deviceSlotPlatforms as H, listMountedVolumes as I, SERVER_OUTDATED as J, removeSlotDevice as K, measuredDirectorySizeAsync as L, directorySize as M, directorySizeAsync as N, withConfigLockAsync$1 as O, formatBytes as P, doctorIo as Q, volumeRootFor as R, removeProject as S, setSupervisor as T, parseDeviceSlotKey as U, deviceSlotKey as V, parseDeviceSlotOption as W, diagnoseOutcome as X, diagnoseConnectFailure as Y, diagnoseTailscale as Z, loadConfig$1 as _, clearDevice as a, realIo as at, releaseAndroidConsolePort as b, ensureConfig as c, setupRemedy as ct, getConfigDir as d, notOnTailnetDiagnosis as dt, findPeer$1 as et, getConfigPath$1 as f, readHostPermissions as ft, isPathPrefix$1 as g, getRepoSettings as h, clearAndroidBootPending as i, reachMachine as it, acquireAvdClaim as j, writeConfigSetting as k, findEnclosingWorktreeRoot as l, tailscaleStatusAsync as lt, getProjectSettings as m, allMetroPorts as n, outdatedServer as nt, clearSupervisor as o, resolveEndpoint as ot, getProject as p, validateDeviceSlot as q, claimMetroPort as r, parseMachine$1 as rt, configCorruptRepair as s, sayHello as st, allConsolePortsAndSerials as t, openAuthenticated as tt, getConcurrencyLimits as u, diagnosisFinding as ut, recordIosSchemeApprovals as v, setDevice as w, releaseMetroPort as x, refuseRelativeStimPaths as y, assignSlotDevice as z };