sootsim 0.0.3 → 0.1.36

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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -4
  3. package/dist-cli/bin.js +12 -12
  4. package/dist-cli/chunks/{agent-D5NBV32O.js → agent-YZB6D3DR.js} +4 -4
  5. package/dist-cli/chunks/agent-wrapper-VHCVS22I.js +15 -0
  6. package/dist-cli/chunks/{assert-EJ7DQS2H.js → assert-AIVCKKLG.js} +2 -2
  7. package/dist-cli/chunks/auto-bootstrap-MLNTX23H.js +2 -0
  8. package/dist-cli/chunks/chunk-27P763IZ.js +61 -0
  9. package/dist-cli/chunks/chunk-3UIWOHC2.js +62 -0
  10. package/dist-cli/chunks/chunk-5KGFHWVR.js +1 -0
  11. package/dist-cli/chunks/chunk-5QIUJNT3.js +5 -0
  12. package/dist-cli/chunks/{chunk-RLS6PHBW.js → chunk-6GGMKFWJ.js} +1 -1
  13. package/dist-cli/chunks/{chunk-CQ6PX2EU.js → chunk-6Z275LCY.js} +2 -2
  14. package/dist-cli/chunks/chunk-75LBYBKW.js +11 -0
  15. package/dist-cli/chunks/chunk-A5BRCXYE.js +2 -0
  16. package/dist-cli/chunks/{chunk-FTRI7SVV.js → chunk-CYCXOAVZ.js} +2 -2
  17. package/dist-cli/chunks/{chunk-5IPP4HAW.js → chunk-DFN3GGH7.js} +2 -2
  18. package/dist-cli/chunks/chunk-EBEHZJRG.js +117 -0
  19. package/dist-cli/chunks/{chunk-3SLEIN6B.js → chunk-EJLNUMMP.js} +1 -1
  20. package/dist-cli/chunks/{chunk-3K6VDPVD.js → chunk-EWSQSALM.js} +2 -2
  21. package/dist-cli/chunks/{chunk-NKJLTISU.js → chunk-FE7UI3MT.js} +4 -4
  22. package/dist-cli/chunks/chunk-G663654J.js +1 -0
  23. package/dist-cli/chunks/chunk-G7XQD4KC.js +4 -0
  24. package/dist-cli/chunks/chunk-GW7XY5KC.js +2 -0
  25. package/dist-cli/chunks/{chunk-O2HBPZW5.js → chunk-H2QO4TDV.js} +2 -2
  26. package/dist-cli/chunks/{chunk-UZL5ZZ4E.js → chunk-HWCKZXNJ.js} +2 -2
  27. package/dist-cli/chunks/{chunk-BYLX2DO4.js → chunk-HWFHBMAQ.js} +2 -2
  28. package/dist-cli/chunks/chunk-IJMYFYDZ.js +2 -0
  29. package/dist-cli/chunks/chunk-J7CTD37P.js +1 -0
  30. package/dist-cli/chunks/{chunk-44CBTM22.js → chunk-KAXZHEKM.js} +1 -1
  31. package/dist-cli/chunks/{chunk-P5C3UASK.js → chunk-LHDWH7VS.js} +1 -1
  32. package/dist-cli/chunks/{chunk-H3JVJXOC.js → chunk-N32NCVL2.js} +2 -2
  33. package/dist-cli/chunks/{chunk-D4JFMCXD.js → chunk-NIZBR7EK.js} +2 -2
  34. package/dist-cli/chunks/{chunk-XJBPH4JR.js → chunk-NYY36OKU.js} +12 -12
  35. package/dist-cli/chunks/{chunk-SUZR2SZZ.js → chunk-OXN2PEB7.js} +1 -1
  36. package/dist-cli/chunks/{chunk-46LRF7PH.js → chunk-PJL25JQV.js} +1 -1
  37. package/dist-cli/chunks/{chunk-OG5CKIPC.js → chunk-RMW5BO3S.js} +2 -2
  38. package/dist-cli/chunks/chunk-SHO54NET.js +2 -0
  39. package/dist-cli/chunks/chunk-SMVJOWSV.js +16 -0
  40. package/dist-cli/chunks/chunk-TC6V7YFC.js +3 -0
  41. package/dist-cli/chunks/{chunk-VI3VW5BL.js → chunk-VFDRZNPN.js} +1 -1
  42. package/dist-cli/chunks/{chunk-AFTHIY3L.js → chunk-YIO6S3R5.js} +1 -1
  43. package/dist-cli/chunks/{chunk-X2W4IRXK.js → chunk-YLIIVTTQ.js} +2 -2
  44. package/dist-cli/chunks/chunk-YR7BGGYE.js +2 -0
  45. package/dist-cli/chunks/chunk-ZEW3RF5Q.js +1 -0
  46. package/dist-cli/chunks/{compat-5KSMOWLB.js → compat-Y2O2U7FL.js} +2 -2
  47. package/dist-cli/chunks/{config-NJB6PQHU.js → config-SRBOFUCI.js} +2 -2
  48. package/dist-cli/chunks/control-PL2V2O6S.js +2 -0
  49. package/dist-cli/chunks/daemon-IZC32PZW.js +50 -0
  50. package/dist-cli/chunks/{debug-QVOBTTLP.js → debug-BIDMW2PE.js} +3 -3
  51. package/dist-cli/chunks/demo-app-registry-5JFOUU3D.js +2 -0
  52. package/dist-cli/chunks/{detox-ZZSNZL4T.js → detox-B3FDOIS3.js} +2 -2
  53. package/dist-cli/chunks/{device-PQB3YGHN.js → device-ZZSI363W.js} +2 -2
  54. package/dist-cli/chunks/drivers-S4NGK4DB.js +2 -0
  55. package/dist-cli/chunks/{electron-JB26VHOO.js → electron-5YFHXEOI.js} +3 -3
  56. package/dist-cli/chunks/flow-JJBO6TFY.js +2 -0
  57. package/dist-cli/chunks/{hints-IGYDXXDS.js → hints-G5HBBV2O.js} +2 -2
  58. package/dist-cli/chunks/home-paths-VWC3FWA3.js +2 -0
  59. package/dist-cli/chunks/{inspect-DSU6ELRM.js → inspect-POOPWUQI.js} +66 -62
  60. package/dist-cli/chunks/install-MP6FHXNZ.js +2 -0
  61. package/dist-cli/chunks/install-desktop-2MYEI4FM.js +23 -0
  62. package/dist-cli/chunks/{install-dev-desktop-4DP3UY2X.js → install-dev-desktop-SKH3KEHY.js} +2 -2
  63. package/dist-cli/chunks/{keys-R5LAPAAL.js → keys-7PNASIQR.js} +2 -2
  64. package/dist-cli/chunks/{launch-K3WJV4QA.js → launch-JNS47LAQ.js} +3 -3
  65. package/dist-cli/chunks/login-YWZWUHBS.js +26 -0
  66. package/dist-cli/chunks/logout-O6SXMSBP.js +2 -0
  67. package/dist-cli/chunks/{maestro-YALWKKGU.js → maestro-CW6XVUKV.js} +3 -3
  68. package/dist-cli/chunks/{preview-D35EEONY.js → preview-WGKJO5FS.js} +2 -2
  69. package/dist-cli/chunks/{profile-MAF7NM5Q.js → profile-SUOBRPIC.js} +2 -2
  70. package/dist-cli/chunks/{record-ZCPQNGFW.js → record-QPWLYH5R.js} +2 -2
  71. package/dist-cli/chunks/runtime-KEMO2MSB.js +25 -0
  72. package/dist-cli/chunks/{screenshot-NQVZYC3C.js → screenshot-JTY46V7G.js} +2 -2
  73. package/dist-cli/chunks/{screenshot-mode-E45D2ZFH.js → screenshot-mode-7OYBBX6D.js} +2 -2
  74. package/dist-cli/chunks/{screenshots-I4SQI4DA.js → screenshots-QISKC4GD.js} +2 -2
  75. package/dist-cli/chunks/server-YSFJAKAV.js +34 -0
  76. package/dist-cli/chunks/setup-repo-LFB3HBEO.js +2 -0
  77. package/dist-cli/chunks/{skills-N4U63E5W.js → skills-MO7BFNVM.js} +2 -2
  78. package/dist-cli/chunks/store-6MFL53I4.js +2 -0
  79. package/dist-cli/chunks/telemetry-CN42GMVC.js +2 -0
  80. package/dist-cli/chunks/{test-VBD6N3AR.js → test-XUI3KNNQ.js} +3 -3
  81. package/dist-cli/chunks/upload-6FUT7AX5.js +2 -0
  82. package/dist-cli/chunks/{whoami-4K6JGMWH.js → whoami-TQFHY42N.js} +2 -2
  83. package/dist-lib/agent-daemon-client.cjs +3 -1
  84. package/dist-lib/agent-events.cjs +1 -1
  85. package/dist-lib/agent-sessions.cjs +2 -1
  86. package/dist-lib/attached-projects.cjs +1 -1
  87. package/dist-lib/auth/shared-session.cjs +1 -1
  88. package/dist-lib/backend-origin.cjs +1 -1
  89. package/dist-lib/bridge-constants.cjs +1 -1
  90. package/dist-lib/cli-constants.cjs +1 -1
  91. package/dist-lib/config.cjs +1 -1
  92. package/dist-lib/dev-bundle-resolution.cjs +3 -1
  93. package/dist-lib/home-paths.cjs +29 -3
  94. package/dist-lib/host/bridge-host.cjs +499 -59
  95. package/dist-lib/index.cjs +2 -2
  96. package/dist-lib/metro.cjs +2 -2
  97. package/dist-lib/render-mode.cjs +1 -1
  98. package/dist-lib/vite-base.cjs +800 -102
  99. package/dist-lib/vite.cjs +1 -1
  100. package/package.json +5 -3
  101. package/dist-cli/chunks/agent-wrapper-Y7I5QGHM.js +0 -15
  102. package/dist-cli/chunks/auto-bootstrap-Q7GNLISM.js +0 -2
  103. package/dist-cli/chunks/chunk-2FPPPJE5.js +0 -2
  104. package/dist-cli/chunks/chunk-3WPAEUOO.js +0 -1
  105. package/dist-cli/chunks/chunk-4RYT6AQV.js +0 -16
  106. package/dist-cli/chunks/chunk-5AG24UFX.js +0 -119
  107. package/dist-cli/chunks/chunk-BU3TZP4Y.js +0 -11
  108. package/dist-cli/chunks/chunk-CPMW2QLM.js +0 -1
  109. package/dist-cli/chunks/chunk-EEBR5YP5.js +0 -62
  110. package/dist-cli/chunks/chunk-EQ7G3UHS.js +0 -4
  111. package/dist-cli/chunks/chunk-LV5U7TI4.js +0 -1
  112. package/dist-cli/chunks/chunk-REYWQVAH.js +0 -2
  113. package/dist-cli/chunks/chunk-USRNDVQ3.js +0 -2
  114. package/dist-cli/chunks/chunk-WUYJFYOW.js +0 -2
  115. package/dist-cli/chunks/chunk-ZSRMXBGK.js +0 -2
  116. package/dist-cli/chunks/control-2F3AGZAO.js +0 -2
  117. package/dist-cli/chunks/daemon-MLG65V4S.js +0 -49
  118. package/dist-cli/chunks/demo-app-registry-XRYNJ4GC.js +0 -2
  119. package/dist-cli/chunks/drivers-GWDQEGWD.js +0 -2
  120. package/dist-cli/chunks/flow-7JRQXMFV.js +0 -2
  121. package/dist-cli/chunks/home-paths-CEGSGQTD.js +0 -2
  122. package/dist-cli/chunks/install-K6IJKADG.js +0 -65
  123. package/dist-cli/chunks/install-desktop-SC3LNFFF.js +0 -23
  124. package/dist-cli/chunks/login-A23PYJAW.js +0 -26
  125. package/dist-cli/chunks/logout-AJ24PH5O.js +0 -2
  126. package/dist-cli/chunks/runtime-Z2WIXYUN.js +0 -25
  127. package/dist-cli/chunks/server-ZUXKJRR5.js +0 -29
  128. package/dist-cli/chunks/store-4A6X4GBJ.js +0 -2
  129. package/dist-cli/chunks/upload-Y6FZ5XF2.js +0 -2
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.0.3 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.36 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -35,10 +35,10 @@ __export(bridge_host_exports, {
35
35
  SootSimBridgeHost: () => SootSimBridgeHost
36
36
  });
37
37
  module.exports = __toCommonJS(bridge_host_exports);
38
- var import_child_process2 = require("child_process");
39
- var import_fs2 = __toESM(require("fs"), 1);
38
+ var import_child_process4 = require("child_process");
39
+ var import_fs4 = __toESM(require("fs"), 1);
40
40
  var import_http2 = require("http");
41
- var import_path2 = __toESM(require("path"), 1);
41
+ var import_path4 = __toESM(require("path"), 1);
42
42
  var import_ws = require("ws");
43
43
 
44
44
  // src/bridge-constants.ts
@@ -51,6 +51,7 @@ var import_path = __toESM(require("path"), 1);
51
51
  var SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
52
52
  var ACTIVE_RUNTIME_FILE = "active";
53
53
  var DAEMON_LOCKFILE = "daemon.json";
54
+ var CONFIG_FILE = "config.json";
54
55
  var DAEMON_HEARTBEAT_STALE_MS = 3e4;
55
56
  function sootsimHomeDir() {
56
57
  const override = process.env[SOOTSIM_HOME_ENV];
@@ -72,6 +73,9 @@ function cacheDir() {
72
73
  function daemonLockfilePath() {
73
74
  return import_path.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
74
75
  }
76
+ function configFilePath() {
77
+ return import_path.default.join(sootsimHomeDir(), CONFIG_FILE);
78
+ }
75
79
  function ensureSootsimHome() {
76
80
  import_fs.default.mkdirSync(sootsimHomeDir(), { recursive: true });
77
81
  import_fs.default.mkdirSync(runtimesDir(), { recursive: true });
@@ -181,12 +185,223 @@ function removeDaemonLockfile() {
181
185
  }
182
186
  }
183
187
 
188
+ // src/runtime-delivery.ts
189
+ var import_child_process = require("child_process");
190
+ var import_crypto = __toESM(require("crypto"), 1);
191
+ var import_fs2 = __toESM(require("fs"), 1);
192
+ var import_path2 = __toESM(require("path"), 1);
193
+ var import_stream = require("stream");
194
+ var import_promises = require("stream/promises");
195
+ var DEFAULT_RUNTIME_CDN_ORIGIN = "https://sootbean.com";
196
+ var RUNTIME_CDN_ORIGIN_ENV = "SOOTSIM_CDN_ORIGIN";
197
+ var RUNTIME_CHANNEL_ENV = "SOOTSIM_RUNTIME_CHANNEL";
198
+ function readConfig() {
199
+ try {
200
+ const parsed = JSON.parse(import_fs2.default.readFileSync(configFilePath(), "utf8"));
201
+ return parsed && typeof parsed === "object" ? parsed : {};
202
+ } catch {
203
+ return {};
204
+ }
205
+ }
206
+ function resolveRuntimeCdnOrigin(input) {
207
+ const config = readConfig();
208
+ const value = input || process.env[RUNTIME_CDN_ORIGIN_ENV] || config.cdnOrigin || DEFAULT_RUNTIME_CDN_ORIGIN;
209
+ return value.replace(/\/+$/, "");
210
+ }
211
+ function resolveRuntimeChannel(input) {
212
+ const config = readConfig();
213
+ return input || process.env[RUNTIME_CHANNEL_ENV] || config.runtimeChannel || "stable";
214
+ }
215
+ function runtimeManifestUrl(cdnOrigin) {
216
+ const url = new URL(`${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/manifest.json`);
217
+ url.searchParams.set("t", String(Date.now()));
218
+ return url.toString();
219
+ }
220
+ function runtimeTarballUrl(version, cdnOrigin) {
221
+ return `${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/sootsim-runtime-${version}.tar.gz`;
222
+ }
223
+ async function fetchRuntimeManifest(cdnOrigin) {
224
+ const url = runtimeManifestUrl(cdnOrigin);
225
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
226
+ if (!res.ok) {
227
+ throw new Error(`manifest fetch failed: ${res.status} ${res.statusText} (${url})`);
228
+ }
229
+ return await res.json();
230
+ }
231
+ function resolveRuntimeVersion(manifest, opts = {}) {
232
+ const channel = resolveRuntimeChannel(opts.channel);
233
+ const version = opts.version || manifest.channels[channel]?.latest;
234
+ if (!version) {
235
+ throw new Error(
236
+ `no version specified and channel '${channel}' has no latest entry in the manifest`
237
+ );
238
+ }
239
+ const entry = manifest.versions[version];
240
+ if (!entry) {
241
+ throw new Error(
242
+ `version ${version} not found in manifest; available: ${Object.keys(manifest.versions).slice(-10).join(", ") || "(none)"}`
243
+ );
244
+ }
245
+ return { version, channel, entry };
246
+ }
247
+ async function installRuntime(opts = {}) {
248
+ ensureSootsimHome();
249
+ const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
250
+ const manifest = await fetchRuntimeManifest(cdnOrigin);
251
+ const { version, channel, entry } = resolveRuntimeVersion(manifest, opts);
252
+ const destDir = runtimeDir(version);
253
+ const setActive = opts.setActive !== false;
254
+ if (!opts.force && import_fs2.default.existsSync(import_path2.default.join(destDir, "index.html"))) {
255
+ if (setActive) writeActiveRuntime(version);
256
+ return {
257
+ version,
258
+ channel,
259
+ cdnOrigin,
260
+ runtimeDir: destDir,
261
+ installed: false,
262
+ activated: setActive,
263
+ manifest
264
+ };
265
+ }
266
+ const tarUrl = entry.tarball || runtimeTarballUrl(version, cdnOrigin);
267
+ const tarCachePath = import_path2.default.join(cacheDir(), `sootsim-runtime-${version}.tar.gz`);
268
+ process.stderr.write(`sootsim: downloading runtime ${version}\u2026
269
+ `);
270
+ await downloadToFile(tarUrl, tarCachePath);
271
+ process.stderr.write(`sootsim: extracting runtime ${version}\u2026
272
+ `);
273
+ const actualSha = await sha256File(tarCachePath);
274
+ if (actualSha !== entry.sha256) {
275
+ import_fs2.default.rmSync(tarCachePath, { force: true });
276
+ throw new Error(
277
+ `sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
278
+ );
279
+ }
280
+ const tmpDir = import_path2.default.join(import_path2.default.dirname(destDir), `.installing-${version}-${process.pid}`);
281
+ import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
282
+ import_fs2.default.mkdirSync(tmpDir, { recursive: true });
283
+ try {
284
+ await extractTarball(tarCachePath, tmpDir);
285
+ if (!import_fs2.default.existsSync(import_path2.default.join(tmpDir, "index.html"))) {
286
+ throw new Error(`extracted tarball for runtime ${version} is missing index.html`);
287
+ }
288
+ import_fs2.default.rmSync(destDir, { recursive: true, force: true });
289
+ import_fs2.default.renameSync(tmpDir, destDir);
290
+ } catch (err) {
291
+ import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
292
+ throw err;
293
+ }
294
+ if (setActive) writeActiveRuntime(version);
295
+ return {
296
+ version,
297
+ channel,
298
+ cdnOrigin,
299
+ runtimeDir: destDir,
300
+ installed: true,
301
+ activated: setActive,
302
+ manifest
303
+ };
304
+ }
305
+ async function updateRuntimeToLatest(opts = {}) {
306
+ ensureSootsimHome();
307
+ const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
308
+ const channel = resolveRuntimeChannel(opts.channel);
309
+ const manifest = await fetchRuntimeManifest(cdnOrigin);
310
+ const latestVersion = manifest.channels[channel]?.latest;
311
+ if (!latestVersion) {
312
+ return {
313
+ checked: true,
314
+ updated: false,
315
+ reason: `channel '${channel}' has no latest runtime`,
316
+ activeVersion: readActiveRuntime()
317
+ };
318
+ }
319
+ const entry = manifest.versions[latestVersion];
320
+ if (!entry) {
321
+ return {
322
+ checked: true,
323
+ updated: false,
324
+ reason: `manifest is missing version ${latestVersion}`,
325
+ activeVersion: readActiveRuntime(),
326
+ latestVersion
327
+ };
328
+ }
329
+ const activeVersion = readActiveRuntime();
330
+ const activeDir = activeVersion ? runtimeDir(activeVersion) : null;
331
+ const activeInstalled = activeDir ? import_fs2.default.existsSync(import_path2.default.join(activeDir, "index.html")) : false;
332
+ const shouldInstall = !activeVersion || !activeInstalled || compareSemver(latestVersion, activeVersion) > 0;
333
+ if (!shouldInstall) {
334
+ return {
335
+ checked: true,
336
+ updated: false,
337
+ reason: "active runtime is current",
338
+ activeVersion,
339
+ latestVersion
340
+ };
341
+ }
342
+ const install = await installRuntime({
343
+ version: latestVersion,
344
+ channel,
345
+ cdnOrigin,
346
+ setActive: false
347
+ });
348
+ return {
349
+ checked: true,
350
+ updated: true,
351
+ activeVersion: latestVersion,
352
+ latestVersion,
353
+ install
354
+ };
355
+ }
356
+ async function downloadToFile(url, destPath) {
357
+ const res = await fetch(url);
358
+ if (!res.ok || !res.body) {
359
+ throw new Error(`download failed: ${res.status} ${res.statusText} (${url})`);
360
+ }
361
+ import_fs2.default.mkdirSync(import_path2.default.dirname(destPath), { recursive: true });
362
+ const tmp = `${destPath}.partial`;
363
+ try {
364
+ await (0, import_promises.pipeline)(
365
+ import_stream.Readable.fromWeb(res.body),
366
+ import_fs2.default.createWriteStream(tmp)
367
+ );
368
+ import_fs2.default.renameSync(tmp, destPath);
369
+ } catch (err) {
370
+ try {
371
+ import_fs2.default.unlinkSync(tmp);
372
+ } catch {
373
+ }
374
+ throw err;
375
+ }
376
+ }
377
+ function sha256File(filePath) {
378
+ return new Promise((resolve2, reject) => {
379
+ const hash = import_crypto.default.createHash("sha256");
380
+ const stream = import_fs2.default.createReadStream(filePath);
381
+ stream.on("data", (chunk) => hash.update(chunk));
382
+ stream.on("error", reject);
383
+ stream.on("end", () => resolve2(hash.digest("hex")));
384
+ });
385
+ }
386
+ function extractTarball(tarPath, destDir) {
387
+ return new Promise((resolve2, reject) => {
388
+ const child = (0, import_child_process.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
389
+ stdio: ["ignore", "inherit", "inherit"]
390
+ });
391
+ child.on("error", reject);
392
+ child.on("exit", (code) => {
393
+ if (code === 0) resolve2();
394
+ else reject(new Error(`tar exited with code ${code}`));
395
+ });
396
+ });
397
+ }
398
+
184
399
  // src/host/agent-host.ts
185
400
  var import_node_fs4 = __toESM(require("node:fs"), 1);
186
401
  var import_node_path4 = __toESM(require("node:path"), 1);
187
402
 
188
403
  // scripts/dev-server-scanner.ts
189
- var import_child_process = require("child_process");
404
+ var import_child_process2 = require("child_process");
190
405
  var import_http = __toESM(require("http"), 1);
191
406
  var import_net = __toESM(require("net"), 1);
192
407
  var import_util = require("util");
@@ -221,11 +436,13 @@ function normalizeNativeDevBundleUrl(bundleUrl) {
221
436
  try {
222
437
  const isAbsolute = isAbsoluteHttpUrl(bundleUrl);
223
438
  const parsed = new URL(bundleUrl, "http://soot.local");
439
+ parsed.pathname = parsed.pathname.replace(/\.\.bundle$/, ".bundle");
224
440
  if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl;
225
441
  if (!parsed.searchParams.has("dev")) parsed.searchParams.set("dev", "true");
226
442
  if (!parsed.searchParams.has("minify")) {
227
443
  parsed.searchParams.set("minify", "false");
228
444
  }
445
+ parsed.searchParams.delete("transform.bytecode");
229
446
  if (isAbsolute) return parsed.toString();
230
447
  return `${parsed.pathname}${parsed.search}${parsed.hash}`;
231
448
  } catch {
@@ -610,7 +827,7 @@ var APPS = [
610
827
  ];
611
828
 
612
829
  // scripts/dev-server-scanner.ts
613
- var execP = (0, import_util.promisify)(import_child_process.exec);
830
+ var execP = (0, import_util.promisify)(import_child_process2.exec);
614
831
  var TIMEOUT_MS = 250;
615
832
  var TCP_GATE_MS = 120;
616
833
  function tcpPing(port, timeout = TCP_GATE_MS) {
@@ -630,10 +847,10 @@ function tcpPing(port, timeout = TCP_GATE_MS) {
630
847
  sock.connect(port, "localhost");
631
848
  });
632
849
  }
633
- function httpGet(port, path6, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
850
+ function httpGet(port, path7, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
634
851
  return new Promise((resolve2) => {
635
852
  const req = import_http.default.request(
636
- { hostname: "localhost", port, path: path6, method, timeout, headers },
853
+ { hostname: "localhost", port, path: path7, method, timeout, headers },
637
854
  (res) => {
638
855
  let body = "";
639
856
  res.on("data", (c) => body += c.toString());
@@ -766,9 +983,9 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
766
983
  if (client.name) result.projectName = client.name;
767
984
  if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
768
985
  if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
769
- const launchUrl = manifest?.launchAsset?.url;
770
- if (launchUrl && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
771
- result.bundleUrl = withRuntimeConfig(result.port, launchUrl);
986
+ const launchUrl2 = manifest?.launchAsset?.url;
987
+ if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
988
+ result.bundleUrl = withRuntimeConfig(result.port, launchUrl2);
772
989
  }
773
990
  const rawIconUrl = client.iconUrl || client.ios?.iconUrl || client.icon || client.ios?.icon;
774
991
  if (rawIconUrl) {
@@ -837,13 +1054,13 @@ async function probePort(port, buildIconProxyUrl) {
837
1054
  const manifest = JSON.parse(manifestRes.body);
838
1055
  const client = manifest?.extra?.expoClient || {};
839
1056
  if (client.name) {
840
- const launchUrl = manifest?.launchAsset?.url || `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
1057
+ const launchUrl2 = manifest?.launchAsset?.url || `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
841
1058
  knownNonPatched.add(port);
842
1059
  return applyManifest(
843
1060
  {
844
1061
  port,
845
1062
  framework: "one",
846
- bundleUrl: withRuntimeConfig(port, launchUrl),
1063
+ bundleUrl: withRuntimeConfig(port, launchUrl2),
847
1064
  hmrUrl: `ws://localhost:${port}/hot`,
848
1065
  lastSeen: Date.now()
849
1066
  },
@@ -1556,6 +1773,7 @@ async function startSession(opts) {
1556
1773
  ];
1557
1774
  if (opts.codexBin) wrapperArgs.push("--codex-bin", opts.codexBin);
1558
1775
  if (opts.claudeBin) wrapperArgs.push("--claude-bin", opts.claudeBin);
1776
+ if (opts.freshThread) wrapperArgs.push("--fresh-thread");
1559
1777
  if (claudeSessionUuid) {
1560
1778
  wrapperArgs.push("--claude-session-uuid", claudeSessionUuid);
1561
1779
  }
@@ -2157,6 +2375,155 @@ function mapFrameworkToProjectFramework(fw) {
2157
2375
  return "unknown";
2158
2376
  }
2159
2377
 
2378
+ // src/host/open-url.ts
2379
+ var import_child_process3 = require("child_process");
2380
+ var import_fs3 = require("fs");
2381
+ var import_os2 = require("os");
2382
+ var import_path3 = require("path");
2383
+ var MAC_CHROMIUM_CANDIDATES = [
2384
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
2385
+ "~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
2386
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
2387
+ "~/Applications/Chromium.app/Contents/MacOS/Chromium",
2388
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
2389
+ "~/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
2390
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
2391
+ "~/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
2392
+ "/Applications/Arc.app/Contents/MacOS/Arc",
2393
+ "~/Applications/Arc.app/Contents/MacOS/Arc"
2394
+ ];
2395
+ var LINUX_CHROMIUM_CANDIDATES = [
2396
+ "/usr/bin/google-chrome",
2397
+ "/usr/bin/chromium",
2398
+ "/usr/bin/chromium-browser",
2399
+ "/usr/bin/microsoft-edge",
2400
+ "/usr/bin/brave-browser",
2401
+ "/snap/bin/chromium"
2402
+ ];
2403
+ var WINDOWS_CHROMIUM_CANDIDATES = [
2404
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
2405
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
2406
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
2407
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
2408
+ "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
2409
+ "C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
2410
+ ];
2411
+ var UNIX_CHROMIUM_COMMANDS = [
2412
+ "google-chrome",
2413
+ "chromium",
2414
+ "chromium-browser",
2415
+ "microsoft-edge",
2416
+ "brave-browser"
2417
+ ];
2418
+ var WINDOWS_CHROMIUM_COMMANDS = ["chrome", "msedge", "brave"];
2419
+ function expandHome(candidate) {
2420
+ return candidate.startsWith("~/") ? (0, import_path3.join)((0, import_os2.homedir)(), candidate.slice(2)) : candidate;
2421
+ }
2422
+ function firstExisting(candidates) {
2423
+ for (const candidate of candidates) {
2424
+ const expanded = expandHome(candidate);
2425
+ if ((0, import_fs3.existsSync)(expanded)) return expanded;
2426
+ }
2427
+ return null;
2428
+ }
2429
+ function lookupExecutable(command, platform) {
2430
+ try {
2431
+ const tool = platform === "win32" ? "where" : "which";
2432
+ const out = (0, import_child_process3.execFileSync)(tool, [command], {
2433
+ encoding: "utf8",
2434
+ timeout: 1500,
2435
+ stdio: ["ignore", "pipe", "ignore"]
2436
+ }).trim();
2437
+ return out ? out.split(/\r?\n/)[0] : null;
2438
+ } catch {
2439
+ return null;
2440
+ }
2441
+ }
2442
+ function resolveChromiumBinary(platform = process.platform) {
2443
+ const envCandidate = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;
2444
+ if (envCandidate && (0, import_fs3.existsSync)(envCandidate)) return envCandidate;
2445
+ const direct = platform === "darwin" ? firstExisting(MAC_CHROMIUM_CANDIDATES) : platform === "win32" ? firstExisting(WINDOWS_CHROMIUM_CANDIDATES) : firstExisting(LINUX_CHROMIUM_CANDIDATES);
2446
+ if (direct) return direct;
2447
+ const commands = platform === "win32" ? WINDOWS_CHROMIUM_COMMANDS : UNIX_CHROMIUM_COMMANDS;
2448
+ for (const command of commands) {
2449
+ const found = lookupExecutable(command, platform);
2450
+ if (found) return found;
2451
+ }
2452
+ return null;
2453
+ }
2454
+ function buildOpenUrlCommand(url, options = {}) {
2455
+ if (!url) throw new Error("openUrl requires a url");
2456
+ const platform = options.platform ?? process.platform;
2457
+ if (options.newWindow) {
2458
+ return buildChromiumUrlCommand(url, { ...options, platform, newWindow: true });
2459
+ }
2460
+ if (platform === "darwin") {
2461
+ return {
2462
+ command: "open",
2463
+ args: options.background === false ? [url] : ["-g", url],
2464
+ via: "system"
2465
+ };
2466
+ }
2467
+ if (platform === "win32") {
2468
+ return {
2469
+ command: "cmd",
2470
+ args: ["/c", "start", "", url],
2471
+ via: "system"
2472
+ };
2473
+ }
2474
+ return {
2475
+ command: "xdg-open",
2476
+ args: [url],
2477
+ via: "system"
2478
+ };
2479
+ }
2480
+ function buildChromiumUrlCommand(url, options = {}) {
2481
+ if (!url) throw new Error("openUrl requires a url");
2482
+ const platform = options.platform ?? process.platform;
2483
+ const chromiumBinary = "chromiumBinary" in options ? options.chromiumBinary : resolveChromiumBinary(platform);
2484
+ if (!chromiumBinary) {
2485
+ throw new Error("browser launch requires Chrome, Chromium, Edge, Brave, or Arc");
2486
+ }
2487
+ const args = [];
2488
+ if (options.headless) {
2489
+ args.push("--headless=new");
2490
+ } else if (options.newWindow !== false) {
2491
+ args.push("--new-window");
2492
+ }
2493
+ args.push(url);
2494
+ return {
2495
+ command: chromiumBinary,
2496
+ args,
2497
+ via: "chromium",
2498
+ target: chromiumBinary
2499
+ };
2500
+ }
2501
+ async function spawnLaunch(command, args, detached) {
2502
+ return new Promise((resolve2, reject) => {
2503
+ const child = (0, import_child_process3.spawn)(command, args, {
2504
+ detached,
2505
+ stdio: "ignore"
2506
+ });
2507
+ child.once("error", reject);
2508
+ child.once("spawn", () => {
2509
+ if (detached) child.unref();
2510
+ resolve2(child.pid);
2511
+ });
2512
+ });
2513
+ }
2514
+ async function launchUrl(url, options = {}) {
2515
+ const command = buildOpenUrlCommand(url, options);
2516
+ const pid = await spawnLaunch(command.command, command.args, options.detached ?? true);
2517
+ return {
2518
+ ...command,
2519
+ pid,
2520
+ attachUrl: url
2521
+ };
2522
+ }
2523
+ async function openUrl(url, options = {}) {
2524
+ await launchUrl(url, options);
2525
+ }
2526
+
2160
2527
  // src/host/bridge-host.ts
2161
2528
  var WRITE_COMMAND_TYPES = /* @__PURE__ */ new Set(["tap", "keyboard", "close"]);
2162
2529
  function shouldAcquireLease(msg) {
@@ -2166,6 +2533,8 @@ function shouldAcquireLease(msg) {
2166
2533
  return WRITE_COMMAND_TYPES.has(msg.type);
2167
2534
  }
2168
2535
  var DAEMON_HEARTBEAT_INTERVAL_MS = 5e3;
2536
+ var DEFAULT_RUNTIME_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
2537
+ var RUNTIME_UPDATE_INTERVAL_ENV = "SOOTSIM_RUNTIME_UPDATE_INTERVAL_MS";
2169
2538
  var HTTP_MIME_TYPES = {
2170
2539
  ".html": "text/html; charset=utf-8",
2171
2540
  ".js": "application/javascript",
@@ -2211,6 +2580,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2211
2580
  static CLI_IDLE_TIMEOUT_MS = 6e4;
2212
2581
  static CLI_LEASE_TTL_MS = 6e5;
2213
2582
  static USER_ACTIVE_LEASE_TTL_MS = 8e3;
2583
+ // explicit user actions (clicking Boot, focusing the tab to take it over)
2584
+ // hold the tab longer than passive canvas interaction so reconnecting clis
2585
+ // can't immediately reclaim while the user gets oriented.
2586
+ static USER_BOOT_LEASE_TTL_MS = 6e4;
2214
2587
  static BROWSER_RECONNECT_TTL_MS = 3e4;
2215
2588
  preferredPort;
2216
2589
  portFallbackCount;
@@ -2218,14 +2591,15 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2218
2591
  effectivePort = 0;
2219
2592
  startedAt = 0;
2220
2593
  heartbeatTimer = null;
2594
+ runtimeUpdateTimer = null;
2595
+ runtimeUpdateInFlight = null;
2221
2596
  activeRuntimeVersion = null;
2222
2597
  activeRuntimeDirPath = null;
2223
2598
  constructor(opts = {}) {
2224
2599
  this.preferredPort = opts.port || DEFAULT_SOOTSIM_BRIDGE_PORT;
2225
2600
  this.port = this.preferredPort;
2226
2601
  this.shouldWriteLockfile = opts.writeLockfile === true;
2227
- const defaultFallback = this.shouldWriteLockfile ? 1 : 10;
2228
- this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? defaultFallback);
2602
+ this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? 10);
2229
2603
  this.openUrlHandler = opts.openUrl;
2230
2604
  this.agentHost = new AgentHost({ getExcludePorts: opts.agentScanExcludes });
2231
2605
  }
@@ -2315,6 +2689,8 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2315
2689
  const origin = req.headers.origin;
2316
2690
  const role = origin ? "browser" : "cli";
2317
2691
  let browser = null;
2692
+ ws.on("error", () => {
2693
+ });
2318
2694
  this.agentHost.registerSocket(ws);
2319
2695
  if (role === "browser") {
2320
2696
  browser = {
@@ -2458,15 +2834,18 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2458
2834
  }
2459
2835
  }
2460
2836
  const hadLease = !!browser.cliLease;
2461
- browser.cliLease = void 0;
2462
- if (booted.length > 0 || hadLease) {
2463
- process.stderr.write(
2464
- `sootsim booted ${booted.length} cli client(s)${hadLease ? " + cleared lease" : ""} from [${browser.id}]
2837
+ browser.cliLease = {
2838
+ kind: "user-active",
2839
+ cliSessionKey: "__user-active__",
2840
+ cliLabel: "active user",
2841
+ expiresAt: Date.now() + _SootSimBridgeHost.USER_BOOT_LEASE_TTL_MS
2842
+ };
2843
+ process.stderr.write(
2844
+ `sootsim booted ${booted.length} cli client(s)${hadLease ? " (overrode prior lease)" : ""}; held tab for user [${browser.id}]
2465
2845
  `
2466
- );
2467
- this.recordBrowserAction(browser.id, "browser booted cli clients");
2468
- this.broadcastBrowserClientStates();
2469
- }
2846
+ );
2847
+ this.recordBrowserAction(browser.id, "browser booted cli clients");
2848
+ this.broadcastBrowserClientStates();
2470
2849
  return;
2471
2850
  }
2472
2851
  const internalPending = this.pendingCommands.get(msg.id);
@@ -2539,7 +2918,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2539
2918
  if (typeof msg.url !== "string" || !msg.url) {
2540
2919
  throw new Error("bridge:open requires a url");
2541
2920
  }
2542
- await this.openUrl(msg.url);
2921
+ await this.openUrl(msg.url, { newWindow: msg.newWindow === true });
2543
2922
  if (ws.readyState === import_ws.WebSocket.OPEN) {
2544
2923
  ws.send(
2545
2924
  JSON.stringify({
@@ -2703,9 +3082,11 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2703
3082
  }
2704
3083
  }, DAEMON_HEARTBEAT_INTERVAL_MS);
2705
3084
  this.heartbeatTimer.unref();
3085
+ this.startRuntimeUpdater();
2706
3086
  }
2707
3087
  void this.agentHost.seedOnBoot();
2708
3088
  }
3089
+ bootstrapping = true;
2709
3090
  buildLockfileSnapshot() {
2710
3091
  return {
2711
3092
  schema: 1,
@@ -2716,7 +3097,8 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2716
3097
  activeRuntime: this.activeRuntimeVersion,
2717
3098
  activeRuntimeDir: this.activeRuntimeDirPath,
2718
3099
  startedAt: this.startedAt,
2719
- heartbeatAt: Date.now()
3100
+ heartbeatAt: Date.now(),
3101
+ bootstrapping: this.bootstrapping
2720
3102
  };
2721
3103
  }
2722
3104
  writeLockfileSnapshot() {
@@ -2726,6 +3108,61 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2726
3108
  this.activeRuntimeVersion = readActiveRuntime();
2727
3109
  this.activeRuntimeDirPath = activeRuntimeDir();
2728
3110
  }
3111
+ resolveRuntimeUpdateIntervalMs() {
3112
+ const raw = Number(process.env[RUNTIME_UPDATE_INTERVAL_ENV]);
3113
+ if (Number.isFinite(raw) && raw > 0) return Math.max(100, Math.round(raw));
3114
+ return DEFAULT_RUNTIME_UPDATE_INTERVAL_MS;
3115
+ }
3116
+ startRuntimeUpdater() {
3117
+ if (!this.shouldWriteLockfile || this.runtimeUpdateTimer) return;
3118
+ void this.runRuntimeUpdate("startup");
3119
+ const intervalMs = this.resolveRuntimeUpdateIntervalMs();
3120
+ this.runtimeUpdateTimer = setInterval(() => {
3121
+ void this.runRuntimeUpdate("periodic");
3122
+ }, intervalMs);
3123
+ this.runtimeUpdateTimer.unref();
3124
+ }
3125
+ runRuntimeUpdate(reason) {
3126
+ if (this.runtimeUpdateInFlight) return this.runtimeUpdateInFlight;
3127
+ this.runtimeUpdateInFlight = (async () => {
3128
+ try {
3129
+ if (reason === "startup") {
3130
+ process.stderr.write("sootsim: checking for runtime updates\u2026\n");
3131
+ }
3132
+ const result = await updateRuntimeToLatest();
3133
+ if (!result.updated || !result.latestVersion) {
3134
+ if (reason === "startup") {
3135
+ process.stderr.write(
3136
+ `sootsim: runtime ${this.activeRuntimeVersion ?? "(none)"} is current
3137
+ `
3138
+ );
3139
+ }
3140
+ return;
3141
+ }
3142
+ const active = this.setActiveRuntime(result.latestVersion);
3143
+ process.stderr.write(`sootsim runtime updated to ${active.version} (${reason})
3144
+ `);
3145
+ } catch (err) {
3146
+ process.stderr.write(
3147
+ `sootsim runtime update failed (${reason}): ${err instanceof Error ? err.message : String(err)}
3148
+ `
3149
+ );
3150
+ } finally {
3151
+ this.runtimeUpdateInFlight = null;
3152
+ if (reason === "startup" && this.bootstrapping) {
3153
+ this.bootstrapping = false;
3154
+ if (this.shouldWriteLockfile && this.httpServer) {
3155
+ try {
3156
+ this.writeLockfileSnapshot();
3157
+ } catch {
3158
+ }
3159
+ }
3160
+ process.stderr.write("sootsim: ready\n");
3161
+ }
3162
+ }
3163
+ })();
3164
+ return this.runtimeUpdateInFlight;
3165
+ }
2729
3166
  /** update the active runtime on disk + in memory. the caller guarantees
2730
3167
  * the version directory exists. pushes a runtime:changed message to all
2731
3168
  * connected browsers so electron (or any renderer) can reload. */
@@ -2883,47 +3320,50 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2883
3320
  return;
2884
3321
  }
2885
3322
  }
2886
- const resolved = import_path2.default.resolve(baseDir, "." + rel);
2887
- const baseWithSep = baseDir.endsWith(import_path2.default.sep) ? baseDir : baseDir + import_path2.default.sep;
3323
+ const resolved = import_path4.default.resolve(baseDir, "." + rel);
3324
+ const baseWithSep = baseDir.endsWith(import_path4.default.sep) ? baseDir : baseDir + import_path4.default.sep;
2888
3325
  if (!resolved.startsWith(baseWithSep) && resolved !== baseDir) {
2889
3326
  res.writeHead(403);
2890
3327
  res.end("forbidden");
2891
3328
  return;
2892
3329
  }
2893
- import_fs2.default.realpath(resolved, (realErr, realResolved) => {
3330
+ import_fs4.default.realpath(resolved, (realErr, realResolved) => {
2894
3331
  const servePath = realErr ? resolved : realResolved;
2895
- const servePathWithSep = servePath.endsWith(import_path2.default.sep) ? servePath : servePath + import_path2.default.sep;
3332
+ const servePathWithSep = servePath.endsWith(import_path4.default.sep) ? servePath : servePath + import_path4.default.sep;
2896
3333
  if (!realErr) {
2897
3334
  const realBaseWithSep = (() => {
2898
3335
  try {
2899
- const rb = import_fs2.default.realpathSync(baseDir);
2900
- return rb.endsWith(import_path2.default.sep) ? rb : rb + import_path2.default.sep;
3336
+ const rb = import_fs4.default.realpathSync(baseDir);
3337
+ return rb.endsWith(import_path4.default.sep) ? rb : rb + import_path4.default.sep;
2901
3338
  } catch {
2902
3339
  return baseWithSep;
2903
3340
  }
2904
3341
  })();
2905
- if (!servePathWithSep.startsWith(realBaseWithSep) && servePath + import_path2.default.sep !== realBaseWithSep) {
3342
+ if (!servePathWithSep.startsWith(realBaseWithSep) && servePath + import_path4.default.sep !== realBaseWithSep) {
2906
3343
  res.writeHead(403);
2907
3344
  res.end("forbidden");
2908
3345
  return;
2909
3346
  }
2910
3347
  }
2911
- import_fs2.default.stat(servePath, (err, stats) => {
3348
+ import_fs4.default.stat(servePath, (err, stats) => {
2912
3349
  if (err || !stats?.isFile()) {
2913
- const ext2 = import_path2.default.extname(rel).toLowerCase();
3350
+ const ext2 = import_path4.default.extname(rel).toLowerCase();
2914
3351
  if (ext2 && ext2 !== ".html") {
2915
3352
  res.writeHead(404);
2916
3353
  res.end("not found");
2917
3354
  return;
2918
3355
  }
2919
- const indexPath = import_path2.default.join(baseDir, "index.html");
2920
- import_fs2.default.readFile(indexPath, (err2, data) => {
3356
+ const indexPath = import_path4.default.join(baseDir, "index.html");
3357
+ import_fs4.default.readFile(indexPath, (err2, data) => {
2921
3358
  if (err2) {
2922
3359
  res.writeHead(404);
2923
3360
  res.end("not found");
2924
3361
  return;
2925
3362
  }
2926
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3363
+ res.writeHead(200, {
3364
+ "Content-Type": "text/html; charset=utf-8",
3365
+ "Cache-Control": "no-store"
3366
+ });
2927
3367
  if (method === "HEAD") {
2928
3368
  res.end();
2929
3369
  return;
@@ -2932,14 +3372,17 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
2932
3372
  });
2933
3373
  return;
2934
3374
  }
2935
- const ext = import_path2.default.extname(servePath).toLowerCase();
3375
+ const ext = import_path4.default.extname(servePath).toLowerCase();
2936
3376
  const contentType = HTTP_MIME_TYPES[ext] || "application/octet-stream";
2937
- res.writeHead(200, { "Content-Type": contentType });
3377
+ res.writeHead(200, {
3378
+ "Content-Type": contentType,
3379
+ "Cache-Control": "no-store"
3380
+ });
2938
3381
  if (method === "HEAD") {
2939
3382
  res.end();
2940
3383
  return;
2941
3384
  }
2942
- const stream = import_fs2.default.createReadStream(servePath);
3385
+ const stream = import_fs4.default.createReadStream(servePath);
2943
3386
  stream.pipe(res);
2944
3387
  stream.on("error", () => {
2945
3388
  try {
@@ -3022,7 +3465,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
3022
3465
  const target = `${filePath}${loc}`;
3023
3466
  const trySpawn = (cmd, args) => new Promise((resolve2) => {
3024
3467
  try {
3025
- const child = (0, import_child_process2.spawn)(cmd, args, { detached: true, stdio: "ignore" });
3468
+ const child = (0, import_child_process4.spawn)(cmd, args, { detached: true, stdio: "ignore" });
3026
3469
  let settled = false;
3027
3470
  child.on("error", () => {
3028
3471
  if (settled) return;
@@ -3049,26 +3492,12 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
3049
3492
  if (await trySpawn("code", ["-g", target])) return;
3050
3493
  await this.openUrl(filePath);
3051
3494
  }
3052
- async openUrl(url) {
3495
+ async openUrl(url, options = {}) {
3053
3496
  if (this.openUrlHandler) {
3054
- await this.openUrlHandler(url);
3497
+ await this.openUrlHandler(url, options);
3055
3498
  return;
3056
3499
  }
3057
- if (process.platform === "darwin") {
3058
- const child2 = (0, import_child_process2.spawn)("open", ["-g", url], { detached: true, stdio: "ignore" });
3059
- child2.unref();
3060
- return;
3061
- }
3062
- if (process.platform === "win32") {
3063
- const child2 = (0, import_child_process2.spawn)("cmd", ["/c", "start", "", url], {
3064
- detached: true,
3065
- stdio: "ignore"
3066
- });
3067
- child2.unref();
3068
- return;
3069
- }
3070
- const child = (0, import_child_process2.spawn)("xdg-open", [url], { detached: true, stdio: "ignore" });
3071
- child.unref();
3500
+ await openUrl(url, options);
3072
3501
  }
3073
3502
  async close() {
3074
3503
  if (this.cliIdleTimer) {
@@ -3079,6 +3508,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
3079
3508
  clearInterval(this.heartbeatTimer);
3080
3509
  this.heartbeatTimer = null;
3081
3510
  }
3511
+ if (this.runtimeUpdateTimer) {
3512
+ clearInterval(this.runtimeUpdateTimer);
3513
+ this.runtimeUpdateTimer = null;
3514
+ }
3082
3515
  if (this.shouldWriteLockfile) {
3083
3516
  try {
3084
3517
  removeDaemonLockfile();
@@ -3215,11 +3648,14 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
3215
3648
  if (existing && existing.kind === "cli") {
3216
3649
  return;
3217
3650
  }
3651
+ const now = Date.now();
3652
+ const refreshed = now + _SootSimBridgeHost.USER_ACTIVE_LEASE_TTL_MS;
3653
+ const expiresAt = existing && existing.kind === "user-active" ? Math.max(existing.expiresAt, refreshed) : refreshed;
3218
3654
  browser.cliLease = {
3219
3655
  kind: "user-active",
3220
3656
  cliSessionKey: "__user-active__",
3221
3657
  cliLabel: "active user",
3222
- expiresAt: Date.now() + _SootSimBridgeHost.USER_ACTIVE_LEASE_TTL_MS
3658
+ expiresAt
3223
3659
  };
3224
3660
  this.broadcastBrowserClientStates();
3225
3661
  }
@@ -3434,6 +3870,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
3434
3870
  clearInterval(this.cliIdleTimer);
3435
3871
  this.cliIdleTimer = null;
3436
3872
  }
3873
+ if (this.runtimeUpdateTimer) {
3874
+ clearInterval(this.runtimeUpdateTimer);
3875
+ this.runtimeUpdateTimer = null;
3876
+ }
3437
3877
  const wss = this.wss;
3438
3878
  const httpServer = this.httpServer;
3439
3879
  this.wss = null;