sootsim 0.0.4 → 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-PJAOF4JS.js → agent-YZB6D3DR.js} +4 -4
  5. package/dist-cli/chunks/agent-wrapper-VHCVS22I.js +15 -0
  6. package/dist-cli/chunks/{assert-P47NW4AF.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-W7CYWXRZ.js → chunk-6GGMKFWJ.js} +1 -1
  13. package/dist-cli/chunks/{chunk-EHMSE3Q3.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-UQ3N6FZF.js → chunk-CYCXOAVZ.js} +2 -2
  17. package/dist-cli/chunks/{chunk-QIP7LYQI.js → chunk-DFN3GGH7.js} +2 -2
  18. package/dist-cli/chunks/chunk-EBEHZJRG.js +117 -0
  19. package/dist-cli/chunks/{chunk-UKYK63H6.js → chunk-EJLNUMMP.js} +1 -1
  20. package/dist-cli/chunks/{chunk-DQKQYPIG.js → chunk-EWSQSALM.js} +2 -2
  21. package/dist-cli/chunks/{chunk-BQRM4E66.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-QQOBLF7O.js → chunk-H2QO4TDV.js} +2 -2
  26. package/dist-cli/chunks/{chunk-I6XGFZPA.js → chunk-HWCKZXNJ.js} +2 -2
  27. package/dist-cli/chunks/{chunk-UNFERMZ3.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-432TMHBG.js → chunk-KAXZHEKM.js} +1 -1
  31. package/dist-cli/chunks/{chunk-WWDJCKMI.js → chunk-LHDWH7VS.js} +1 -1
  32. package/dist-cli/chunks/{chunk-VGXARPIH.js → chunk-N32NCVL2.js} +2 -2
  33. package/dist-cli/chunks/{chunk-XJF46GU2.js → chunk-NIZBR7EK.js} +2 -2
  34. package/dist-cli/chunks/{chunk-MQDPKSCK.js → chunk-NYY36OKU.js} +12 -12
  35. package/dist-cli/chunks/{chunk-5TTQKPGH.js → chunk-OXN2PEB7.js} +1 -1
  36. package/dist-cli/chunks/{chunk-SY74J6F4.js → chunk-PJL25JQV.js} +1 -1
  37. package/dist-cli/chunks/{chunk-4XBPZQLW.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-6SZMLFCR.js → chunk-VFDRZNPN.js} +1 -1
  42. package/dist-cli/chunks/{chunk-AFQBSK2J.js → chunk-YIO6S3R5.js} +1 -1
  43. package/dist-cli/chunks/{chunk-AUR2LTNX.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-ILLJ7VDL.js → compat-Y2O2U7FL.js} +2 -2
  47. package/dist-cli/chunks/{config-CDIAJIIT.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-6SMCTPMC.js → debug-BIDMW2PE.js} +3 -3
  51. package/dist-cli/chunks/demo-app-registry-5JFOUU3D.js +2 -0
  52. package/dist-cli/chunks/{detox-R4G5INNB.js → detox-B3FDOIS3.js} +2 -2
  53. package/dist-cli/chunks/{device-YSLCWS4E.js → device-ZZSI363W.js} +2 -2
  54. package/dist-cli/chunks/drivers-S4NGK4DB.js +2 -0
  55. package/dist-cli/chunks/{electron-JZOFO37G.js → electron-5YFHXEOI.js} +3 -3
  56. package/dist-cli/chunks/flow-JJBO6TFY.js +2 -0
  57. package/dist-cli/chunks/{hints-O4QR6UGI.js → hints-G5HBBV2O.js} +2 -2
  58. package/dist-cli/chunks/home-paths-VWC3FWA3.js +2 -0
  59. package/dist-cli/chunks/{inspect-DRFAUJUH.js → inspect-POOPWUQI.js} +56 -52
  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-CAJHPRNP.js → install-dev-desktop-SKH3KEHY.js} +2 -2
  63. package/dist-cli/chunks/{keys-OWQ7SOTM.js → keys-7PNASIQR.js} +2 -2
  64. package/dist-cli/chunks/{launch-WUEDHSO5.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-PMHK6EHI.js → maestro-CW6XVUKV.js} +3 -3
  68. package/dist-cli/chunks/{preview-4RVHA2PP.js → preview-WGKJO5FS.js} +2 -2
  69. package/dist-cli/chunks/{profile-3IVNHUS6.js → profile-SUOBRPIC.js} +2 -2
  70. package/dist-cli/chunks/{record-KEWLM5JR.js → record-QPWLYH5R.js} +2 -2
  71. package/dist-cli/chunks/runtime-KEMO2MSB.js +25 -0
  72. package/dist-cli/chunks/{screenshot-BXRAQERZ.js → screenshot-JTY46V7G.js} +2 -2
  73. package/dist-cli/chunks/{screenshot-mode-5IXEDIUS.js → screenshot-mode-7OYBBX6D.js} +2 -2
  74. package/dist-cli/chunks/{screenshots-T4MQF3TB.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-DJA6QEVR.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-IWUHNFXV.js → test-XUI3KNNQ.js} +3 -3
  81. package/dist-cli/chunks/upload-6FUT7AX5.js +2 -0
  82. package/dist-cli/chunks/{whoami-MCXFWKIH.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-STO7PLQD.js +0 -15
  102. package/dist-cli/chunks/auto-bootstrap-SC2LMI2H.js +0 -2
  103. package/dist-cli/chunks/chunk-47S5DXXX.js +0 -11
  104. package/dist-cli/chunks/chunk-4VXB2DBA.js +0 -119
  105. package/dist-cli/chunks/chunk-C3QLIYCS.js +0 -16
  106. package/dist-cli/chunks/chunk-F4ARVCRR.js +0 -1
  107. package/dist-cli/chunks/chunk-HAKR72LJ.js +0 -2
  108. package/dist-cli/chunks/chunk-HGFIS26A.js +0 -2
  109. package/dist-cli/chunks/chunk-MZPAJ5PQ.js +0 -1
  110. package/dist-cli/chunks/chunk-OAHMYSMD.js +0 -2
  111. package/dist-cli/chunks/chunk-W3TYN64D.js +0 -62
  112. package/dist-cli/chunks/chunk-WRF43M33.js +0 -4
  113. package/dist-cli/chunks/chunk-WVBPATRA.js +0 -2
  114. package/dist-cli/chunks/chunk-ZF5FCFLD.js +0 -2
  115. package/dist-cli/chunks/chunk-ZKNI5MRD.js +0 -1
  116. package/dist-cli/chunks/control-7QGKUCAX.js +0 -2
  117. package/dist-cli/chunks/daemon-4BLYGM5N.js +0 -49
  118. package/dist-cli/chunks/demo-app-registry-HLI5UGGI.js +0 -2
  119. package/dist-cli/chunks/drivers-YIXRFFBQ.js +0 -2
  120. package/dist-cli/chunks/flow-L7X5FGIN.js +0 -2
  121. package/dist-cli/chunks/home-paths-4YJJYGR6.js +0 -2
  122. package/dist-cli/chunks/install-BATRTWRI.js +0 -65
  123. package/dist-cli/chunks/install-desktop-6X474IQ3.js +0 -23
  124. package/dist-cli/chunks/login-54YJ2KH6.js +0 -26
  125. package/dist-cli/chunks/logout-XECXLEXW.js +0 -2
  126. package/dist-cli/chunks/runtime-PJKHEB36.js +0 -25
  127. package/dist-cli/chunks/server-CIP3LH45.js +0 -29
  128. package/dist-cli/chunks/store-SPC247DB.js +0 -2
  129. package/dist-cli/chunks/upload-UPD2RSYF.js +0 -2
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.0.4 | (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;
@@ -62,6 +62,9 @@ function cacheDir() {
62
62
  function daemonLockfilePath() {
63
63
  return import_path.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
64
64
  }
65
+ function configFilePath() {
66
+ return import_path.default.join(sootsimHomeDir(), CONFIG_FILE);
67
+ }
65
68
  function ensureSootsimHome() {
66
69
  import_fs.default.mkdirSync(sootsimHomeDir(), { recursive: true });
67
70
  import_fs.default.mkdirSync(runtimesDir(), { recursive: true });
@@ -169,7 +172,7 @@ function removeDaemonLockfile() {
169
172
  } catch {
170
173
  }
171
174
  }
172
- var import_fs, import_os, import_path, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES;
175
+ var import_fs, import_os, import_path, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, CONFIG_FILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES;
173
176
  var init_home_paths = __esm({
174
177
  "src/home-paths.ts"() {
175
178
  "use strict";
@@ -179,11 +182,230 @@ var init_home_paths = __esm({
179
182
  SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
180
183
  ACTIVE_RUNTIME_FILE = "active";
181
184
  DAEMON_LOCKFILE = "daemon.json";
185
+ CONFIG_FILE = "config.json";
182
186
  DAEMON_HEARTBEAT_STALE_MS = 3e4;
183
187
  DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
184
188
  }
185
189
  });
186
190
 
191
+ // src/runtime-delivery.ts
192
+ function readConfig() {
193
+ try {
194
+ const parsed = JSON.parse(import_fs2.default.readFileSync(configFilePath(), "utf8"));
195
+ return parsed && typeof parsed === "object" ? parsed : {};
196
+ } catch {
197
+ return {};
198
+ }
199
+ }
200
+ function resolveRuntimeCdnOrigin(input) {
201
+ const config = readConfig();
202
+ const value = input || process.env[RUNTIME_CDN_ORIGIN_ENV] || config.cdnOrigin || DEFAULT_RUNTIME_CDN_ORIGIN;
203
+ return value.replace(/\/+$/, "");
204
+ }
205
+ function resolveRuntimeChannel(input) {
206
+ const config = readConfig();
207
+ return input || process.env[RUNTIME_CHANNEL_ENV] || config.runtimeChannel || "stable";
208
+ }
209
+ function runtimeManifestUrl(cdnOrigin) {
210
+ const url = new URL(`${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/manifest.json`);
211
+ url.searchParams.set("t", String(Date.now()));
212
+ return url.toString();
213
+ }
214
+ function runtimeTarballUrl(version, cdnOrigin) {
215
+ return `${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/sootsim-runtime-${version}.tar.gz`;
216
+ }
217
+ async function fetchRuntimeManifest(cdnOrigin) {
218
+ const url = runtimeManifestUrl(cdnOrigin);
219
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
220
+ if (!res.ok) {
221
+ throw new Error(`manifest fetch failed: ${res.status} ${res.statusText} (${url})`);
222
+ }
223
+ return await res.json();
224
+ }
225
+ function resolveRuntimeVersion(manifest, opts = {}) {
226
+ const channel = resolveRuntimeChannel(opts.channel);
227
+ const version = opts.version || manifest.channels[channel]?.latest;
228
+ if (!version) {
229
+ throw new Error(
230
+ `no version specified and channel '${channel}' has no latest entry in the manifest`
231
+ );
232
+ }
233
+ const entry = manifest.versions[version];
234
+ if (!entry) {
235
+ throw new Error(
236
+ `version ${version} not found in manifest; available: ${Object.keys(manifest.versions).slice(-10).join(", ") || "(none)"}`
237
+ );
238
+ }
239
+ return { version, channel, entry };
240
+ }
241
+ async function installRuntime(opts = {}) {
242
+ ensureSootsimHome();
243
+ const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
244
+ const manifest = await fetchRuntimeManifest(cdnOrigin);
245
+ const { version, channel, entry } = resolveRuntimeVersion(manifest, opts);
246
+ const destDir = runtimeDir(version);
247
+ const setActive = opts.setActive !== false;
248
+ if (!opts.force && import_fs2.default.existsSync(import_path2.default.join(destDir, "index.html"))) {
249
+ if (setActive) writeActiveRuntime(version);
250
+ return {
251
+ version,
252
+ channel,
253
+ cdnOrigin,
254
+ runtimeDir: destDir,
255
+ installed: false,
256
+ activated: setActive,
257
+ manifest
258
+ };
259
+ }
260
+ const tarUrl = entry.tarball || runtimeTarballUrl(version, cdnOrigin);
261
+ const tarCachePath = import_path2.default.join(cacheDir(), `sootsim-runtime-${version}.tar.gz`);
262
+ process.stderr.write(`sootsim: downloading runtime ${version}\u2026
263
+ `);
264
+ await downloadToFile(tarUrl, tarCachePath);
265
+ process.stderr.write(`sootsim: extracting runtime ${version}\u2026
266
+ `);
267
+ const actualSha = await sha256File(tarCachePath);
268
+ if (actualSha !== entry.sha256) {
269
+ import_fs2.default.rmSync(tarCachePath, { force: true });
270
+ throw new Error(
271
+ `sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
272
+ );
273
+ }
274
+ const tmpDir = import_path2.default.join(import_path2.default.dirname(destDir), `.installing-${version}-${process.pid}`);
275
+ import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
276
+ import_fs2.default.mkdirSync(tmpDir, { recursive: true });
277
+ try {
278
+ await extractTarball(tarCachePath, tmpDir);
279
+ if (!import_fs2.default.existsSync(import_path2.default.join(tmpDir, "index.html"))) {
280
+ throw new Error(`extracted tarball for runtime ${version} is missing index.html`);
281
+ }
282
+ import_fs2.default.rmSync(destDir, { recursive: true, force: true });
283
+ import_fs2.default.renameSync(tmpDir, destDir);
284
+ } catch (err) {
285
+ import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
286
+ throw err;
287
+ }
288
+ if (setActive) writeActiveRuntime(version);
289
+ return {
290
+ version,
291
+ channel,
292
+ cdnOrigin,
293
+ runtimeDir: destDir,
294
+ installed: true,
295
+ activated: setActive,
296
+ manifest
297
+ };
298
+ }
299
+ async function updateRuntimeToLatest(opts = {}) {
300
+ ensureSootsimHome();
301
+ const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
302
+ const channel = resolveRuntimeChannel(opts.channel);
303
+ const manifest = await fetchRuntimeManifest(cdnOrigin);
304
+ const latestVersion = manifest.channels[channel]?.latest;
305
+ if (!latestVersion) {
306
+ return {
307
+ checked: true,
308
+ updated: false,
309
+ reason: `channel '${channel}' has no latest runtime`,
310
+ activeVersion: readActiveRuntime()
311
+ };
312
+ }
313
+ const entry = manifest.versions[latestVersion];
314
+ if (!entry) {
315
+ return {
316
+ checked: true,
317
+ updated: false,
318
+ reason: `manifest is missing version ${latestVersion}`,
319
+ activeVersion: readActiveRuntime(),
320
+ latestVersion
321
+ };
322
+ }
323
+ const activeVersion = readActiveRuntime();
324
+ const activeDir = activeVersion ? runtimeDir(activeVersion) : null;
325
+ const activeInstalled = activeDir ? import_fs2.default.existsSync(import_path2.default.join(activeDir, "index.html")) : false;
326
+ const shouldInstall = !activeVersion || !activeInstalled || compareSemver(latestVersion, activeVersion) > 0;
327
+ if (!shouldInstall) {
328
+ return {
329
+ checked: true,
330
+ updated: false,
331
+ reason: "active runtime is current",
332
+ activeVersion,
333
+ latestVersion
334
+ };
335
+ }
336
+ const install = await installRuntime({
337
+ version: latestVersion,
338
+ channel,
339
+ cdnOrigin,
340
+ setActive: false
341
+ });
342
+ return {
343
+ checked: true,
344
+ updated: true,
345
+ activeVersion: latestVersion,
346
+ latestVersion,
347
+ install
348
+ };
349
+ }
350
+ async function downloadToFile(url, destPath) {
351
+ const res = await fetch(url);
352
+ if (!res.ok || !res.body) {
353
+ throw new Error(`download failed: ${res.status} ${res.statusText} (${url})`);
354
+ }
355
+ import_fs2.default.mkdirSync(import_path2.default.dirname(destPath), { recursive: true });
356
+ const tmp = `${destPath}.partial`;
357
+ try {
358
+ await (0, import_promises.pipeline)(
359
+ import_stream.Readable.fromWeb(res.body),
360
+ import_fs2.default.createWriteStream(tmp)
361
+ );
362
+ import_fs2.default.renameSync(tmp, destPath);
363
+ } catch (err) {
364
+ try {
365
+ import_fs2.default.unlinkSync(tmp);
366
+ } catch {
367
+ }
368
+ throw err;
369
+ }
370
+ }
371
+ function sha256File(filePath) {
372
+ return new Promise((resolve2, reject) => {
373
+ const hash = import_crypto.default.createHash("sha256");
374
+ const stream = import_fs2.default.createReadStream(filePath);
375
+ stream.on("data", (chunk) => hash.update(chunk));
376
+ stream.on("error", reject);
377
+ stream.on("end", () => resolve2(hash.digest("hex")));
378
+ });
379
+ }
380
+ function extractTarball(tarPath, destDir) {
381
+ return new Promise((resolve2, reject) => {
382
+ const child = (0, import_child_process.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
383
+ stdio: ["ignore", "inherit", "inherit"]
384
+ });
385
+ child.on("error", reject);
386
+ child.on("exit", (code) => {
387
+ if (code === 0) resolve2();
388
+ else reject(new Error(`tar exited with code ${code}`));
389
+ });
390
+ });
391
+ }
392
+ var import_child_process, import_crypto, import_fs2, import_path2, import_stream, import_promises, DEFAULT_RUNTIME_CDN_ORIGIN, RUNTIME_CDN_ORIGIN_ENV, RUNTIME_CHANNEL_ENV;
393
+ var init_runtime_delivery = __esm({
394
+ "src/runtime-delivery.ts"() {
395
+ "use strict";
396
+ import_child_process = require("child_process");
397
+ import_crypto = __toESM(require("crypto"), 1);
398
+ import_fs2 = __toESM(require("fs"), 1);
399
+ import_path2 = __toESM(require("path"), 1);
400
+ import_stream = require("stream");
401
+ import_promises = require("stream/promises");
402
+ init_home_paths();
403
+ DEFAULT_RUNTIME_CDN_ORIGIN = "https://sootbean.com";
404
+ RUNTIME_CDN_ORIGIN_ENV = "SOOTSIM_CDN_ORIGIN";
405
+ RUNTIME_CHANNEL_ENV = "SOOTSIM_RUNTIME_CHANNEL";
406
+ }
407
+ });
408
+
187
409
  // src/config.ts
188
410
  function hasOwnKeys(value) {
189
411
  return !!value && Object.keys(value).length > 0;
@@ -220,11 +442,13 @@ function normalizeNativeDevBundleUrl(bundleUrl) {
220
442
  try {
221
443
  const isAbsolute = isAbsoluteHttpUrl(bundleUrl);
222
444
  const parsed = new URL(bundleUrl, "http://soot.local");
445
+ parsed.pathname = parsed.pathname.replace(/\.\.bundle$/, ".bundle");
223
446
  if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl;
224
447
  if (!parsed.searchParams.has("dev")) parsed.searchParams.set("dev", "true");
225
448
  if (!parsed.searchParams.has("minify")) {
226
449
  parsed.searchParams.set("minify", "false");
227
450
  }
451
+ parsed.searchParams.delete("transform.bytecode");
228
452
  if (isAbsolute) return parsed.toString();
229
453
  return `${parsed.pathname}${parsed.search}${parsed.hash}`;
230
454
  } catch {
@@ -637,10 +861,10 @@ function tcpPing(port, timeout = TCP_GATE_MS) {
637
861
  sock.connect(port, "localhost");
638
862
  });
639
863
  }
640
- function httpGet(port, path7, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
864
+ function httpGet(port, path8, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
641
865
  return new Promise((resolve2) => {
642
866
  const req = import_http.default.request(
643
- { hostname: "localhost", port, path: path7, method, timeout, headers },
867
+ { hostname: "localhost", port, path: path8, method, timeout, headers },
644
868
  (res) => {
645
869
  let body = "";
646
870
  res.on("data", (c) => body += c.toString());
@@ -761,9 +985,9 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
761
985
  if (client.name) result.projectName = client.name;
762
986
  if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
763
987
  if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
764
- const launchUrl = manifest?.launchAsset?.url;
765
- if (launchUrl && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
766
- result.bundleUrl = withRuntimeConfig(result.port, launchUrl);
988
+ const launchUrl2 = manifest?.launchAsset?.url;
989
+ if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
990
+ result.bundleUrl = withRuntimeConfig(result.port, launchUrl2);
767
991
  }
768
992
  const rawIconUrl = client.iconUrl || client.ios?.iconUrl || client.icon || client.ios?.icon;
769
993
  if (rawIconUrl) {
@@ -830,13 +1054,13 @@ async function probePort(port, buildIconProxyUrl) {
830
1054
  const manifest = JSON.parse(manifestRes.body);
831
1055
  const client = manifest?.extra?.expoClient || {};
832
1056
  if (client.name) {
833
- 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`;
834
1058
  knownNonPatched.add(port);
835
1059
  return applyManifest(
836
1060
  {
837
1061
  port,
838
1062
  framework: "one",
839
- bundleUrl: withRuntimeConfig(port, launchUrl),
1063
+ bundleUrl: withRuntimeConfig(port, launchUrl2),
840
1064
  hmrUrl: `ws://localhost:${port}/hot`,
841
1065
  lastSeen: Date.now()
842
1066
  },
@@ -945,18 +1169,18 @@ async function scanDevServers(opts = {}) {
945
1169
  }
946
1170
  return results.filter((r) => !isSootSelfServer(r));
947
1171
  }
948
- var import_child_process, import_http, import_net, import_util, execP, TIMEOUT_MS, TCP_GATE_MS, FALLBACK_PORTS, cwdByPid, knownNonPatched, knownNonExpo, portCache, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
1172
+ var import_child_process2, import_http, import_net, import_util, execP, TIMEOUT_MS, TCP_GATE_MS, FALLBACK_PORTS, cwdByPid, knownNonPatched, knownNonExpo, portCache, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
949
1173
  var init_dev_server_scanner = __esm({
950
1174
  "scripts/dev-server-scanner.ts"() {
951
1175
  "use strict";
952
- import_child_process = require("child_process");
1176
+ import_child_process2 = require("child_process");
953
1177
  import_http = __toESM(require("http"), 1);
954
1178
  import_net = __toESM(require("net"), 1);
955
1179
  import_util = require("util");
956
1180
  init_config();
957
1181
  init_native_dev_bundle_url();
958
1182
  init_demo_app_registry();
959
- execP = (0, import_util.promisify)(import_child_process.exec);
1183
+ execP = (0, import_util.promisify)(import_child_process2.exec);
960
1184
  TIMEOUT_MS = 250;
961
1185
  TCP_GATE_MS = 120;
962
1186
  FALLBACK_PORTS = [
@@ -1582,6 +1806,7 @@ async function startSession(opts) {
1582
1806
  ];
1583
1807
  if (opts.codexBin) wrapperArgs.push("--codex-bin", opts.codexBin);
1584
1808
  if (opts.claudeBin) wrapperArgs.push("--claude-bin", opts.claudeBin);
1809
+ if (opts.freshThread) wrapperArgs.push("--fresh-thread");
1585
1810
  if (claudeSessionUuid) {
1586
1811
  wrapperArgs.push("--claude-session-uuid", claudeSessionUuid);
1587
1812
  }
@@ -2215,6 +2440,161 @@ var init_agent_host = __esm({
2215
2440
  }
2216
2441
  });
2217
2442
 
2443
+ // src/host/open-url.ts
2444
+ function expandHome(candidate) {
2445
+ return candidate.startsWith("~/") ? (0, import_path3.join)((0, import_os2.homedir)(), candidate.slice(2)) : candidate;
2446
+ }
2447
+ function firstExisting(candidates) {
2448
+ for (const candidate of candidates) {
2449
+ const expanded = expandHome(candidate);
2450
+ if ((0, import_fs3.existsSync)(expanded)) return expanded;
2451
+ }
2452
+ return null;
2453
+ }
2454
+ function lookupExecutable(command, platform) {
2455
+ try {
2456
+ const tool = platform === "win32" ? "where" : "which";
2457
+ const out = (0, import_child_process3.execFileSync)(tool, [command], {
2458
+ encoding: "utf8",
2459
+ timeout: 1500,
2460
+ stdio: ["ignore", "pipe", "ignore"]
2461
+ }).trim();
2462
+ return out ? out.split(/\r?\n/)[0] : null;
2463
+ } catch {
2464
+ return null;
2465
+ }
2466
+ }
2467
+ function resolveChromiumBinary(platform = process.platform) {
2468
+ const envCandidate = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;
2469
+ if (envCandidate && (0, import_fs3.existsSync)(envCandidate)) return envCandidate;
2470
+ const direct = platform === "darwin" ? firstExisting(MAC_CHROMIUM_CANDIDATES) : platform === "win32" ? firstExisting(WINDOWS_CHROMIUM_CANDIDATES) : firstExisting(LINUX_CHROMIUM_CANDIDATES);
2471
+ if (direct) return direct;
2472
+ const commands = platform === "win32" ? WINDOWS_CHROMIUM_COMMANDS : UNIX_CHROMIUM_COMMANDS;
2473
+ for (const command of commands) {
2474
+ const found = lookupExecutable(command, platform);
2475
+ if (found) return found;
2476
+ }
2477
+ return null;
2478
+ }
2479
+ function buildOpenUrlCommand(url, options = {}) {
2480
+ if (!url) throw new Error("openUrl requires a url");
2481
+ const platform = options.platform ?? process.platform;
2482
+ if (options.newWindow) {
2483
+ return buildChromiumUrlCommand(url, { ...options, platform, newWindow: true });
2484
+ }
2485
+ if (platform === "darwin") {
2486
+ return {
2487
+ command: "open",
2488
+ args: options.background === false ? [url] : ["-g", url],
2489
+ via: "system"
2490
+ };
2491
+ }
2492
+ if (platform === "win32") {
2493
+ return {
2494
+ command: "cmd",
2495
+ args: ["/c", "start", "", url],
2496
+ via: "system"
2497
+ };
2498
+ }
2499
+ return {
2500
+ command: "xdg-open",
2501
+ args: [url],
2502
+ via: "system"
2503
+ };
2504
+ }
2505
+ function buildChromiumUrlCommand(url, options = {}) {
2506
+ if (!url) throw new Error("openUrl requires a url");
2507
+ const platform = options.platform ?? process.platform;
2508
+ const chromiumBinary = "chromiumBinary" in options ? options.chromiumBinary : resolveChromiumBinary(platform);
2509
+ if (!chromiumBinary) {
2510
+ throw new Error("browser launch requires Chrome, Chromium, Edge, Brave, or Arc");
2511
+ }
2512
+ const args = [];
2513
+ if (options.headless) {
2514
+ args.push("--headless=new");
2515
+ } else if (options.newWindow !== false) {
2516
+ args.push("--new-window");
2517
+ }
2518
+ args.push(url);
2519
+ return {
2520
+ command: chromiumBinary,
2521
+ args,
2522
+ via: "chromium",
2523
+ target: chromiumBinary
2524
+ };
2525
+ }
2526
+ async function spawnLaunch(command, args, detached) {
2527
+ return new Promise((resolve2, reject) => {
2528
+ const child = (0, import_child_process3.spawn)(command, args, {
2529
+ detached,
2530
+ stdio: "ignore"
2531
+ });
2532
+ child.once("error", reject);
2533
+ child.once("spawn", () => {
2534
+ if (detached) child.unref();
2535
+ resolve2(child.pid);
2536
+ });
2537
+ });
2538
+ }
2539
+ async function launchUrl(url, options = {}) {
2540
+ const command = buildOpenUrlCommand(url, options);
2541
+ const pid = await spawnLaunch(command.command, command.args, options.detached ?? true);
2542
+ return {
2543
+ ...command,
2544
+ pid,
2545
+ attachUrl: url
2546
+ };
2547
+ }
2548
+ async function openUrl(url, options = {}) {
2549
+ await launchUrl(url, options);
2550
+ }
2551
+ var import_child_process3, import_fs3, import_os2, import_path3, MAC_CHROMIUM_CANDIDATES, LINUX_CHROMIUM_CANDIDATES, WINDOWS_CHROMIUM_CANDIDATES, UNIX_CHROMIUM_COMMANDS, WINDOWS_CHROMIUM_COMMANDS;
2552
+ var init_open_url = __esm({
2553
+ "src/host/open-url.ts"() {
2554
+ "use strict";
2555
+ import_child_process3 = require("child_process");
2556
+ import_fs3 = require("fs");
2557
+ import_os2 = require("os");
2558
+ import_path3 = require("path");
2559
+ MAC_CHROMIUM_CANDIDATES = [
2560
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
2561
+ "~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
2562
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
2563
+ "~/Applications/Chromium.app/Contents/MacOS/Chromium",
2564
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
2565
+ "~/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
2566
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
2567
+ "~/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
2568
+ "/Applications/Arc.app/Contents/MacOS/Arc",
2569
+ "~/Applications/Arc.app/Contents/MacOS/Arc"
2570
+ ];
2571
+ LINUX_CHROMIUM_CANDIDATES = [
2572
+ "/usr/bin/google-chrome",
2573
+ "/usr/bin/chromium",
2574
+ "/usr/bin/chromium-browser",
2575
+ "/usr/bin/microsoft-edge",
2576
+ "/usr/bin/brave-browser",
2577
+ "/snap/bin/chromium"
2578
+ ];
2579
+ WINDOWS_CHROMIUM_CANDIDATES = [
2580
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
2581
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
2582
+ "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
2583
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
2584
+ "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
2585
+ "C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
2586
+ ];
2587
+ UNIX_CHROMIUM_COMMANDS = [
2588
+ "google-chrome",
2589
+ "chromium",
2590
+ "chromium-browser",
2591
+ "microsoft-edge",
2592
+ "brave-browser"
2593
+ ];
2594
+ WINDOWS_CHROMIUM_COMMANDS = ["chrome", "msedge", "brave"];
2595
+ }
2596
+ });
2597
+
2218
2598
  // src/host/bridge-host.ts
2219
2599
  var bridge_host_exports = {};
2220
2600
  __export(bridge_host_exports, {
@@ -2226,20 +2606,24 @@ function shouldAcquireLease(msg) {
2226
2606
  if (msg.readOnly === true) return false;
2227
2607
  return WRITE_COMMAND_TYPES.has(msg.type);
2228
2608
  }
2229
- var import_child_process2, import_fs2, import_http2, import_path2, import_ws, WRITE_COMMAND_TYPES, DAEMON_HEARTBEAT_INTERVAL_MS, HTTP_MIME_TYPES, SootSimBridgeHost;
2609
+ var import_child_process4, import_fs4, import_http2, import_path4, import_ws, WRITE_COMMAND_TYPES, DAEMON_HEARTBEAT_INTERVAL_MS, DEFAULT_RUNTIME_UPDATE_INTERVAL_MS, RUNTIME_UPDATE_INTERVAL_ENV, HTTP_MIME_TYPES, SootSimBridgeHost;
2230
2610
  var init_bridge_host = __esm({
2231
2611
  "src/host/bridge-host.ts"() {
2232
2612
  "use strict";
2233
- import_child_process2 = require("child_process");
2234
- import_fs2 = __toESM(require("fs"), 1);
2613
+ import_child_process4 = require("child_process");
2614
+ import_fs4 = __toESM(require("fs"), 1);
2235
2615
  import_http2 = require("http");
2236
- import_path2 = __toESM(require("path"), 1);
2616
+ import_path4 = __toESM(require("path"), 1);
2237
2617
  import_ws = require("ws");
2238
2618
  init_bridge_constants();
2239
2619
  init_home_paths();
2620
+ init_runtime_delivery();
2240
2621
  init_agent_host();
2622
+ init_open_url();
2241
2623
  WRITE_COMMAND_TYPES = /* @__PURE__ */ new Set(["tap", "keyboard", "close"]);
2242
2624
  DAEMON_HEARTBEAT_INTERVAL_MS = 5e3;
2625
+ DEFAULT_RUNTIME_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
2626
+ RUNTIME_UPDATE_INTERVAL_ENV = "SOOTSIM_RUNTIME_UPDATE_INTERVAL_MS";
2243
2627
  HTTP_MIME_TYPES = {
2244
2628
  ".html": "text/html; charset=utf-8",
2245
2629
  ".js": "application/javascript",
@@ -2285,6 +2669,10 @@ var init_bridge_host = __esm({
2285
2669
  static CLI_IDLE_TIMEOUT_MS = 6e4;
2286
2670
  static CLI_LEASE_TTL_MS = 6e5;
2287
2671
  static USER_ACTIVE_LEASE_TTL_MS = 8e3;
2672
+ // explicit user actions (clicking Boot, focusing the tab to take it over)
2673
+ // hold the tab longer than passive canvas interaction so reconnecting clis
2674
+ // can't immediately reclaim while the user gets oriented.
2675
+ static USER_BOOT_LEASE_TTL_MS = 6e4;
2288
2676
  static BROWSER_RECONNECT_TTL_MS = 3e4;
2289
2677
  preferredPort;
2290
2678
  portFallbackCount;
@@ -2292,14 +2680,15 @@ var init_bridge_host = __esm({
2292
2680
  effectivePort = 0;
2293
2681
  startedAt = 0;
2294
2682
  heartbeatTimer = null;
2683
+ runtimeUpdateTimer = null;
2684
+ runtimeUpdateInFlight = null;
2295
2685
  activeRuntimeVersion = null;
2296
2686
  activeRuntimeDirPath = null;
2297
2687
  constructor(opts = {}) {
2298
2688
  this.preferredPort = opts.port || DEFAULT_SOOTSIM_BRIDGE_PORT;
2299
2689
  this.port = this.preferredPort;
2300
2690
  this.shouldWriteLockfile = opts.writeLockfile === true;
2301
- const defaultFallback = this.shouldWriteLockfile ? 1 : 10;
2302
- this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? defaultFallback);
2691
+ this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? 10);
2303
2692
  this.openUrlHandler = opts.openUrl;
2304
2693
  this.agentHost = new AgentHost({ getExcludePorts: opts.agentScanExcludes });
2305
2694
  }
@@ -2389,6 +2778,8 @@ var init_bridge_host = __esm({
2389
2778
  const origin = req.headers.origin;
2390
2779
  const role = origin ? "browser" : "cli";
2391
2780
  let browser = null;
2781
+ ws.on("error", () => {
2782
+ });
2392
2783
  this.agentHost.registerSocket(ws);
2393
2784
  if (role === "browser") {
2394
2785
  browser = {
@@ -2532,15 +2923,18 @@ var init_bridge_host = __esm({
2532
2923
  }
2533
2924
  }
2534
2925
  const hadLease = !!browser.cliLease;
2535
- browser.cliLease = void 0;
2536
- if (booted.length > 0 || hadLease) {
2537
- process.stderr.write(
2538
- `sootsim booted ${booted.length} cli client(s)${hadLease ? " + cleared lease" : ""} from [${browser.id}]
2926
+ browser.cliLease = {
2927
+ kind: "user-active",
2928
+ cliSessionKey: "__user-active__",
2929
+ cliLabel: "active user",
2930
+ expiresAt: Date.now() + _SootSimBridgeHost.USER_BOOT_LEASE_TTL_MS
2931
+ };
2932
+ process.stderr.write(
2933
+ `sootsim booted ${booted.length} cli client(s)${hadLease ? " (overrode prior lease)" : ""}; held tab for user [${browser.id}]
2539
2934
  `
2540
- );
2541
- this.recordBrowserAction(browser.id, "browser booted cli clients");
2542
- this.broadcastBrowserClientStates();
2543
- }
2935
+ );
2936
+ this.recordBrowserAction(browser.id, "browser booted cli clients");
2937
+ this.broadcastBrowserClientStates();
2544
2938
  return;
2545
2939
  }
2546
2940
  const internalPending = this.pendingCommands.get(msg.id);
@@ -2613,7 +3007,7 @@ var init_bridge_host = __esm({
2613
3007
  if (typeof msg.url !== "string" || !msg.url) {
2614
3008
  throw new Error("bridge:open requires a url");
2615
3009
  }
2616
- await this.openUrl(msg.url);
3010
+ await this.openUrl(msg.url, { newWindow: msg.newWindow === true });
2617
3011
  if (ws.readyState === import_ws.WebSocket.OPEN) {
2618
3012
  ws.send(
2619
3013
  JSON.stringify({
@@ -2777,9 +3171,11 @@ var init_bridge_host = __esm({
2777
3171
  }
2778
3172
  }, DAEMON_HEARTBEAT_INTERVAL_MS);
2779
3173
  this.heartbeatTimer.unref();
3174
+ this.startRuntimeUpdater();
2780
3175
  }
2781
3176
  void this.agentHost.seedOnBoot();
2782
3177
  }
3178
+ bootstrapping = true;
2783
3179
  buildLockfileSnapshot() {
2784
3180
  return {
2785
3181
  schema: 1,
@@ -2790,7 +3186,8 @@ var init_bridge_host = __esm({
2790
3186
  activeRuntime: this.activeRuntimeVersion,
2791
3187
  activeRuntimeDir: this.activeRuntimeDirPath,
2792
3188
  startedAt: this.startedAt,
2793
- heartbeatAt: Date.now()
3189
+ heartbeatAt: Date.now(),
3190
+ bootstrapping: this.bootstrapping
2794
3191
  };
2795
3192
  }
2796
3193
  writeLockfileSnapshot() {
@@ -2800,6 +3197,61 @@ var init_bridge_host = __esm({
2800
3197
  this.activeRuntimeVersion = readActiveRuntime();
2801
3198
  this.activeRuntimeDirPath = activeRuntimeDir();
2802
3199
  }
3200
+ resolveRuntimeUpdateIntervalMs() {
3201
+ const raw = Number(process.env[RUNTIME_UPDATE_INTERVAL_ENV]);
3202
+ if (Number.isFinite(raw) && raw > 0) return Math.max(100, Math.round(raw));
3203
+ return DEFAULT_RUNTIME_UPDATE_INTERVAL_MS;
3204
+ }
3205
+ startRuntimeUpdater() {
3206
+ if (!this.shouldWriteLockfile || this.runtimeUpdateTimer) return;
3207
+ void this.runRuntimeUpdate("startup");
3208
+ const intervalMs = this.resolveRuntimeUpdateIntervalMs();
3209
+ this.runtimeUpdateTimer = setInterval(() => {
3210
+ void this.runRuntimeUpdate("periodic");
3211
+ }, intervalMs);
3212
+ this.runtimeUpdateTimer.unref();
3213
+ }
3214
+ runRuntimeUpdate(reason) {
3215
+ if (this.runtimeUpdateInFlight) return this.runtimeUpdateInFlight;
3216
+ this.runtimeUpdateInFlight = (async () => {
3217
+ try {
3218
+ if (reason === "startup") {
3219
+ process.stderr.write("sootsim: checking for runtime updates\u2026\n");
3220
+ }
3221
+ const result = await updateRuntimeToLatest();
3222
+ if (!result.updated || !result.latestVersion) {
3223
+ if (reason === "startup") {
3224
+ process.stderr.write(
3225
+ `sootsim: runtime ${this.activeRuntimeVersion ?? "(none)"} is current
3226
+ `
3227
+ );
3228
+ }
3229
+ return;
3230
+ }
3231
+ const active = this.setActiveRuntime(result.latestVersion);
3232
+ process.stderr.write(`sootsim runtime updated to ${active.version} (${reason})
3233
+ `);
3234
+ } catch (err) {
3235
+ process.stderr.write(
3236
+ `sootsim runtime update failed (${reason}): ${err instanceof Error ? err.message : String(err)}
3237
+ `
3238
+ );
3239
+ } finally {
3240
+ this.runtimeUpdateInFlight = null;
3241
+ if (reason === "startup" && this.bootstrapping) {
3242
+ this.bootstrapping = false;
3243
+ if (this.shouldWriteLockfile && this.httpServer) {
3244
+ try {
3245
+ this.writeLockfileSnapshot();
3246
+ } catch {
3247
+ }
3248
+ }
3249
+ process.stderr.write("sootsim: ready\n");
3250
+ }
3251
+ }
3252
+ })();
3253
+ return this.runtimeUpdateInFlight;
3254
+ }
2803
3255
  /** update the active runtime on disk + in memory. the caller guarantees
2804
3256
  * the version directory exists. pushes a runtime:changed message to all
2805
3257
  * connected browsers so electron (or any renderer) can reload. */
@@ -2957,47 +3409,50 @@ var init_bridge_host = __esm({
2957
3409
  return;
2958
3410
  }
2959
3411
  }
2960
- const resolved = import_path2.default.resolve(baseDir, "." + rel);
2961
- const baseWithSep = baseDir.endsWith(import_path2.default.sep) ? baseDir : baseDir + import_path2.default.sep;
3412
+ const resolved = import_path4.default.resolve(baseDir, "." + rel);
3413
+ const baseWithSep = baseDir.endsWith(import_path4.default.sep) ? baseDir : baseDir + import_path4.default.sep;
2962
3414
  if (!resolved.startsWith(baseWithSep) && resolved !== baseDir) {
2963
3415
  res.writeHead(403);
2964
3416
  res.end("forbidden");
2965
3417
  return;
2966
3418
  }
2967
- import_fs2.default.realpath(resolved, (realErr, realResolved) => {
3419
+ import_fs4.default.realpath(resolved, (realErr, realResolved) => {
2968
3420
  const servePath = realErr ? resolved : realResolved;
2969
- const servePathWithSep = servePath.endsWith(import_path2.default.sep) ? servePath : servePath + import_path2.default.sep;
3421
+ const servePathWithSep = servePath.endsWith(import_path4.default.sep) ? servePath : servePath + import_path4.default.sep;
2970
3422
  if (!realErr) {
2971
3423
  const realBaseWithSep = (() => {
2972
3424
  try {
2973
- const rb = import_fs2.default.realpathSync(baseDir);
2974
- return rb.endsWith(import_path2.default.sep) ? rb : rb + import_path2.default.sep;
3425
+ const rb = import_fs4.default.realpathSync(baseDir);
3426
+ return rb.endsWith(import_path4.default.sep) ? rb : rb + import_path4.default.sep;
2975
3427
  } catch {
2976
3428
  return baseWithSep;
2977
3429
  }
2978
3430
  })();
2979
- if (!servePathWithSep.startsWith(realBaseWithSep) && servePath + import_path2.default.sep !== realBaseWithSep) {
3431
+ if (!servePathWithSep.startsWith(realBaseWithSep) && servePath + import_path4.default.sep !== realBaseWithSep) {
2980
3432
  res.writeHead(403);
2981
3433
  res.end("forbidden");
2982
3434
  return;
2983
3435
  }
2984
3436
  }
2985
- import_fs2.default.stat(servePath, (err, stats) => {
3437
+ import_fs4.default.stat(servePath, (err, stats) => {
2986
3438
  if (err || !stats?.isFile()) {
2987
- const ext2 = import_path2.default.extname(rel).toLowerCase();
3439
+ const ext2 = import_path4.default.extname(rel).toLowerCase();
2988
3440
  if (ext2 && ext2 !== ".html") {
2989
3441
  res.writeHead(404);
2990
3442
  res.end("not found");
2991
3443
  return;
2992
3444
  }
2993
- const indexPath = import_path2.default.join(baseDir, "index.html");
2994
- import_fs2.default.readFile(indexPath, (err2, data) => {
3445
+ const indexPath = import_path4.default.join(baseDir, "index.html");
3446
+ import_fs4.default.readFile(indexPath, (err2, data) => {
2995
3447
  if (err2) {
2996
3448
  res.writeHead(404);
2997
3449
  res.end("not found");
2998
3450
  return;
2999
3451
  }
3000
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3452
+ res.writeHead(200, {
3453
+ "Content-Type": "text/html; charset=utf-8",
3454
+ "Cache-Control": "no-store"
3455
+ });
3001
3456
  if (method === "HEAD") {
3002
3457
  res.end();
3003
3458
  return;
@@ -3006,14 +3461,17 @@ var init_bridge_host = __esm({
3006
3461
  });
3007
3462
  return;
3008
3463
  }
3009
- const ext = import_path2.default.extname(servePath).toLowerCase();
3464
+ const ext = import_path4.default.extname(servePath).toLowerCase();
3010
3465
  const contentType = HTTP_MIME_TYPES[ext] || "application/octet-stream";
3011
- res.writeHead(200, { "Content-Type": contentType });
3466
+ res.writeHead(200, {
3467
+ "Content-Type": contentType,
3468
+ "Cache-Control": "no-store"
3469
+ });
3012
3470
  if (method === "HEAD") {
3013
3471
  res.end();
3014
3472
  return;
3015
3473
  }
3016
- const stream = import_fs2.default.createReadStream(servePath);
3474
+ const stream = import_fs4.default.createReadStream(servePath);
3017
3475
  stream.pipe(res);
3018
3476
  stream.on("error", () => {
3019
3477
  try {
@@ -3096,7 +3554,7 @@ var init_bridge_host = __esm({
3096
3554
  const target = `${filePath}${loc}`;
3097
3555
  const trySpawn = (cmd, args) => new Promise((resolve2) => {
3098
3556
  try {
3099
- const child = (0, import_child_process2.spawn)(cmd, args, { detached: true, stdio: "ignore" });
3557
+ const child = (0, import_child_process4.spawn)(cmd, args, { detached: true, stdio: "ignore" });
3100
3558
  let settled = false;
3101
3559
  child.on("error", () => {
3102
3560
  if (settled) return;
@@ -3123,26 +3581,12 @@ var init_bridge_host = __esm({
3123
3581
  if (await trySpawn("code", ["-g", target])) return;
3124
3582
  await this.openUrl(filePath);
3125
3583
  }
3126
- async openUrl(url) {
3584
+ async openUrl(url, options = {}) {
3127
3585
  if (this.openUrlHandler) {
3128
- await this.openUrlHandler(url);
3129
- return;
3130
- }
3131
- if (process.platform === "darwin") {
3132
- const child2 = (0, import_child_process2.spawn)("open", ["-g", url], { detached: true, stdio: "ignore" });
3133
- child2.unref();
3586
+ await this.openUrlHandler(url, options);
3134
3587
  return;
3135
3588
  }
3136
- if (process.platform === "win32") {
3137
- const child2 = (0, import_child_process2.spawn)("cmd", ["/c", "start", "", url], {
3138
- detached: true,
3139
- stdio: "ignore"
3140
- });
3141
- child2.unref();
3142
- return;
3143
- }
3144
- const child = (0, import_child_process2.spawn)("xdg-open", [url], { detached: true, stdio: "ignore" });
3145
- child.unref();
3589
+ await openUrl(url, options);
3146
3590
  }
3147
3591
  async close() {
3148
3592
  if (this.cliIdleTimer) {
@@ -3153,6 +3597,10 @@ var init_bridge_host = __esm({
3153
3597
  clearInterval(this.heartbeatTimer);
3154
3598
  this.heartbeatTimer = null;
3155
3599
  }
3600
+ if (this.runtimeUpdateTimer) {
3601
+ clearInterval(this.runtimeUpdateTimer);
3602
+ this.runtimeUpdateTimer = null;
3603
+ }
3156
3604
  if (this.shouldWriteLockfile) {
3157
3605
  try {
3158
3606
  removeDaemonLockfile();
@@ -3289,11 +3737,14 @@ var init_bridge_host = __esm({
3289
3737
  if (existing && existing.kind === "cli") {
3290
3738
  return;
3291
3739
  }
3740
+ const now = Date.now();
3741
+ const refreshed = now + _SootSimBridgeHost.USER_ACTIVE_LEASE_TTL_MS;
3742
+ const expiresAt = existing && existing.kind === "user-active" ? Math.max(existing.expiresAt, refreshed) : refreshed;
3292
3743
  browser.cliLease = {
3293
3744
  kind: "user-active",
3294
3745
  cliSessionKey: "__user-active__",
3295
3746
  cliLabel: "active user",
3296
- expiresAt: Date.now() + _SootSimBridgeHost.USER_ACTIVE_LEASE_TTL_MS
3747
+ expiresAt
3297
3748
  };
3298
3749
  this.broadcastBrowserClientStates();
3299
3750
  }
@@ -3508,6 +3959,10 @@ var init_bridge_host = __esm({
3508
3959
  clearInterval(this.cliIdleTimer);
3509
3960
  this.cliIdleTimer = null;
3510
3961
  }
3962
+ if (this.runtimeUpdateTimer) {
3963
+ clearInterval(this.runtimeUpdateTimer);
3964
+ this.runtimeUpdateTimer = null;
3965
+ }
3511
3966
  const wss = this.wss;
3512
3967
  const httpServer = this.httpServer;
3513
3968
  this.wss = null;
@@ -3532,45 +3987,49 @@ var init_bridge_host = __esm({
3532
3987
  // src/vite-plugin.ts
3533
3988
  var vite_plugin_exports = {};
3534
3989
  __export(vite_plugin_exports, {
3990
+ engineShellWorkerPlugins: () => engineShellWorkerPlugins,
3535
3991
  fixJsxRuntimeExports: () => fixJsxRuntimeExports,
3992
+ keyboardControllerBindingsRedirect: () => keyboardControllerBindingsRedirect,
3993
+ reanimatedNativeSeamRedirect: () => reanimatedNativeSeamRedirect,
3536
3994
  sootsim: () => sootsim,
3537
3995
  workerReactBridgePlugin: () => workerReactBridgePlugin,
3996
+ workletsBabelTransform: () => workletsBabelTransform,
3538
3997
  wsBridgePlugin: () => wsBridgePlugin
3539
3998
  });
3540
3999
  module.exports = __toCommonJS(vite_plugin_exports);
3541
- var import_fs3 = __toESM(require("fs"), 1);
3542
- var import_path3 = __toESM(require("path"), 1);
4000
+ var import_fs5 = __toESM(require("fs"), 1);
4001
+ var import_path5 = __toESM(require("path"), 1);
3543
4002
  var import_url = require("url");
3544
4003
  var import_vite = require("vite");
3545
4004
  init_bridge_constants();
3546
- var sootsimRoot = import_path3.default.resolve(import_path3.default.dirname((0, import_url.fileURLToPath)(__sootsim_import_meta_url)), "..");
3547
- var workspaceRoot = import_path3.default.resolve(sootsimRoot, "..", "..");
3548
- var workspaceNodeModules = import_path3.default.resolve(workspaceRoot, "node_modules");
3549
- var workspaceTamaguiDir = import_path3.default.resolve(workspaceNodeModules, "@tamagui");
3550
- var engineRoot = import_path3.default.resolve(workspaceRoot, "packages/sootsim-engine");
3551
- var rnShimPath = import_path3.default.resolve(engineRoot, "src/react-native/index.ts");
3552
- var reactBridgePath = import_path3.default.resolve(engineRoot, "src/react-bridge.ts");
4005
+ var sootsimRoot = import_path5.default.resolve(import_path5.default.dirname((0, import_url.fileURLToPath)(__sootsim_import_meta_url)), "..");
4006
+ var workspaceRoot = import_path5.default.resolve(sootsimRoot, "..", "..");
4007
+ var workspaceNodeModules = import_path5.default.resolve(workspaceRoot, "node_modules");
4008
+ var workspaceTamaguiDir = import_path5.default.resolve(workspaceNodeModules, "@tamagui");
4009
+ var engineRoot = import_path5.default.resolve(workspaceRoot, "packages/sootsim-engine");
4010
+ var rnShimPath = import_path5.default.resolve(engineRoot, "src/react-native/index.ts");
4011
+ var reactBridgePath = import_path5.default.resolve(engineRoot, "src/react-bridge.ts");
3553
4012
  var sootsimBrowserSourceAliases = [
3554
4013
  {
3555
4014
  find: "sootsim/backend-origin",
3556
- replacement: import_path3.default.resolve(sootsimRoot, "src/backend-origin.ts")
4015
+ replacement: import_path5.default.resolve(sootsimRoot, "src/backend-origin.ts")
3557
4016
  },
3558
4017
  {
3559
4018
  find: "sootsim/bridge-constants",
3560
- replacement: import_path3.default.resolve(sootsimRoot, "src/bridge-constants.ts")
4019
+ replacement: import_path5.default.resolve(sootsimRoot, "src/bridge-constants.ts")
3561
4020
  },
3562
4021
  {
3563
4022
  find: "sootsim/dev-bundle-resolution",
3564
- replacement: import_path3.default.resolve(sootsimRoot, "src/dev-bundle-resolution.ts")
4023
+ replacement: import_path5.default.resolve(sootsimRoot, "src/dev-bundle-resolution.ts")
3565
4024
  }
3566
4025
  ];
3567
- var compatRoot = import_path3.default.resolve(sootsimRoot, "..", "compat");
3568
- var compatStubsDir = import_path3.default.resolve(compatRoot, "src/stubs");
3569
- var nativeAutoStubPath = import_path3.default.resolve(compatStubsDir, "native-auto-stub.ts");
3570
- var rnDeepStubDefault = import_path3.default.resolve(compatStubsDir, "react-native-internals.ts");
4026
+ var compatRoot = import_path5.default.resolve(sootsimRoot, "..", "compat");
4027
+ var compatStubsDir = import_path5.default.resolve(compatRoot, "src/stubs");
4028
+ var nativeAutoStubPath = import_path5.default.resolve(compatStubsDir, "native-auto-stub.ts");
4029
+ var rnDeepStubDefault = import_path5.default.resolve(compatStubsDir, "react-native-internals.ts");
3571
4030
  function getInternalTamaguiPackages() {
3572
4031
  try {
3573
- const scoped = import_fs3.default.readdirSync(workspaceTamaguiDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => `@tamagui/${entry.name}`);
4032
+ const scoped = import_fs5.default.readdirSync(workspaceTamaguiDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => `@tamagui/${entry.name}`);
3574
4033
  return ["tamagui", ...scoped].sort();
3575
4034
  } catch {
3576
4035
  return ["tamagui"];
@@ -3578,19 +4037,19 @@ function getInternalTamaguiPackages() {
3578
4037
  }
3579
4038
  var internalTamaguiPackages = getInternalTamaguiPackages();
3580
4039
  var rnLibraryStubs = {
3581
- "react-native/Libraries/Pressability/usePressability": import_path3.default.resolve(
4040
+ "react-native/Libraries/Pressability/usePressability": import_path5.default.resolve(
3582
4041
  compatStubsDir,
3583
4042
  "rn-libraries/usePressability.ts"
3584
4043
  ),
3585
- "react-native/Libraries/Pressability/Pressability": import_path3.default.resolve(
4044
+ "react-native/Libraries/Pressability/Pressability": import_path5.default.resolve(
3586
4045
  compatStubsDir,
3587
4046
  "rn-libraries/Pressability.ts"
3588
4047
  ),
3589
- "react-native/Libraries/Renderer/shims/ReactFabric": import_path3.default.resolve(
4048
+ "react-native/Libraries/Renderer/shims/ReactFabric": import_path5.default.resolve(
3590
4049
  compatStubsDir,
3591
4050
  "rn-libraries/ReactFabric.ts"
3592
4051
  ),
3593
- "react-native/Libraries/Renderer/shims/ReactNative": import_path3.default.resolve(
4052
+ "react-native/Libraries/Renderer/shims/ReactNative": import_path5.default.resolve(
3594
4053
  compatStubsDir,
3595
4054
  "rn-libraries/ReactNative.ts"
3596
4055
  )
@@ -3601,11 +4060,18 @@ var builtinStubs = {
3601
4060
  "react-native-gesture-handler/ReanimatedSwipeable": "reanimated-swipeable.ts",
3602
4061
  // NOTE: 'soot-swipeable' is a real workspace package (packages/soot-swipeable),
3603
4062
  // resolved naturally by vite — no builtin stub entry needed.
3604
- "react-native-reanimated": "react-native-reanimated.ts",
4063
+ // react-native-reanimated is mostly pure-JS; only the native seam (turbomodule
4064
+ // + platform functions) is redirected. wired below as `reanimatedNativeSeamRedirect`.
3605
4065
  "react-native-screens": "react-native-screens.ts",
3606
4066
  "@react-native-masked-view/masked-view": "masked-view.ts",
3607
4067
  "react-native-svg": "react-native-svg.ts",
3608
- "react-native-keyboard-controller": "react-native-keyboard-controller.ts",
4068
+ // react-native-keyboard-controller is mostly pure-JS — KeyboardAwareScrollView,
4069
+ // KeyboardStickyView, hooks, animated module, and KeyboardAvoidingView all sit
4070
+ // on top of reanimated worklets + scrollTo over a thin native-binding seam
4071
+ // (`bindings.ts` / `bindings.native.ts`) plus RN's platform findNodeHandle.
4072
+ // per the project policy of not shimming pure-JS libs, we let the package
4073
+ // resolve from node_modules and redirect only those native/platform seams.
4074
+ // wired below as `keyboardControllerBindingsRedirect`.
3609
4075
  "react-native-teleport": "react-native-teleport.tsx",
3610
4076
  "react-native-ios-context-menu": "react-native-ios-context-menu.ts",
3611
4077
  "react-native-ios-utilities": "react-native-ios-utilities.ts",
@@ -3625,8 +4091,18 @@ var builtinStubs = {
3625
4091
  "@callstack/react-native-bottom-tabs": "react-native-bottom-tabs.ts",
3626
4092
  "@expo/ui/swift-ui": "expo-ui.ts",
3627
4093
  "@expo/ui": "expo-ui.ts",
3628
- "react-native-worklets": "react-native-worklets.ts",
4094
+ // worklets stub lives in a package-shaped directory (with package.json)
4095
+ // so upstream code that does `require('react-native-worklets/package.json')`
4096
+ // (e.g. reanimated's assertWorkletsVersion) can read a real version field.
4097
+ "react-native-worklets": "react-native-worklets-pkg/index.ts",
4098
+ "react-native-worklets/package.json": "react-native-worklets-pkg/package.json",
3629
4099
  "react-native-webview": "react-native-webview.ts",
4100
+ // pager-view, video — bundle source paths reach codegenNativeCommands /
4101
+ // requireNativeComponent from `react-native` deep imports that resolve to
4102
+ // undefined, so the JS-only stubs MUST replace the package at build time.
4103
+ "react-native-pager-view": "react-native-pager-view.ts",
4104
+ "react-native-video": "react-native-video.ts",
4105
+ "expo-video": "expo-video.ts",
3630
4106
  "expo-haptics": "expo-haptics.ts",
3631
4107
  "expo-crypto": "expo-crypto.ts",
3632
4108
  "expo-clipboard": "expo-clipboard.ts",
@@ -3675,14 +4151,14 @@ function isNativePackage(source) {
3675
4151
  }
3676
4152
  function moduleExistsIn(pkgName, dir) {
3677
4153
  try {
3678
- return import_fs3.default.existsSync(import_path3.default.join(dir, "node_modules", pkgName));
4154
+ return import_fs5.default.existsSync(import_path5.default.join(dir, "node_modules", pkgName));
3679
4155
  } catch {
3680
4156
  return false;
3681
4157
  }
3682
4158
  }
3683
4159
  function sootsim(options = {}) {
3684
- const appDir = options.app ? import_path3.default.resolve(options.app) : "";
3685
- const extraSources = (options.sources || []).map((s) => import_path3.default.resolve(s));
4160
+ const appDir = options.app ? import_path5.default.resolve(options.app) : "";
4161
+ const extraSources = (options.sources || []).map((s) => import_path5.default.resolve(s));
3686
4162
  const ownedPackages = /* @__PURE__ */ new Set([...coreOwnedPackages, ...options.ownedPackages || []]);
3687
4163
  function isAppSource(filePath) {
3688
4164
  if (filePath.includes("/node_modules/")) return false;
@@ -3695,6 +4171,13 @@ function sootsim(options = {}) {
3695
4171
  sootsimConfigPlugin(appDir, extraSources),
3696
4172
  fixJsxRuntimeExports(),
3697
4173
  nodeModulesJsxTransform(),
4174
+ // must run before metroNativeResolve — that plugin resolves relative
4175
+ // imports like `./bindings` to absolute file paths via the platform-
4176
+ // extension lookup, and once it returns a resolved id, downstream
4177
+ // resolveId hooks don't see the source string anymore.
4178
+ keyboardControllerBindingsRedirect(),
4179
+ reanimatedNativeSeamRedirect(),
4180
+ workletsBabelTransform(isAppSource),
3698
4181
  metroNativeResolve(isAppSource),
3699
4182
  reactNativeRequirePlugin(isAppSource),
3700
4183
  externalAppResolvePlugin(appDir, isAppSource, ownedPackages),
@@ -3703,6 +4186,129 @@ function sootsim(options = {}) {
3703
4186
  stubMissingImages(isAppSource)
3704
4187
  ];
3705
4188
  }
4189
+ function engineShellWorkerPlugins() {
4190
+ return [
4191
+ workerReactBridgePlugin(),
4192
+ fixJsxRuntimeExports(),
4193
+ reanimatedNativeSeamRedirect(),
4194
+ keyboardControllerBindingsRedirect(),
4195
+ workletsBabelTransform(() => true)
4196
+ ];
4197
+ }
4198
+ function keyboardControllerBindingsRedirect() {
4199
+ const target = import_path5.default.resolve(compatStubsDir, "react-native-keyboard-controller.ts");
4200
+ const findNodeHandleTarget = "\0sootsim:rnkc-find-node-handle-native";
4201
+ const nativePlatformBasenames = /* @__PURE__ */ new Set(["findNodeHandle", "reanimated"]);
4202
+ const nativeExts = [
4203
+ ".ios.tsx",
4204
+ ".ios.ts",
4205
+ ".ios.jsx",
4206
+ ".ios.js",
4207
+ ".native.tsx",
4208
+ ".native.ts",
4209
+ ".native.jsx",
4210
+ ".native.js",
4211
+ ".native.mjs"
4212
+ ];
4213
+ const resolveNativePlatformFile = (source, importer) => {
4214
+ const base = import_path5.default.resolve(import_path5.default.dirname(importer), source);
4215
+ for (const ext of nativeExts) {
4216
+ const candidate = base + ext;
4217
+ try {
4218
+ if (import_fs5.default.existsSync(candidate)) return candidate;
4219
+ } catch {
4220
+ }
4221
+ }
4222
+ for (const ext of nativeExts) {
4223
+ const candidate = import_path5.default.join(base, "index" + ext);
4224
+ try {
4225
+ if (import_fs5.default.existsSync(candidate)) return candidate;
4226
+ } catch {
4227
+ }
4228
+ }
4229
+ return null;
4230
+ };
4231
+ return {
4232
+ name: "sootsim-keyboard-controller-bindings-redirect",
4233
+ enforce: "pre",
4234
+ resolveId(source, importer) {
4235
+ if (!importer) return null;
4236
+ if (!importer.includes("/react-native-keyboard-controller/")) return null;
4237
+ const basename = source.split("/").pop() ?? "";
4238
+ if (nativePlatformBasenames.has(basename)) {
4239
+ return resolveNativePlatformFile(source, importer) ?? (basename === "findNodeHandle" ? findNodeHandleTarget : null);
4240
+ }
4241
+ if (basename !== "bindings" && basename !== "bindings.native") return null;
4242
+ if (importer === target) return null;
4243
+ return target;
4244
+ },
4245
+ load(id) {
4246
+ if (id === findNodeHandleTarget) {
4247
+ return `export { findNodeHandle } from ${JSON.stringify(rnShimPath)};`;
4248
+ }
4249
+ return null;
4250
+ }
4251
+ };
4252
+ }
4253
+ var reanimatedNativeSeamFiles = /* @__PURE__ */ new Set([
4254
+ "NativeReanimatedModule",
4255
+ "scrollTo",
4256
+ "measure",
4257
+ "setNativeProps",
4258
+ "dispatchCommand",
4259
+ "setGestureState"
4260
+ ]);
4261
+ var reanimatedFabricUtilsRedirectPath = import_path5.default.resolve(
4262
+ compatStubsDir,
4263
+ "react-native-reanimated-fabric-utils.ts"
4264
+ );
4265
+ var reanimatedValidateWorkletsVersionPath = import_path5.default.resolve(
4266
+ compatStubsDir,
4267
+ "react-native-reanimated-validate-worklets-version.ts"
4268
+ );
4269
+ function reanimatedNativeSeamRedirect() {
4270
+ const target = import_path5.default.resolve(compatStubsDir, "react-native-reanimated.ts");
4271
+ return {
4272
+ name: "sootsim-reanimated-native-seam-redirect",
4273
+ enforce: "pre",
4274
+ resolveId(source, importer) {
4275
+ if (source === "react-native-worklets/package.json") {
4276
+ return "\0sootsim:react-native-worklets-package-json";
4277
+ }
4278
+ if (!importer) return null;
4279
+ if (source === "react-native-reanimated/scripts/validate-worklets-version" || source === "react-native-reanimated/scripts/validate-worklets-version.js") {
4280
+ return reanimatedValidateWorkletsVersionPath;
4281
+ }
4282
+ if (!importer.includes("/react-native-reanimated/")) return null;
4283
+ if (importer === target) return null;
4284
+ const basename = (source.split("/").pop() ?? "").replace(
4285
+ /\.(native|web|ios|android)$/,
4286
+ ""
4287
+ );
4288
+ if (basename === "fabricUtils" && importer !== reanimatedFabricUtilsRedirectPath) {
4289
+ return reanimatedFabricUtilsRedirectPath;
4290
+ }
4291
+ if (!reanimatedNativeSeamFiles.has(basename)) return null;
4292
+ if (process.env.SOOTSIM_REANIMATED_REDIRECT_DEBUG) {
4293
+ console.log(
4294
+ "[reanimatedRedirect]",
4295
+ basename,
4296
+ "<-",
4297
+ source,
4298
+ "from",
4299
+ importer.slice(-80)
4300
+ );
4301
+ }
4302
+ return target;
4303
+ },
4304
+ load(id) {
4305
+ if (id === "\0sootsim:react-native-worklets-package-json") {
4306
+ return `export default ${JSON.stringify({ version: "0.0.0-sootsim", name: "react-native-worklets" })}`;
4307
+ }
4308
+ return null;
4309
+ }
4310
+ };
4311
+ }
3706
4312
  function wsBridgePlugin() {
3707
4313
  let bridgeHost = null;
3708
4314
  return {
@@ -3733,12 +4339,12 @@ function sootsimConfigPlugin(appDir, extraSources) {
3733
4339
  for (const src of extraSources) fsAllow.push(src);
3734
4340
  if (appDir) {
3735
4341
  fsAllow.push(appDir);
3736
- const appNodeModules = import_path3.default.join(appDir, "node_modules");
3737
- if (import_fs3.default.existsSync(appNodeModules)) fsAllow.push(appNodeModules);
4342
+ const appNodeModules = import_path5.default.join(appDir, "node_modules");
4343
+ if (import_fs5.default.existsSync(appNodeModules)) fsAllow.push(appNodeModules);
3738
4344
  }
3739
4345
  const stubAliases = Object.entries(builtinStubs).sort((a, b) => b[0].length - a[0].length).map(([find, file]) => ({
3740
4346
  find,
3741
- replacement: import_path3.default.resolve(compatStubsDir, file)
4347
+ replacement: import_path5.default.resolve(compatStubsDir, file)
3742
4348
  }));
3743
4349
  const dedupe = ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"];
3744
4350
  if (!appDir) {
@@ -3920,10 +4526,10 @@ function workerReactBridgePlugin() {
3920
4526
  async resolveId(source, importer, options) {
3921
4527
  if (source !== "react" || !importer) return null;
3922
4528
  if (options?.scan || options?.ssr) return null;
3923
- const shouldRedirect = importer.includes("/sootsim/src/") || importer.includes("/sootsim-engine/src/") || importer.includes("/compat/src/") || importer.includes("react-reconciler");
3924
- if (!shouldRedirect) return null;
3925
4529
  if (importer.includes("react-bridge")) return null;
3926
4530
  if (importer.includes("stub-registry")) return null;
4531
+ const shouldRedirect = importer.includes("/sootsim/src/") || importer.includes("/sootsim-engine/src/") || importer.includes("/compat/src/") || importer.includes("react-reconciler") || importer.includes("/node_modules/");
4532
+ if (!shouldRedirect) return null;
3927
4533
  return { id: reactBridgePath, external: false };
3928
4534
  }
3929
4535
  };
@@ -3971,6 +4577,94 @@ function fixJsxRuntimeExports() {
3971
4577
  }
3972
4578
  };
3973
4579
  }
4580
+ var REANIMATED_AUTOWORKLETIZATION_KEYWORDS = [
4581
+ "worklet",
4582
+ "useAnimatedGestureHandler",
4583
+ "useAnimatedScrollHandler",
4584
+ "useFrameCallback",
4585
+ "useAnimatedStyle",
4586
+ "useAnimatedProps",
4587
+ "createAnimatedPropAdapter",
4588
+ "useDerivedValue",
4589
+ "useAnimatedReaction",
4590
+ "useWorkletCallback",
4591
+ "withTiming",
4592
+ "withSpring",
4593
+ "withDecay",
4594
+ "withRepeat",
4595
+ "runOnUI",
4596
+ "executeOnUIRuntimeSync"
4597
+ ];
4598
+ var REANIMATED_REGEX = new RegExp(REANIMATED_AUTOWORKLETIZATION_KEYWORDS.join("|"));
4599
+ var REANIMATED_IGNORED_PATHS_REGEX = /node_modules\/(react|react-dom|react-native|react-native-web)\//;
4600
+ var SOOTSIM_ENGINE_SRC_PATH = "/packages/sootsim-engine/src/";
4601
+ var SOOTSIM_ENGINE_TEST_FIXTURE_PATH = "/packages/sootsim-engine/src/test-fixtures/";
4602
+ var REANIMATED_IMPORT_REGEX = /(?:from\s+['"]react-native-reanimated(?:\/[^'"]*)?['"]|require\(\s*['"]react-native-reanimated(?:\/[^'"]*)?['"]\s*\))/;
4603
+ function shouldApplyWorkletsPlugin(id, code) {
4604
+ if (!/\.(tsx?|jsx?|mjs|cjs)$/.test(id)) return false;
4605
+ if (REANIMATED_IGNORED_PATHS_REGEX.test(id)) return false;
4606
+ if (id.includes(SOOTSIM_ENGINE_SRC_PATH) && !id.includes(SOOTSIM_ENGINE_TEST_FIXTURE_PATH) && !REANIMATED_IMPORT_REGEX.test(code))
4607
+ return false;
4608
+ return REANIMATED_REGEX.test(code);
4609
+ }
4610
+ function workletsBabelTransform(_isAppSource = () => false) {
4611
+ let pluginPathPromise = null;
4612
+ let babelCorePromise = null;
4613
+ return {
4614
+ name: "sootsim-worklets-babel-transform",
4615
+ enforce: "pre",
4616
+ async transform(code, id) {
4617
+ if (!shouldApplyWorkletsPlugin(id, code)) return null;
4618
+ try {
4619
+ if (process.env.SOOTSIM_WORKLETS_BABEL_DEBUG) {
4620
+ console.log("[worklets-babel] transforming", id.slice(-80));
4621
+ }
4622
+ if (!babelCorePromise) {
4623
+ babelCorePromise = import("@babel/core");
4624
+ }
4625
+ if (!pluginPathPromise) {
4626
+ pluginPathPromise = (async () => {
4627
+ const sootsimResolveBase = import_path5.default.resolve(workspaceRoot);
4628
+ return import_path5.default.resolve(
4629
+ sootsimResolveBase,
4630
+ "node_modules",
4631
+ "react-native-worklets",
4632
+ "plugin"
4633
+ );
4634
+ })();
4635
+ }
4636
+ const [{ transformAsync }, pluginPath] = await Promise.all([
4637
+ babelCorePromise,
4638
+ pluginPathPromise
4639
+ ]);
4640
+ const isTSX = id.endsWith(".tsx");
4641
+ const isTS = isTSX || id.endsWith(".ts");
4642
+ const result = await transformAsync(code, {
4643
+ filename: id,
4644
+ babelrc: false,
4645
+ configFile: false,
4646
+ sourceMaps: true,
4647
+ presets: isTS ? [
4648
+ [
4649
+ "@babel/preset-typescript",
4650
+ { isTSX, allExtensions: isTSX, allowDeclareFields: true }
4651
+ ]
4652
+ ] : [],
4653
+ plugins: [
4654
+ ["@babel/plugin-syntax-jsx"],
4655
+ [pluginPath, { processNestedWorklets: true }]
4656
+ ]
4657
+ });
4658
+ if (!result?.code) return null;
4659
+ return { code: result.code, map: result.map ?? null };
4660
+ } catch (err) {
4661
+ const message = err instanceof Error ? err.message : String(err);
4662
+ console.warn(`[sootsim-worklets-babel] transform failed for ${id}: ${message}`);
4663
+ return null;
4664
+ }
4665
+ }
4666
+ };
4667
+ }
3974
4668
  function nodeModulesJsxTransform() {
3975
4669
  return {
3976
4670
  name: "sootsim-node-modules-jsx",
@@ -4008,14 +4702,14 @@ function metroNativeResolve(isAppSource) {
4008
4702
  for (const ext of platformExts) {
4009
4703
  const candidate = base + ext;
4010
4704
  try {
4011
- if (import_fs3.default.existsSync(candidate)) return candidate;
4705
+ if (import_fs5.default.existsSync(candidate)) return candidate;
4012
4706
  } catch {
4013
4707
  }
4014
4708
  }
4015
4709
  for (const ext of platformExts) {
4016
- const candidate = import_path3.default.join(base, "index" + ext);
4710
+ const candidate = import_path5.default.join(base, "index" + ext);
4017
4711
  try {
4018
- if (import_fs3.default.existsSync(candidate)) return candidate;
4712
+ if (import_fs5.default.existsSync(candidate)) return candidate;
4019
4713
  } catch {
4020
4714
  }
4021
4715
  }
@@ -4030,9 +4724,9 @@ function metroNativeResolve(isAppSource) {
4030
4724
  if (!source.startsWith(".")) return null;
4031
4725
  if (platformExts.some((ext) => source.endsWith(ext))) return null;
4032
4726
  const hasBaseExt = allExts.some((ext) => source.endsWith(ext));
4033
- const dir = import_path3.default.dirname(importer);
4727
+ const dir = import_path5.default.dirname(importer);
4034
4728
  if (hasBaseExt) {
4035
- const resolved = import_path3.default.resolve(dir, source);
4729
+ const resolved = import_path5.default.resolve(dir, source);
4036
4730
  for (const ext of allExts) {
4037
4731
  if (source.endsWith(ext)) {
4038
4732
  const found = tryResolve(resolved.slice(0, -ext.length));
@@ -4041,7 +4735,7 @@ function metroNativeResolve(isAppSource) {
4041
4735
  }
4042
4736
  }
4043
4737
  } else {
4044
- const found = tryResolve(import_path3.default.resolve(dir, source));
4738
+ const found = tryResolve(import_path5.default.resolve(dir, source));
4045
4739
  if (found) return found;
4046
4740
  }
4047
4741
  return null;
@@ -4098,7 +4792,7 @@ function reactNativeRequirePlugin(isAppSource) {
4098
4792
  }
4099
4793
  function externalAppResolvePlugin(appDir, isAppSource, ownedPackages) {
4100
4794
  if (!appDir) return { name: "sootsim-external-app-resolve" };
4101
- const appVirtualImporter = import_path3.default.join(appDir, "_virtual_.js");
4795
+ const appVirtualImporter = import_path5.default.join(appDir, "_virtual_.js");
4102
4796
  return {
4103
4797
  name: "sootsim-external-app-resolve",
4104
4798
  enforce: "pre",
@@ -4108,7 +4802,7 @@ function externalAppResolvePlugin(appDir, isAppSource, ownedPackages) {
4108
4802
  return null;
4109
4803
  if (ownedPackages.has(source) || ownedPackages.has(getPackageName(source)))
4110
4804
  return null;
4111
- if (!isAppSource(importer) && !importer.includes(import_path3.default.join(appDir, "node_modules")))
4805
+ if (!isAppSource(importer) && !importer.includes(import_path5.default.join(appDir, "node_modules")))
4112
4806
  return null;
4113
4807
  if (source === "react-native" || source.startsWith("react-native/")) return null;
4114
4808
  const resolved = await this.resolve(source, appVirtualImporter, {
@@ -4210,8 +4904,12 @@ function stubMissingImages(isAppSource) {
4210
4904
  }
4211
4905
  // Annotate the CommonJS export names for ESM import in node:
4212
4906
  0 && (module.exports = {
4907
+ engineShellWorkerPlugins,
4213
4908
  fixJsxRuntimeExports,
4909
+ keyboardControllerBindingsRedirect,
4910
+ reanimatedNativeSeamRedirect,
4214
4911
  sootsim,
4215
4912
  workerReactBridgePlugin,
4913
+ workletsBabelTransform,
4216
4914
  wsBridgePlugin
4217
4915
  });