stim 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,6 +1,6 @@
1
- import { T as upsertProject, d as getConfigDir, p as getProject } from "./config-lvc7d3C2.mjs";
2
- import { n as appProjectProblem } from "./project-CWlvEaeN.mjs";
3
- import { t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
1
+ import { E as upsertProject, d as getConfigDir, p as getProject } from "./config-D0DbpZtM.mjs";
2
+ import { n as appProjectProblem } from "./project-Du91Ilvc.mjs";
3
+ import { t as compareStimVersions } from "./stim-installations-QMEI32v1.mjs";
4
4
  import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  //#region src/guide-status.ts
@@ -0,0 +1,197 @@
1
+ import { t as BuildConnection, y as pinnedEndpoint$1 } from "./client-BTY686R6.mjs";
2
+ import { t as configuredMachines } from "./machines-BIBUdhpj.mjs";
3
+ import { createHash } from "node:crypto";
4
+ import { lstatSync, readFileSync, readdirSync, readlinkSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { HOSTED_APP_CHUNK_BYTES, hostedMacosAppSlot, isJsonObject, readDeviceHostMachines } from "@stim-cli/core/state";
7
+ //#region src/device-host/hosted-client.ts
8
+ const CONNECT_TIMEOUT_MS = 1e4;
9
+ /** Covers 90s stop + 15s final logs + three 10s worker group-settle bounds, with 45s for polling and transport. */
10
+ const SESSION_TIMEOUT_MS = 18e4;
11
+ const INSTALL_TIMEOUT_MS = 3e5;
12
+ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
13
+ const sha256 = (content) => createHash("sha256").update(content).digest("hex");
14
+ function credentialRefusal(message, remedy = "Run stim doctor.") {
15
+ return Object.assign(new Error(message), {
16
+ code: "STIM_HOSTING_REFUSED",
17
+ remedy
18
+ });
19
+ }
20
+ function hostingCredential(machine) {
21
+ const configured = configuredMachines();
22
+ if (configured === null) throw credentialRefusal("hosting.machines is invalid. Run stim guide settings and correct it.", "Run stim guide settings and correct hosting.machines.");
23
+ if (!configured.includes(machine)) throw Object.assign(/* @__PURE__ */ new Error(`${machine} is not in hosting.machines. Add it with stim settings set hosting.machines, then run stim doctor --fix to ask it for hosting access.`), { code: "STIM_BAD_ARG" });
24
+ let credentials;
25
+ try {
26
+ credentials = readDeviceHostMachines();
27
+ } catch {
28
+ throw credentialRefusal("The hosting credentials are unreadable. Run stim doctor.");
29
+ }
30
+ const credential = credentials.find((each) => each.machine === machine);
31
+ if (!credential) throw credentialRefusal(`This Mac has not asked ${machine} for hosting access. Run stim doctor --fix.`, "Run stim doctor --fix.");
32
+ if (credential.state !== "approved") {
33
+ const remedy = `A person on ${machine} approves it with stim-server devices grant ${credential.deviceId} --device-host; then run stim doctor.`;
34
+ throw credentialRefusal(`${machine} has not confirmed hosting access for this Mac. ${remedy}`, remedy);
35
+ }
36
+ return credential;
37
+ }
38
+ /** Connects only to the pinned node of an approved hosting machine, and only when it grants `device-host`. */
39
+ async function connectHost(machine, timeoutMs = CONNECT_TIMEOUT_MS, strict = false) {
40
+ const credential = hostingCredential(machine);
41
+ const target = pinnedEndpoint$1(credential);
42
+ if (typeof target === "string") throw Object.assign(/* @__PURE__ */ new Error(`Stim does not connect to ${machine}: ${target}.`), { code: "STIM_HOSTING_REFUSED" });
43
+ const opened = await BuildConnection.open(target, credential.deviceToken, timeoutMs, "device-host").catch((error) => {
44
+ throw Object.assign(new Error(`${machine} is unreachable. Check stim-server and its tailnet serve route on ${machine}.`, { cause: error }), { code: strict ? "STIM_HOSTING_REFUSED" : "closed" });
45
+ });
46
+ if (!(opened instanceof BuildConnection)) throw Object.assign(/* @__PURE__ */ new Error(opened.refused ? `${machine} refused this Mac: ${opened.failure.replaceAll(credential.deviceToken, "[redacted]").replace(/\.+$/, "")}. Run stim doctor.` : `${machine} is unreachable: ${opened.failure.replaceAll(credential.deviceToken, "[redacted]")}. Check stim-server and its tailnet serve route on ${machine}.`), {
47
+ code: strict ? "STIM_HOSTING_REFUSED" : opened.refused ? "STIM_HOSTING_REFUSED" : opened.code ?? "closed",
48
+ hostCode: opened.code
49
+ });
50
+ return {
51
+ machine,
52
+ credential,
53
+ connection: opened
54
+ };
55
+ }
56
+ const gatewaySecrets = /* @__PURE__ */ new WeakMap();
57
+ function redactHostText(host, message) {
58
+ for (const secret of [host.credential.deviceToken, gatewaySecrets.get(host)]) if (secret) message = message.replaceAll(secret, "[redacted]");
59
+ return message;
60
+ }
61
+ async function call(host, method, params, timeoutMs) {
62
+ if (method === "device-host.metro.open" && isJsonObject(params) && typeof params.secret === "string") gatewaySecrets.set(host, params.secret);
63
+ const reply = await host.connection.request(method, params, timeoutMs);
64
+ if ("error" in reply) throw Object.assign(/* @__PURE__ */ new Error(`${host.machine} refused ${method}: ${redactHostText(host, reply.error.message)}`), { code: reply.error.code });
65
+ if (!isJsonObject(reply.result)) throw new Error(`${host.machine} answered ${method} without a result.`);
66
+ return typeof reply.result.notice === "string" ? {
67
+ ...reply.result,
68
+ notice: redactHostText(host, reply.result.notice)
69
+ } : reply.result;
70
+ }
71
+ function hostedSession(host, value, platform = "macos") {
72
+ if (typeof value.id !== "string" || typeof value.state !== "string" || value.platform !== platform) throw new Error(`${host.machine} answered with a session that is not a hosted ${platform} session.`);
73
+ return {
74
+ id: value.id,
75
+ state: value.state,
76
+ device: value.device,
77
+ ...hostedMacosAppSlot(value.appSlot) ? { appSlot: value.appSlot } : {},
78
+ ...typeof value.notice === "string" ? { notice: value.notice } : {}
79
+ };
80
+ }
81
+ const heldNoLonger = (error) => error instanceof Error && "code" in error && error.code === "unknown-session";
82
+ async function attach(host, session, timeoutMs, platform = "macos") {
83
+ return hostedSession(host, await call(host, "device-host.attach", { session }, timeoutMs), platform);
84
+ }
85
+ const probeCache = /* @__PURE__ */ new Map();
86
+ function probeHostedSession(placement, { timeoutMs = 3e3, ttlMs = 1e4 } = {}) {
87
+ const key = JSON.stringify([placement.machine, placement.session]);
88
+ const cached = probeCache.get(key);
89
+ if (ttlMs > 0 && cached && cached.expiresAt > Date.now()) return cached.promise;
90
+ const promise = probeSession(placement, timeoutMs);
91
+ if (ttlMs > 0) {
92
+ const entry = {
93
+ expiresAt: Infinity,
94
+ promise
95
+ };
96
+ probeCache.delete(key);
97
+ probeCache.set(key, entry);
98
+ if (probeCache.size > 64) probeCache.delete(probeCache.keys().next().value);
99
+ promise.then((result) => {
100
+ entry.expiresAt = Date.now() + ttlMs;
101
+ return result;
102
+ });
103
+ }
104
+ return promise;
105
+ }
106
+ async function probeSession(placement, timeoutMs) {
107
+ let host;
108
+ try {
109
+ host = await connectHost(placement.machine, timeoutMs);
110
+ const session = await attach(host, placement.session, timeoutMs, placement.device === void 0 ? "macos" : "ios");
111
+ if (session.state === "ready" || session.state === "stopped") return { state: session.state };
112
+ return {
113
+ state: "unknown",
114
+ notice: session.state === "unknown" ? session.notice : `The hosted session ${session.id} on ${host.machine} is ${session.state}.${session.notice ? ` ${session.notice}` : ""}`
115
+ };
116
+ } catch (error) {
117
+ if (heldNoLonger(error)) return { state: "stopped" };
118
+ return {
119
+ state: "unreachable",
120
+ reason: error instanceof Error ? error.message : String(error)
121
+ };
122
+ } finally {
123
+ host?.connection.close();
124
+ }
125
+ }
126
+ async function settle(host, session, passing, timeoutMs, platform = "macos") {
127
+ const deadline = Date.now() + timeoutMs;
128
+ while (passing.includes(session.state)) {
129
+ if (Date.now() > deadline) throw new Error(`The hosted session ${session.id} on ${host.machine} stayed ${session.state}.`);
130
+ await sleep(500);
131
+ session = await attach(host, session.id, void 0, platform);
132
+ }
133
+ return session;
134
+ }
135
+ function unknownSession(host, session) {
136
+ return /* @__PURE__ */ new Error(`The hosted session ${session.id} on ${host.machine} is in an unknown state${session.notice ? `: ${session.notice}` : ""}. Run stim stop to reconcile it.`);
137
+ }
138
+ function bundleManifest(bundle) {
139
+ const files = [];
140
+ const content = /* @__PURE__ */ new Map();
141
+ const walk = (dir, prefix) => {
142
+ for (const name of readdirSync(dir)) {
143
+ const absolute = join(dir, name);
144
+ const path = prefix ? `${prefix}/${name}` : name;
145
+ const stat = lstatSync(absolute);
146
+ let read;
147
+ let kind;
148
+ if (stat.isDirectory()) {
149
+ walk(absolute, path);
150
+ continue;
151
+ } else if (stat.isSymbolicLink()) {
152
+ read = () => Buffer.from(readlinkSync(absolute));
153
+ kind = "link";
154
+ } else if (stat.isFile()) {
155
+ read = () => readFileSync(absolute);
156
+ kind = stat.mode & 73 ? "exec" : "file";
157
+ } else continue;
158
+ const bytes = read();
159
+ const digest = sha256(bytes);
160
+ files.push({
161
+ path,
162
+ kind,
163
+ size: bytes.length,
164
+ sha256: digest
165
+ });
166
+ content.set(digest, read);
167
+ }
168
+ };
169
+ walk(bundle, "");
170
+ files.sort((a, b) => a.path.localeCompare(b.path));
171
+ return {
172
+ files,
173
+ content
174
+ };
175
+ }
176
+ async function upload(host, ids, missing, content) {
177
+ if (!Array.isArray(missing)) throw new Error(`${host.machine} answered device-host.app.offer without missing content.`);
178
+ for (const entry of missing) {
179
+ const read = isJsonObject(entry) && typeof entry.sha256 === "string" ? content.get(entry.sha256) : void 0;
180
+ if (!read || !isJsonObject(entry) || typeof entry.offset !== "number") throw new Error(`${host.machine} asked for content this app does not have.`);
181
+ const bytes = read();
182
+ let offset = entry.offset;
183
+ do {
184
+ const data = bytes.subarray(offset, offset + HOSTED_APP_CHUNK_BYTES).toString("base64");
185
+ const next = (await call(host, "device-host.app.chunk", {
186
+ ...ids,
187
+ sha256: entry.sha256,
188
+ offset,
189
+ data
190
+ })).offset;
191
+ if (typeof next !== "number" || next <= offset && next !== bytes.length) throw new Error(`${host.machine} did not accept app content at byte ${offset}.`);
192
+ offset = next;
193
+ } while (offset < bytes.length);
194
+ }
195
+ }
196
+ //#endregion
197
+ export { call as a, hostedSession as c, sha256 as d, sleep as f, bundleManifest as i, probeHostedSession as l, upload as m, SESSION_TIMEOUT_MS as n, connectHost as o, unknownSession as p, attach as r, heldNoLonger as s, INSTALL_TIMEOUT_MS as t, settle as u };
@@ -0,0 +1,220 @@
1
+ import { r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-B8-aJ1_5.mjs";
2
+ import { a as call, c as hostedSession, d as sha256, f as sleep, i as bundleManifest, m as upload, n as SESSION_TIMEOUT_MS, o as connectHost, p as unknownSession, r as attach, s as heldNoLonger, t as INSTALL_TIMEOUT_MS, u as settle } from "./hosted-client-a-WmqE2L.mjs";
3
+ import { i as requireHostedMetro, n as closeHostedMetro, r as requestHostedMetro } from "./metro-gateway-BJ-rxQAB.mjs";
4
+ import { i as agentAccess, r as closeAgentConnection, t as pullHostedIosLogs } from "./hosted-logs-D5g041Q3.mjs";
5
+ import { n as writeHostedIos } from "./ios-state-DS5UpYTH.mjs";
6
+ import { randomUUID } from "node:crypto";
7
+ import { rmSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { hostedIosRecords, isJsonObject, parseHostedChoice, parseHostedDevice, parseHostedIosPlacement, unreadableHostedIos } from "@stim-cli/core/state";
10
+ //#region src/device-host/hosted-ios.ts
11
+ function hostingRefusal(machine, error) {
12
+ return Object.assign(/* @__PURE__ */ new Error(`${machine}: ${error instanceof Error ? error.message : String(error)}`), {
13
+ code: "STIM_HOSTING_REFUSED",
14
+ ...error instanceof Error && "remedy" in error ? { remedy: error.remedy } : {}
15
+ });
16
+ }
17
+ const iosAgentRemoteConfig = (root, slot) => join(workspaceDir(root), "hosted-ios", slot, "agent-device-remote.json");
18
+ async function prepareHostedIos(machine, selectors, recorded) {
19
+ let host;
20
+ try {
21
+ host = await connectHost(machine, void 0, true);
22
+ if (recorded) {
23
+ let session = null;
24
+ try {
25
+ session = await settle(host, await attach(host, recorded.session, void 0, "ios"), ["preparing", "stopping"], SESSION_TIMEOUT_MS, "ios");
26
+ } catch (error) {
27
+ if (!heldNoLonger(error)) throw error;
28
+ }
29
+ if (session && session.state !== "stopped") {
30
+ if (session.state !== "ready") throw unknownSession(host, session);
31
+ const device = parseHostedDevice(session.device);
32
+ if (!device) throw new Error("The ready session has no simulator identity.");
33
+ if (selectors.deviceType && selectors.deviceType !== device.deviceType || selectors.runtime && selectors.runtime.replace(/^iOS /, "") !== device.runtime.replace(/^iOS /, "")) throw new Error(`This session uses ${device.deviceType} (iOS ${device.runtime.replace(/^iOS /, "")}); run stim stop first to change it.`);
34
+ return {
35
+ host,
36
+ choice: device,
37
+ session
38
+ };
39
+ }
40
+ }
41
+ const offer = await call(host, "device-host.offer", {
42
+ platform: "ios",
43
+ ...selectors
44
+ }, 3e3);
45
+ const choice = offer.platform === "ios" ? parseHostedChoice(offer.choice) : null;
46
+ if (typeof offer.declined === "string") throw new Error(offer.declined);
47
+ if (!choice) throw new Error("No compatible installed iOS simulator runtime was offered.");
48
+ if (!isJsonObject(offer.capacity) || offer.capacity.available === 0) throw new Error("No hosted device capacity is available.");
49
+ if (!isJsonObject(offer.resources) || offer.resources.memoryPressure !== "normal") throw new Error("Host memory pressure is unknown or elevated.");
50
+ return {
51
+ host,
52
+ choice,
53
+ session: null
54
+ };
55
+ } catch (error) {
56
+ if (error instanceof Error && "code" in error && error.code === "STIM_BAD_ARG") throw error;
57
+ throw hostingRefusal(machine, error);
58
+ } finally {
59
+ host?.connection.close();
60
+ }
61
+ }
62
+ async function placeHostedIos(target, { root, slot, bundle, handoff, bundleId, selectors, release, devClientScheme, reserved, note, metro = requestHostedMetro }) {
63
+ let host = target.host;
64
+ try {
65
+ if (!release && metro === requestHostedMetro) requireHostedMetro(root);
66
+ host = await connectHost(host.machine, void 0, true);
67
+ target.host = host;
68
+ let session = (target.session ? await attach(host, target.session.id, void 0, "ios") : null) ?? hostedSession(host, await call(host, "device-host.reserve", {
69
+ platform: "ios",
70
+ workspace: root,
71
+ slot,
72
+ attempt: randomUUID(),
73
+ ...selectors
74
+ }), "ios");
75
+ let placement = {
76
+ machine: host.machine,
77
+ selected: host.machine,
78
+ session: session.id,
79
+ appAttempt: randomUUID(),
80
+ device: parseHostedDevice(session.device),
81
+ agent: {
82
+ driver: "none",
83
+ setting: "hosting.agentDriver"
84
+ }
85
+ };
86
+ reserved(placement);
87
+ session = await settle(host, session, ["preparing"], SESSION_TIMEOUT_MS, "ios");
88
+ if (session.state !== "ready") throw unknownSession(host, session);
89
+ const device = parseHostedDevice(session.device);
90
+ if (!device || device.architecture !== target.choice.architecture) throw new Error("The reserved simulator does not match the offered architecture.");
91
+ placement = {
92
+ ...placement,
93
+ device
94
+ };
95
+ reserved(placement);
96
+ if (!release) {
97
+ const gateway = await metro(root, session.id, host.credential);
98
+ await call(host, "device-host.metro.open", {
99
+ session: session.id,
100
+ ...gateway
101
+ });
102
+ } else {
103
+ await call(host, "device-host.metro.close", { session: session.id });
104
+ await closeHostedMetro(root, session.id);
105
+ }
106
+ const { files, content } = bundleManifest(bundle);
107
+ const manifest = Buffer.from(JSON.stringify(files));
108
+ content.set(sha256(manifest), () => manifest);
109
+ const ids = {
110
+ session: session.id,
111
+ attempt: placement.appAttempt
112
+ };
113
+ const offer = {
114
+ ...ids,
115
+ bundleId,
116
+ mode: release ? "release" : "development",
117
+ ...!release && devClientScheme ? { devClientScheme } : {},
118
+ manifest: {
119
+ sha256: sha256(manifest),
120
+ size: manifest.length
121
+ }
122
+ };
123
+ note(`Delivering ${files.length} files to ${host.machine} (${device.runtime}, ${device.architecture})`);
124
+ await upload(host, ids, (await call(host, "device-host.app.offer", offer)).missing, content);
125
+ let missing = (await call(host, "device-host.app.offer", offer)).missing;
126
+ if (handoff && handoff.nodeId === host.credential.nodeId && Array.isArray(missing) && missing.length) {
127
+ if (!host.connection.supports("hosted-ios-data")) note(`${host.machine} needs a newer stim-server for iOS build handoff; uploading the app instead`);
128
+ else try {
129
+ const taken = await call(host, "device-host.app.handoff", {
130
+ ...ids,
131
+ build: {
132
+ handoff: handoff.token,
133
+ sha256: handoff.sha256
134
+ }
135
+ }, 6e4);
136
+ if (taken.files) note(`${host.machine} took ${String(taken.files)} files from the build it ran`);
137
+ missing = (await call(host, "device-host.app.offer", offer)).missing;
138
+ } catch (error) {
139
+ note(`${error instanceof Error ? error.message : String(error)}; uploading the app instead`);
140
+ const fallbackDeadline = Date.now() + 6e4;
141
+ for (;;) try {
142
+ missing = (await call(host, "device-host.app.offer", offer, Math.max(1, Math.min(2e4, fallbackDeadline - Date.now())))).missing;
143
+ break;
144
+ } catch (offerError) {
145
+ if (!String(offerError).includes("native operation in progress") || Date.now() >= fallbackDeadline) throw offerError;
146
+ await sleep(Math.min(500, Math.max(0, fallbackDeadline - Date.now())));
147
+ }
148
+ }
149
+ }
150
+ await upload(host, ids, missing, content);
151
+ note(`Launching on ${host.machine}`);
152
+ let delivery = await call(host, "device-host.app.launch", ids);
153
+ const deadline = Date.now() + INSTALL_TIMEOUT_MS;
154
+ while (delivery.state !== "installed") {
155
+ if (delivery.state === "unknown") throw new Error(typeof delivery.notice === "string" ? delivery.notice : "App installation or launch is unknown.");
156
+ if (Date.now() > deadline) throw new Error("App installation did not finish.");
157
+ await sleep(500);
158
+ delivery = await call(host, "device-host.app.attach", ids);
159
+ }
160
+ if (typeof delivery.notice === "string") note(delivery.notice);
161
+ placement = {
162
+ ...placement,
163
+ agent: agentAccess(iosAgentRemoteConfig(root, slot), host.credential, host.connection.supports("hosted-ios-agent") ? delivery.agent : void 0, note, "ios")
164
+ };
165
+ return {
166
+ placement,
167
+ launched: release && delivery.launched === true ? true : "unverified"
168
+ };
169
+ } catch (error) {
170
+ throw hostingRefusal(host.machine, error);
171
+ }
172
+ }
173
+ async function stopHostedIos(root, slot) {
174
+ const failures = [];
175
+ for (const [name, record] of Object.entries(hostedIosRecords(readWorkspaceState$1(root)))) {
176
+ if (slot !== void 0 && name !== slot) continue;
177
+ const placement = parseHostedIosPlacement(record);
178
+ if (!placement) {
179
+ failures.push(unreadableHostedIos(name));
180
+ continue;
181
+ }
182
+ if (placement.agent.driver === "agent-device") closeAgentConnection(placement.agent.remoteConfig);
183
+ let host;
184
+ try {
185
+ host = await connectHost(placement.machine);
186
+ try {
187
+ await pullHostedIosLogs(root, name, placement, host, true);
188
+ } catch (error) {
189
+ process.stderr.write(`Could not copy final native logs from ${placement.machine}: ${error.message}\n`);
190
+ }
191
+ const stopped = await settle(host, hostedSession(host, await call(host, "device-host.stop", { session: placement.session }), "ios"), ["stopping"], SESSION_TIMEOUT_MS, "ios");
192
+ if (stopped.state !== "stopped") throw unknownSession(host, stopped);
193
+ try {
194
+ await pullHostedIosLogs(root, name, placement, host, true);
195
+ } catch (error) {
196
+ process.stderr.write(`Could not copy the host's final native logs from ${placement.machine}: ${error.message}\n`);
197
+ }
198
+ } catch (error) {
199
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
200
+ const hostCode = error instanceof Error && "hostCode" in error ? error.hostCode : code;
201
+ if (hostCode !== "unknown-session" && hostCode !== "forbidden") {
202
+ const remedy = code === "closed" || code === "timeout" ? "rerun stim stop when that machine answers." : code === "STIM_BAD_ARG" || code === "STIM_HOSTING_REFUSED" ? "run stim doctor, restore hosting access, then rerun stim stop." : "run stim stop to reconcile the session.";
203
+ failures.push(`Could not stop the iOS simulator on ${placement.machine}: ${(error instanceof Error ? error.message : String(error)).replace(/\.+$/, "")}. The placement is kept; ${remedy}`);
204
+ continue;
205
+ }
206
+ } finally {
207
+ host?.connection.close();
208
+ }
209
+ try {
210
+ await closeHostedMetro(root, placement.session);
211
+ rmSync(iosAgentRemoteConfig(root, name), { force: true });
212
+ writeHostedIos(root, name, null);
213
+ } catch (error) {
214
+ failures.push(`${placement.machine}: ${error.message}. The placement is kept; rerun stim stop.`);
215
+ }
216
+ }
217
+ if (failures.length) throw new Error(failures.join("\n"));
218
+ }
219
+ //#endregion
220
+ export { prepareHostedIos as n, stopHostedIos as r, placeHostedIos as t };
@@ -0,0 +1,187 @@
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { L as deviceSlotFileKey, W as validateDeviceSlot } from "./config-D0DbpZtM.mjs";
3
+ import { l as phaseLine } from "./command-output-DnJMdEY-.mjs";
4
+ import { v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
5
+ import { t as macosDir } from "./state-3DwSQIBK.mjs";
6
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
7
+ import { v as parseMachine$1 } from "./client-BTY686R6.mjs";
8
+ import { a as call } from "./hosted-client-a-WmqE2L.mjs";
9
+ import { mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
10
+ import { dirname, join } from "node:path";
11
+ import { LOG_ROTATE_BYTES, withDirLock } from "@stim-cli/core";
12
+ import { isJsonObject, parseHostedAgentGrant, parseHostedLogsCursor } from "@stim-cli/core/state";
13
+ //#region src/device-host/hosted-agent.ts
14
+ function agentAccess(file, credential, grant, note, platform = "macos") {
15
+ const parsed = grant === void 0 ? null : parseHostedAgentGrant(grant);
16
+ if (grant !== void 0 && !parsed) note(`${credential.machine} offered agent control this Stim does not support.`);
17
+ if (!parsed || parsed.driver === "none" || parsed.lease.backend === "ios-instance" !== (platform === "ios")) {
18
+ rmSync(file, { force: true });
19
+ return {
20
+ driver: "none",
21
+ setting: "hosting.agentDriver"
22
+ };
23
+ }
24
+ const port = parseMachine$1(credential.machine)?.port ?? 443;
25
+ const config = {
26
+ daemonBaseUrl: `https://${credential.dnsName}${port === 443 ? "" : `:${port}`}${parsed.path}`,
27
+ daemonAuthToken: parsed.token,
28
+ tenant: parsed.lease.tenant,
29
+ sessionIsolation: "tenant",
30
+ runId: parsed.lease.runId,
31
+ ...platform === "macos" ? { leaseId: parsed.scope } : {},
32
+ leaseBackend: platform === "ios" ? "ios-instance" : "macos-app",
33
+ leaseProvider: "proxy",
34
+ clientId: parsed.lease.clientId,
35
+ deviceKey: parsed.lease.deviceKey,
36
+ platform
37
+ };
38
+ mkdirSync(dirname(file), { recursive: true });
39
+ const tmp = `${file}.${process.pid}.tmp`;
40
+ try {
41
+ writeFileSync(tmp, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
42
+ renameSync(tmp, file);
43
+ } finally {
44
+ rmSync(tmp, { force: true });
45
+ }
46
+ return {
47
+ driver: "agent-device",
48
+ remoteConfig: file,
49
+ command: `agent-device <command> --remote-config ${file}`
50
+ };
51
+ }
52
+ //#endregion
53
+ //#region src/device-host/agent-connection.ts
54
+ function canonical(path) {
55
+ try {
56
+ return realpathSync(path);
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+ function report(message) {
62
+ process.stderr.write(`${phaseLine("device", message.replace(/\s+/g, " "))}\n`);
63
+ }
64
+ function closeAgentConnection(remoteConfig) {
65
+ const exec = getExecutor();
66
+ const deadline = Date.now() + 3e4;
67
+ const run = (args) => {
68
+ const remaining = deadline - Date.now();
69
+ if (remaining <= 0) throw new Error("agent-device cleanup timed out");
70
+ const result = JSON.parse(exec.runFile("agent-device", [...args, "--json"], {
71
+ timeoutMs: Math.min(1e4, remaining),
72
+ killSignal: "SIGKILL"
73
+ }));
74
+ if (result?.success !== true) throw new Error("agent-device did not confirm the command");
75
+ return result.data;
76
+ };
77
+ try {
78
+ if (!exec.findExecutable("agent-device")) return;
79
+ const status = run(["connection", "status"]);
80
+ if (status?.connected !== true || typeof status.session !== "string" || typeof status.remoteConfig !== "string") return;
81
+ const config = canonical(remoteConfig);
82
+ if (config === null || canonical(status.remoteConfig) !== config) return;
83
+ const failures = [];
84
+ for (const args of [[
85
+ "close",
86
+ "--remote-config",
87
+ remoteConfig,
88
+ "--session",
89
+ status.session
90
+ ], [
91
+ "disconnect",
92
+ "--session",
93
+ status.session
94
+ ]]) try {
95
+ run(args);
96
+ } catch (error) {
97
+ failures.push(error.message);
98
+ }
99
+ report(failures.length ? `could not clean up agent-device connection for ${status.session}: ${failures.join("; ")}` : `closed agent-device connection for ${status.session}`);
100
+ } catch (error) {
101
+ report(`could not clean up agent-device connection: ${error.message}`);
102
+ }
103
+ }
104
+ //#endregion
105
+ //#region src/device-host/hosted-logs.ts
106
+ const MAX_PAGES = 64;
107
+ const MACOS = {
108
+ platform: "macos",
109
+ slot: "default"
110
+ };
111
+ const logsStateDir = (root, target) => target.platform === "macos" ? macosDir(root) : join(workspaceDir(root), "ios", validateDeviceSlot(target.slot));
112
+ const cursorFile = (root, target) => join(logsStateDir(root, target), "host-logs-cursor.json");
113
+ const hostLogFile = (root, target) => join(workspaceLogsDir$1(root), `${target.platform === "macos" ? "macos" : deviceSlotFileKey("ios", target.slot)}-host.ndjson`);
114
+ function readCursor(root, session, target) {
115
+ try {
116
+ const stored = JSON.parse(readFileSync(cursorFile(root, target), "utf8"));
117
+ if (isJsonObject(stored) && stored.session === session) return parseHostedLogsCursor(stored.cursor) ?? void 0;
118
+ } catch {}
119
+ }
120
+ const sameCursor = (a, b) => JSON.stringify(Object.entries(a ?? {}).toSorted()) === JSON.stringify(Object.entries(b ?? {}).toSorted());
121
+ function commitPage(root, session, before, records, cursor, target) {
122
+ if (!records.length && sameCursor(before, cursor)) return;
123
+ const directory = logsStateDir(root, target);
124
+ withDirLock(join(directory, "host-logs.lock"), () => {
125
+ if (!sameCursor(readCursor(root, session, target), before)) return;
126
+ if (records.length) {
127
+ const writer = createNdjsonWriter(hostLogFile(root, target), { maxBytes: LOG_ROTATE_BYTES });
128
+ try {
129
+ for (const record of records) writer.write(target.platform === "ios" ? {
130
+ ...record,
131
+ src: "device",
132
+ platform: "ios",
133
+ slot: target.slot,
134
+ event: "hosted_native_log"
135
+ } : record);
136
+ } finally {
137
+ writer.close();
138
+ }
139
+ }
140
+ const temporary = `${cursorFile(root, target)}.${process.pid}.tmp`;
141
+ try {
142
+ writeFileSync(temporary, JSON.stringify({
143
+ session,
144
+ cursor
145
+ }), { mode: 384 });
146
+ renameSync(temporary, cursorFile(root, target));
147
+ } finally {
148
+ rmSync(temporary, { force: true });
149
+ }
150
+ }, { ensureParent: () => mkdirSync(directory, { recursive: true }) });
151
+ }
152
+ async function pullHostedMacosLogs(root, placement, host, target = MACOS, maxPages = MAX_PAGES, final = false) {
153
+ const deadline = Date.now() + (final ? 3e4 : 12e4);
154
+ let checkpoint;
155
+ let progressAt = 0;
156
+ for (let page = 0; page < maxPages && Date.now() < deadline; page++) {
157
+ const before = readCursor(root, placement.session, target);
158
+ if (final && Date.now() >= progressAt) {
159
+ process.stderr.write(`Copying final native logs from ${host.machine} (page ${page + 1})\n`);
160
+ progressAt = Date.now() + 5e3;
161
+ }
162
+ const result = await call(host, "device-host.logs.query", {
163
+ session: placement.session,
164
+ ...before ? { cursor: before } : {}
165
+ }, Math.max(1, Math.min(2e4, deadline - Date.now())));
166
+ const cursor = isJsonObject(result) ? parseHostedLogsCursor(result.cursor) : null;
167
+ if (!isJsonObject(result) || !cursor || !Array.isArray(result.records) || !result.records.every(isJsonObject) || typeof result.more !== "boolean") throw new Error(`${host.machine} answered device-host.logs.query with an unexpected result.`);
168
+ commitPage(root, placement.session, before, result.records, cursor, target);
169
+ if (!result.more) return;
170
+ const nextCheckpoint = typeof result.checkpoint === "number" ? result.checkpoint : void 0;
171
+ if (sameCursor(cursor, before) && nextCheckpoint === checkpoint) {
172
+ if (target.platform === "macos" && !final) return;
173
+ throw new Error("Hosted native logs made no progress; keeping the logs already copied here.");
174
+ }
175
+ checkpoint = nextCheckpoint;
176
+ }
177
+ if (target.platform === "ios") throw new Error(final ? "Final native log copy reached its time or page bound; the host keeps collected logs after stop." : "Hosted native logs still have unread pages; run stim logs again.");
178
+ }
179
+ async function pullHostedIosLogs(root, slot, placement, host, final = false) {
180
+ if (!host.connection.supports("hosted-ios-data")) throw new Error(`${host.machine} needs a newer stim-server to collect hosted iOS native logs.`);
181
+ await pullHostedMacosLogs(root, placement, host, {
182
+ platform: "ios",
183
+ slot
184
+ }, final ? 1024 : MAX_PAGES, final);
185
+ }
186
+ //#endregion
187
+ export { agentAccess as i, pullHostedMacosLogs as n, closeAgentConnection as r, pullHostedIosLogs as t };