sootsim 0.1.295 → 0.1.296

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +13 -4
  2. package/dist-cli/bin.js +5 -5
  3. package/dist-cli/chunks/{agent-ASAYE5YY.js → agent-3F4K6MQK.js} +2 -2
  4. package/dist-cli/chunks/{agent-wrapper-K2D4Q5AA.js → agent-wrapper-5LLVCQRZ.js} +2 -2
  5. package/dist-cli/chunks/app-fonts-LPTY7A5G.js +4 -0
  6. package/dist-cli/chunks/{assert-4FKHHDY7.js → assert-R2ZUXEMS.js} +2 -2
  7. package/dist-cli/chunks/{auth-BQPOPEFK.js → auth-CA62Q6IS.js} +2 -2
  8. package/dist-cli/chunks/{beta-3LNYONDF.js → beta-4AXSOQLU.js} +2 -2
  9. package/dist-cli/chunks/{camera-Y5MZ5V4L.js → camera-Y4RBIONE.js} +2 -2
  10. package/dist-cli/chunks/{chunk-L7V46673.js → chunk-222Y7NQA.js} +1 -1
  11. package/dist-cli/chunks/{chunk-6VF477QO.js → chunk-2LQOLMRK.js} +1 -1
  12. package/dist-cli/chunks/{chunk-S6BEA6OC.js → chunk-2XUKWIAL.js} +1 -1
  13. package/dist-cli/chunks/{chunk-XQRUWC3F.js → chunk-3PAZJSIG.js} +2 -2
  14. package/dist-cli/chunks/{chunk-UAZ3NVKO.js → chunk-4MDLYOEI.js} +2 -2
  15. package/dist-cli/chunks/{chunk-MEYVMDZR.js → chunk-4NFGOGB7.js} +2 -2
  16. package/dist-cli/chunks/chunk-54TQ3ZGI.js +30 -0
  17. package/dist-cli/chunks/chunk-5FNPAMP4.js +125 -0
  18. package/dist-cli/chunks/{chunk-FH7DQ2HB.js → chunk-6C24WCDK.js} +2 -2
  19. package/dist-cli/chunks/{chunk-T2DHHOWE.js → chunk-74FOMZP7.js} +2 -2
  20. package/dist-cli/chunks/{chunk-K7LQB4IF.js → chunk-7GQWF7HE.js} +1 -1
  21. package/dist-cli/chunks/{chunk-RNW36VM5.js → chunk-7V3JNANH.js} +4 -4
  22. package/dist-cli/chunks/{chunk-SU5WQ4SQ.js → chunk-7YQXQSG7.js} +1 -1
  23. package/dist-cli/chunks/{chunk-DKS75M2I.js → chunk-AUBP44CY.js} +2 -2
  24. package/dist-cli/chunks/chunk-B2FB6IYX.js +4 -0
  25. package/dist-cli/chunks/{chunk-E6DZD6JD.js → chunk-B2LDO55K.js} +1 -1
  26. package/dist-cli/chunks/{chunk-XCCDO6I2.js → chunk-BVWIR4CD.js} +1 -1
  27. package/dist-cli/chunks/{chunk-UBIGBHUM.js → chunk-C462URRQ.js} +2 -2
  28. package/dist-cli/chunks/{chunk-CVDC2ER2.js → chunk-CAAADPRH.js} +2 -2
  29. package/dist-cli/chunks/{chunk-ETDZQUGI.js → chunk-CDCVWGNU.js} +3 -3
  30. package/dist-cli/chunks/{chunk-JPPGM6RZ.js → chunk-CS23DQB2.js} +2 -2
  31. package/dist-cli/chunks/chunk-DUYKBCM7.js +4 -0
  32. package/dist-cli/chunks/chunk-EISE56VD.js +6 -0
  33. package/dist-cli/chunks/{chunk-TNKZWHD2.js → chunk-EJ6BAJKX.js} +1 -1
  34. package/dist-cli/chunks/chunk-F2MS365Z.js +4 -0
  35. package/dist-cli/chunks/{chunk-I4QJVACX.js → chunk-FROCFR77.js} +1 -1
  36. package/dist-cli/chunks/{chunk-RV67EC7L.js → chunk-FUPQIX4U.js} +2 -2
  37. package/dist-cli/chunks/{chunk-U2QPKFOY.js → chunk-G2SO5FPN.js} +2 -2
  38. package/dist-cli/chunks/chunk-GG5PWLGC.js +4 -0
  39. package/dist-cli/chunks/chunk-GG6XOQ5P.js +15 -0
  40. package/dist-cli/chunks/chunk-HSGS2U7P.js +6 -0
  41. package/dist-cli/chunks/{chunk-7GZ6XF2B.js → chunk-IR3DUBRF.js} +2 -2
  42. package/dist-cli/chunks/chunk-LHUY46GE.js +4 -0
  43. package/dist-cli/chunks/{chunk-WFYQNZTT.js → chunk-MDBR4XTV.js} +3 -3
  44. package/dist-cli/chunks/{chunk-UWFAWMPE.js → chunk-MQ6QX46T.js} +2 -2
  45. package/dist-cli/chunks/chunk-N3FZRJT7.js +4 -0
  46. package/dist-cli/chunks/{chunk-QPA44BZC.js → chunk-NEARYPSC.js} +11 -11
  47. package/dist-cli/chunks/chunk-P7HBJBHH.js +4 -0
  48. package/dist-cli/chunks/{chunk-7FYIMLU4.js → chunk-PEUWZRXA.js} +17 -17
  49. package/dist-cli/chunks/{chunk-X5FTDIHQ.js → chunk-QIDFGREY.js} +1 -1
  50. package/dist-cli/chunks/{chunk-4NJIXTVJ.js → chunk-R65XMN4D.js} +1 -1
  51. package/dist-cli/chunks/{chunk-Y6FJ5FCY.js → chunk-S5Z4HKWX.js} +1 -1
  52. package/dist-cli/chunks/chunk-SUJSGGSS.js +23 -0
  53. package/dist-cli/chunks/{chunk-BX2NUJGR.js → chunk-TIOPG6QZ.js} +1 -1
  54. package/dist-cli/chunks/chunk-WJVBD2UL.js +9 -0
  55. package/dist-cli/chunks/{chunk-3WQISFAK.js → chunk-X7B5KJ6N.js} +1 -1
  56. package/dist-cli/chunks/{chunk-2RI6RC6I.js → chunk-XMVVZCIM.js} +2 -2
  57. package/dist-cli/chunks/chunk-ZIBKGZJD.js +4 -0
  58. package/dist-cli/chunks/chunk-ZPBIHFQW.js +12 -0
  59. package/dist-cli/chunks/cleanup-4RSKYPQ7.js +25 -0
  60. package/dist-cli/chunks/{cli-version-I6BOBK57.js → cli-version-VZFF7GGM.js} +2 -2
  61. package/dist-cli/chunks/compat-FABLN4UI.js +20 -0
  62. package/dist-cli/chunks/config-74J37LKC.js +13 -0
  63. package/dist-cli/chunks/control-VBK32Y7F.js +4 -0
  64. package/dist-cli/chunks/daemon-VFO7LRR4.js +4 -0
  65. package/dist-cli/chunks/{debug-VG2JOF6H.js → debug-AOYA2CLR.js} +3 -3
  66. package/dist-cli/chunks/{desktop-GH2POW6X.js → desktop-G7XWDCTM.js} +3 -3
  67. package/dist-cli/chunks/{detox-X6T265L5.js → detox-QXJ3L2ZI.js} +2 -2
  68. package/dist-cli/chunks/{device-VDOOSAXO.js → device-NZZ5ZT3Q.js} +2 -2
  69. package/dist-cli/chunks/{diagnose-6D6WNAGL.js → diagnose-5CAEJ42M.js} +2 -2
  70. package/dist-cli/chunks/disk-cleanup-GZSUZJ6Y.js +4 -0
  71. package/dist-cli/chunks/drivers-OLJCUSPO.js +4 -0
  72. package/dist-cli/chunks/{film-UZWYISAB.js → film-C25VRUNC.js} +3 -3
  73. package/dist-cli/chunks/flow-IW3WUF5D.js +4 -0
  74. package/dist-cli/chunks/help-GEVJF2DA.js +4 -0
  75. package/dist-cli/chunks/{hidden-runtime-alias-UGMJ4LJX.js → hidden-runtime-alias-NSLIK6XT.js} +2 -2
  76. package/dist-cli/chunks/home-paths-OC6JZXIM.js +4 -0
  77. package/dist-cli/chunks/inspect-KXJNI7CU.js +4 -0
  78. package/dist-cli/chunks/install-desktop-IYWPRFPB.js +4 -0
  79. package/dist-cli/chunks/{login-K43TRKUC.js → login-LB4TMM4T.js} +4 -4
  80. package/dist-cli/chunks/{logout-L43CR6M2.js → logout-33IOBD4X.js} +2 -2
  81. package/dist-cli/chunks/{maestro-7VZFYLNS.js → maestro-EEVXX536.js} +3 -3
  82. package/dist-cli/chunks/{maestro-generate-PMYHUVH4.js → maestro-generate-ICPWVXGA.js} +3 -3
  83. package/dist-cli/chunks/{mode-QQS5XFFX.js → mode-6IXOZDK3.js} +2 -2
  84. package/dist-cli/chunks/{optional-demo-registry-2V327HDG.js → optional-demo-registry-KBMC7SNI.js} +2 -2
  85. package/dist-cli/chunks/{perf-CL5P3JVP.js → perf-CTTJSAIJ.js} +2 -2
  86. package/dist-cli/chunks/{permissions-RGVCEC2B.js → permissions-BFQS4DIY.js} +2 -2
  87. package/dist-cli/chunks/{record-ASW6UFQT.js → record-MJR5ELHE.js} +3 -3
  88. package/dist-cli/chunks/report-issue-FMJRNDKH.js +29 -0
  89. package/dist-cli/chunks/runtime-PV4HH2OB.js +4 -0
  90. package/dist-cli/chunks/{screenshot-command-FEJGQMQF.js → screenshot-command-MGNASAXR.js} +3 -3
  91. package/dist-cli/chunks/{screenshots-capture-3JPZ5WMX.js → screenshots-capture-N6PANAVT.js} +2 -2
  92. package/dist-cli/chunks/serve-RS6TIO7U.js +44 -0
  93. package/dist-cli/chunks/setup-MFYGYR2F.js +32 -0
  94. package/dist-cli/chunks/{skills-T6EEX5P6.js → skills-TD35LDKK.js} +2 -2
  95. package/dist-cli/chunks/{storage-ZFUK4EQA.js → storage-VCFXDJNO.js} +2 -2
  96. package/dist-cli/chunks/store-VNSTKYBQ.js +4 -0
  97. package/dist-cli/chunks/telemetry-GPNHSYFF.js +4 -0
  98. package/dist-cli/chunks/{timeline-EIURYQVC.js → timeline-UJCZAWJV.js} +2 -2
  99. package/dist-cli/chunks/upgrade-S24NH4YG.js +7 -0
  100. package/dist-cli/chunks/upload-CCKAO2M5.js +4 -0
  101. package/dist-cli/chunks/version-IVPWT2ZQ.js +6 -0
  102. package/dist-cli/chunks/web-6BMWAWJL.js +4 -0
  103. package/dist-cli/chunks/{what-happened-L5ZN7S5G.js → what-happened-HRHB6ETL.js} +2 -2
  104. package/dist-lib/agent-daemon-client.cjs +1 -1
  105. package/dist-lib/agent-events.cjs +1 -1
  106. package/dist-lib/agent-identity.cjs +1 -1
  107. package/dist-lib/agent-sessions.cjs +1 -1
  108. package/dist-lib/attached-projects.cjs +1 -1
  109. package/dist-lib/auth/shared-session.cjs +1 -1
  110. package/dist-lib/backend-origin.cjs +1 -1
  111. package/dist-lib/beta.cjs +1 -1
  112. package/dist-lib/beta.mjs +1 -1
  113. package/dist-lib/bridge-constants.cjs +1 -1
  114. package/dist-lib/cli-constants.cjs +1 -1
  115. package/dist-lib/config.cjs +3 -2
  116. package/dist-lib/detox/index.cjs +1 -1
  117. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  118. package/dist-lib/home-paths.cjs +41 -14
  119. package/dist-lib/host/bridge-host.cjs +215 -84
  120. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  121. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  122. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  123. package/dist-lib/host/websocket-proxy.cjs +1 -1
  124. package/dist-lib/index.cjs +2 -2
  125. package/dist-lib/jump-to-source-babel.cjs +1 -1
  126. package/dist-lib/menu.cjs +2 -3
  127. package/dist-lib/menu.mjs +2 -3
  128. package/dist-lib/metro.cjs +1 -1
  129. package/dist-lib/profiles.cjs +1 -1
  130. package/dist-lib/render-mode.cjs +1 -1
  131. package/dist-lib/scripts/dev-server-scanner.cjs +2 -2
  132. package/dist-lib/sdk.cjs +1 -1
  133. package/dist-lib/sdk.mjs +1 -1
  134. package/dist-lib/skills.cjs +366 -154
  135. package/dist-lib/vite.cjs +2 -2
  136. package/package.json +2 -1
  137. package/scripts/postinstall.cjs +2 -78
  138. package/skills/contrast/SKILL.md +1 -0
  139. package/skills/sootsim-setup/SKILL.md +25 -0
  140. package/src/config.ts +4 -0
  141. package/src/disk-cleanup.ts +5 -26
  142. package/src/home-paths.ts +58 -23
  143. package/src/host/bridge-host.ts +152 -57
  144. package/src/menu.ts +0 -1
  145. package/src/runtime-assets.ts +35 -0
  146. package/src/runtime-delivery.ts +5 -8
  147. package/src/skills/builtin/compat-check.ts +32 -65
  148. package/dist-cli/chunks/app-fonts-36N7AVQ3.js +0 -4
  149. package/dist-cli/chunks/chunk-4ODQVUBH.js +0 -6
  150. package/dist-cli/chunks/chunk-4V2KYK5T.js +0 -4
  151. package/dist-cli/chunks/chunk-7GVBNEQ3.js +0 -9
  152. package/dist-cli/chunks/chunk-COEKUTWV.js +0 -4
  153. package/dist-cli/chunks/chunk-D2DU3ROC.js +0 -6
  154. package/dist-cli/chunks/chunk-DXCQP3VE.js +0 -12
  155. package/dist-cli/chunks/chunk-HT6WYHQL.js +0 -6
  156. package/dist-cli/chunks/chunk-I7AWRAXM.js +0 -125
  157. package/dist-cli/chunks/chunk-OQ7SSRYK.js +0 -4
  158. package/dist-cli/chunks/chunk-QKZVANQG.js +0 -31
  159. package/dist-cli/chunks/chunk-TGYZYSDB.js +0 -15
  160. package/dist-cli/chunks/chunk-TXURXDZN.js +0 -4
  161. package/dist-cli/chunks/chunk-ZJZYGLJ2.js +0 -4
  162. package/dist-cli/chunks/cleanup-SMCA44QC.js +0 -25
  163. package/dist-cli/chunks/compat-FCM2ZQLV.js +0 -35
  164. package/dist-cli/chunks/config-PZSANSAI.js +0 -13
  165. package/dist-cli/chunks/control-PM554MHE.js +0 -4
  166. package/dist-cli/chunks/daemon-5KXEZZLJ.js +0 -4
  167. package/dist-cli/chunks/disk-cleanup-HTOIDDKK.js +0 -4
  168. package/dist-cli/chunks/drivers-CKLBQDXG.js +0 -4
  169. package/dist-cli/chunks/flow-VOS3RKB3.js +0 -4
  170. package/dist-cli/chunks/help-LVMDERFQ.js +0 -4
  171. package/dist-cli/chunks/home-paths-3LYT47SY.js +0 -4
  172. package/dist-cli/chunks/inspect-FHCSOWGQ.js +0 -4
  173. package/dist-cli/chunks/install-desktop-KLMCVDGZ.js +0 -4
  174. package/dist-cli/chunks/runtime-GB3ZE27C.js +0 -4
  175. package/dist-cli/chunks/serve-SYXRHRQO.js +0 -44
  176. package/dist-cli/chunks/setup-QLGL6MC6.js +0 -99
  177. package/dist-cli/chunks/store-WCDTSWJC.js +0 -4
  178. package/dist-cli/chunks/telemetry-D7SL4WW7.js +0 -4
  179. package/dist-cli/chunks/upgrade-Q7ZGWMCJ.js +0 -6
  180. package/dist-cli/chunks/upload-7XEICYJX.js +0 -4
  181. package/dist-cli/chunks/version-PDG4V26R.js +0 -6
  182. package/dist-cli/chunks/web-AMW2TRWB.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.295 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.296 | (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;
@@ -10490,18 +10490,24 @@ function detectFramework(deps) {
10490
10490
  if (deps["react-native"]) return "bare";
10491
10491
  return "unknown";
10492
10492
  }
10493
- function isRNEcosystemPackage(name) {
10493
+ function isRNEcosystemPackage(name, data) {
10494
10494
  if (NON_RN_PACKAGES.has(name)) return false;
10495
- return name.startsWith("react-native") || name.startsWith("expo-") || name.startsWith("@react-native") || name.startsWith("@shopify/") || name.startsWith("@gorhom/") || name.startsWith("@callstack/") || POLYFILL_REGISTRY[name] !== void 0 || UNSUPPORTABLE.has(name);
10495
+ return name.startsWith("react-native") || name.startsWith("expo-") || name.startsWith("@react-native") || name.startsWith("@shopify/") || name.startsWith("@gorhom/") || name.startsWith("@callstack/") || data.registry[name] !== void 0 || data.unsupported.includes(name);
10496
10496
  }
10497
10497
  function scanDeps(pkg) {
10498
+ return scanDepsWithData(pkg, {
10499
+ registry: POLYFILL_REGISTRY,
10500
+ unsupported: [...UNSUPPORTABLE]
10501
+ });
10502
+ }
10503
+ function scanDepsWithData(pkg, data) {
10498
10504
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
10499
10505
  const framework = detectFramework(allDeps);
10500
10506
  const rnVersion = allDeps["react-native"] || null;
10501
10507
  const packages = [];
10502
10508
  for (const [name, version] of Object.entries(allDeps)) {
10503
- if (!isRNEcosystemPackage(name)) continue;
10504
- if (UNSUPPORTABLE.has(name)) {
10509
+ if (!isRNEcosystemPackage(name, data)) continue;
10510
+ if (data.unsupported.includes(name)) {
10505
10511
  packages.push({
10506
10512
  name,
10507
10513
  version,
@@ -10511,14 +10517,14 @@ function scanDeps(pkg) {
10511
10517
  });
10512
10518
  continue;
10513
10519
  }
10514
- const entry = POLYFILL_REGISTRY[name];
10520
+ const entry = data.registry[name];
10515
10521
  if (!entry) {
10516
10522
  packages.push({
10517
10523
  name,
10518
10524
  version,
10519
10525
  coverage: 0.3,
10520
10526
  status: "auto-stub",
10521
- note: "auto-stub (basic no-op proxy)"
10527
+ note: "not yet verified; it may work through SootSim's generic native seam"
10522
10528
  });
10523
10529
  continue;
10524
10530
  }
@@ -10539,7 +10545,7 @@ function scanDeps(pkg) {
10539
10545
  version,
10540
10546
  coverage: 0.3,
10541
10547
  status: "auto-stub",
10542
- note: "version not matched"
10548
+ note: "not yet verified for this version"
10543
10549
  });
10544
10550
  continue;
10545
10551
  }
@@ -10596,18 +10602,106 @@ var init_scan = __esm({
10596
10602
  }
10597
10603
  });
10598
10604
 
10605
+ // ../compat/src/scan-summary.ts
10606
+ function countScanResults(result) {
10607
+ const counts = {
10608
+ supported: 0,
10609
+ partial: 0,
10610
+ unsupported: 0,
10611
+ notYetVerified: 0,
10612
+ notRelevant: 0
10613
+ };
10614
+ for (const pkg of result.packages) {
10615
+ switch (pkg.status) {
10616
+ case "full":
10617
+ counts.supported++;
10618
+ break;
10619
+ case "partial":
10620
+ counts.partial++;
10621
+ break;
10622
+ case "unsupported":
10623
+ counts.unsupported++;
10624
+ break;
10625
+ case "auto-stub":
10626
+ counts.notYetVerified++;
10627
+ break;
10628
+ case "not-relevant":
10629
+ counts.notRelevant++;
10630
+ break;
10631
+ }
10632
+ }
10633
+ return counts;
10634
+ }
10635
+ function formatScanCounts(result) {
10636
+ const counts = countScanResults(result);
10637
+ return [
10638
+ `${counts.supported} supported`,
10639
+ `${counts.partial} partial`,
10640
+ `${counts.unsupported} unsupported`,
10641
+ `${counts.notYetVerified} not yet verified`
10642
+ ].join(", ");
10643
+ }
10644
+ function formatPackageStatus(pkg) {
10645
+ switch (pkg.status) {
10646
+ case "full":
10647
+ return "supported";
10648
+ case "partial":
10649
+ return "partial";
10650
+ case "unsupported":
10651
+ return "unsupported";
10652
+ case "auto-stub":
10653
+ return "not yet verified";
10654
+ case "not-relevant":
10655
+ return "not relevant at runtime";
10656
+ }
10657
+ }
10658
+ function formatDetailedScanLines(result) {
10659
+ const lines = [];
10660
+ const actionable = result.packages.filter(
10661
+ (pkg) => pkg.status === "partial" || pkg.status === "unsupported"
10662
+ );
10663
+ for (const pkg of actionable) {
10664
+ lines.push(`${formatPackageStatus(pkg)}: ${pkg.name}@${pkg.version} \u2014 ${pkg.note}`);
10665
+ }
10666
+ const unknown = result.packages.filter((pkg) => pkg.status === "auto-stub");
10667
+ if (unknown.length > 0) {
10668
+ lines.push("Not yet verified:");
10669
+ for (const pkg of unknown) {
10670
+ lines.push(` ${pkg.name}@${pkg.version} \u2014 ${pkg.note}`);
10671
+ }
10672
+ lines.push(
10673
+ "SootSim may still work. The simulator will report a concrete runtime error if it finds a gap."
10674
+ );
10675
+ }
10676
+ return lines;
10677
+ }
10678
+ var COMPAT_RESULT_CAVEAT;
10679
+ var init_scan_summary = __esm({
10680
+ "../compat/src/scan-summary.ts"() {
10681
+ "use strict";
10682
+ COMPAT_RESULT_CAVEAT = "This is a dependency-level estimate, not a guarantee that every app path works.";
10683
+ }
10684
+ });
10685
+
10599
10686
  // ../compat/src/web.ts
10600
10687
  var web_exports = {};
10601
10688
  __export(web_exports, {
10602
10689
  COMPAT_CATEGORIES: () => COMPAT_CATEGORIES,
10690
+ COMPAT_RESULT_CAVEAT: () => COMPAT_RESULT_CAVEAT,
10603
10691
  POLYFILL_REGISTRY: () => POLYFILL_REGISTRY,
10604
10692
  UNSUPPORTABLE: () => UNSUPPORTABLE,
10605
- scanDeps: () => scanDeps
10693
+ countScanResults: () => countScanResults,
10694
+ formatDetailedScanLines: () => formatDetailedScanLines,
10695
+ formatPackageStatus: () => formatPackageStatus,
10696
+ formatScanCounts: () => formatScanCounts,
10697
+ scanDeps: () => scanDeps,
10698
+ scanDepsWithData: () => scanDepsWithData
10606
10699
  });
10607
10700
  var init_web = __esm({
10608
10701
  "../compat/src/web.ts"() {
10609
10702
  "use strict";
10610
10703
  init_scan();
10704
+ init_scan_summary();
10611
10705
  init_registry();
10612
10706
  }
10613
10707
  });
@@ -10740,7 +10834,7 @@ function hasOwnKeys(value) {
10740
10834
  }
10741
10835
  function hasSootSimConfig(config) {
10742
10836
  if (!config) return false;
10743
- return hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.env) || hasOwnKeys(config.network?.remap) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
10837
+ return typeof config.runtimeVersion === "string" && config.runtimeVersion.length > 0 || hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.env) || hasOwnKeys(config.network?.remap) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
10744
10838
  }
10745
10839
  function applySootSimConfigToUrl(url, config) {
10746
10840
  const parsed = new URL(url);
@@ -11785,7 +11879,7 @@ function readDaemonLockfile() {
11785
11879
  const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
11786
11880
  const raw = buf.subarray(0, bytesRead).toString("utf8");
11787
11881
  const parsed = JSON.parse(raw);
11788
- if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number") {
11882
+ if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
11789
11883
  return parsed;
11790
11884
  }
11791
11885
  return null;
@@ -11804,7 +11898,7 @@ function readDevBridgeLockfile() {
11804
11898
  const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
11805
11899
  const raw = buf.subarray(0, bytesRead).toString("utf8");
11806
11900
  const parsed = JSON.parse(raw);
11807
- if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number")) {
11901
+ if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number") && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
11808
11902
  return parsed;
11809
11903
  }
11810
11904
  return null;
@@ -11887,42 +11981,10 @@ var init_home_paths = __esm({
11887
11981
  }
11888
11982
  });
11889
11983
 
11890
- // src/runtime-assets.ts
11891
- function findMonorepoRoot() {
11892
- if (_monorepoRootCache !== void 0) return _monorepoRootCache;
11893
- let dir = import_path.default.dirname((0, import_url.fileURLToPath)(__sootsim_import_meta_url));
11894
- for (let i = 0; i < 12 && dir !== import_path.default.dirname(dir); i++) {
11895
- if (import_fs.default.existsSync(import_path.default.join(dir, "packages/sootsim-engine/package.json")) && import_fs.default.existsSync(import_path.default.join(dir, "public/sootsim"))) {
11896
- _monorepoRootCache = dir;
11897
- return dir;
11898
- }
11899
- dir = import_path.default.dirname(dir);
11900
- }
11901
- _monorepoRootCache = null;
11902
- return null;
11903
- }
11904
- function resolveDevCheckoutRuntimeRoot() {
11905
- if (process.env.SOOTSIM_HOME && readActiveRuntime()) return null;
11906
- const root = findMonorepoRoot();
11907
- if (!root) return null;
11908
- const devBuild = import_path.default.join(root, "public/sootsim");
11909
- if (import_fs.default.existsSync(import_path.default.join(devBuild, "index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine/index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine-tenant/worker.js"))) {
11910
- return devBuild;
11911
- }
11912
- return null;
11913
- }
11914
- var import_fs, import_path, import_url, _monorepoRootCache;
11915
- var init_runtime_assets = __esm({
11916
- "src/runtime-assets.ts"() {
11917
- "use strict";
11918
- import_fs = __toESM(require("fs"), 1);
11919
- import_path = __toESM(require("path"), 1);
11920
- import_url = require("url");
11921
- init_home_paths();
11922
- }
11923
- });
11924
-
11925
11984
  // ../contrast-runtime-delivery/src/product.ts
11985
+ function isValidRuntimeVersion(value) {
11986
+ return value.length <= 100 && /^[0-9A-Za-z][0-9A-Za-z._+-]*$/.test(value);
11987
+ }
11926
11988
  function listInstalledRuntimes(product) {
11927
11989
  try {
11928
11990
  return import_node_fs3.default.readdirSync(product.runtimesDir(), { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort(compareSemver);
@@ -11985,6 +12047,7 @@ function createRuntimeDelivery(product) {
11985
12047
  return input || process.env[product.env.channel] || config.runtimeChannel || "stable";
11986
12048
  }
11987
12049
  function resolvePin() {
12050
+ if (!product.env.pin) return null;
11988
12051
  const env = process.env[product.env.pin];
11989
12052
  if (env && env.trim()) return env.trim();
11990
12053
  const config = readConfig();
@@ -12014,6 +12077,9 @@ function createRuntimeDelivery(product) {
12014
12077
  `no version specified and channel '${channel}' has no latest entry in the manifest`
12015
12078
  );
12016
12079
  }
12080
+ if (!isValidRuntimeVersion(version)) {
12081
+ throw new Error(`invalid runtime version: ${version}`);
12082
+ }
12017
12083
  const entry = manifest.versions[version];
12018
12084
  if (!entry) {
12019
12085
  throw new Error(
@@ -12029,10 +12095,11 @@ function createRuntimeDelivery(product) {
12029
12095
  const { version, channel, entry } = resolveVersion(manifest, opts);
12030
12096
  const destDir = product.runtimeDir(version);
12031
12097
  const setActive = opts.setActive !== false;
12032
- pruneLocalRetention({ protectVersions: [version] });
12098
+ const protectVersions = [version, ...opts.protectVersions ?? []];
12099
+ pruneLocalRetention({ protectVersions });
12033
12100
  if (!opts.force && import_node_fs4.default.existsSync(import_node_path4.default.join(destDir, product.installedMarker))) {
12034
12101
  if (setActive) product.writeActiveRuntime(version);
12035
- pruneLocalRetention({ protectVersions: [version] });
12102
+ pruneLocalRetention({ protectVersions });
12036
12103
  return {
12037
12104
  version,
12038
12105
  channel,
@@ -12044,29 +12111,32 @@ function createRuntimeDelivery(product) {
12044
12111
  };
12045
12112
  }
12046
12113
  const tarUrl = entry.tarball || tarballUrl(version, cdnOrigin);
12047
- const tarCachePath = import_node_path4.default.join(
12048
- product.cacheDir(),
12114
+ const downloadDir = import_node_fs4.default.mkdtempSync(
12115
+ import_node_path4.default.join(import_node_os2.default.tmpdir(), `${product.id}-runtime-download-`)
12116
+ );
12117
+ const runtimeArchivePath = import_node_path4.default.join(
12118
+ downloadDir,
12049
12119
  runtimeTarballCacheName(product, version)
12050
12120
  );
12051
- process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
12052
- `);
12053
- await downloadToFile(tarUrl, tarCachePath);
12054
- process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
12055
- `);
12056
12121
  const tmpDir = import_node_path4.default.join(
12057
12122
  import_node_path4.default.dirname(destDir),
12058
12123
  `.installing-${version}-${process.pid}`
12059
12124
  );
12060
12125
  import_node_fs4.default.rmSync(tmpDir, { recursive: true, force: true });
12061
12126
  try {
12062
- const actualSha = await sha256File(tarCachePath);
12127
+ process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
12128
+ `);
12129
+ await downloadToFile(tarUrl, runtimeArchivePath);
12130
+ process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
12131
+ `);
12132
+ const actualSha = await sha256File(runtimeArchivePath);
12063
12133
  if (actualSha !== entry.sha256) {
12064
12134
  throw new Error(
12065
12135
  `sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
12066
12136
  );
12067
12137
  }
12068
12138
  import_node_fs4.default.mkdirSync(tmpDir, { recursive: true });
12069
- await extractTarball(tarCachePath, tmpDir);
12139
+ await extractTarball(runtimeArchivePath, tmpDir);
12070
12140
  if (!import_node_fs4.default.existsSync(import_node_path4.default.join(tmpDir, product.installedMarker))) {
12071
12141
  throw new Error(
12072
12142
  `extracted tarball for runtime ${version} is missing ${product.installedMarker}`
@@ -12078,10 +12148,10 @@ function createRuntimeDelivery(product) {
12078
12148
  import_node_fs4.default.rmSync(tmpDir, { recursive: true, force: true });
12079
12149
  throw err;
12080
12150
  } finally {
12081
- import_node_fs4.default.rmSync(tarCachePath, { force: true });
12151
+ import_node_fs4.default.rmSync(downloadDir, { recursive: true, force: true });
12082
12152
  }
12083
12153
  if (setActive) product.writeActiveRuntime(version);
12084
- pruneLocalRetention({ protectVersions: [version] });
12154
+ pruneLocalRetention({ protectVersions });
12085
12155
  return {
12086
12156
  version,
12087
12157
  channel,
@@ -12094,7 +12164,7 @@ function createRuntimeDelivery(product) {
12094
12164
  }
12095
12165
  async function updateToLatest(opts = {}) {
12096
12166
  product.ensureHome();
12097
- pruneLocalRetention();
12167
+ pruneLocalRetention({ protectVersions: opts.protectVersions });
12098
12168
  const cdnOrigin = resolveCdnOrigin(opts.cdnOrigin);
12099
12169
  const channel = resolveChannel(opts.channel);
12100
12170
  const pin = resolvePin();
@@ -12116,7 +12186,8 @@ function createRuntimeDelivery(product) {
12116
12186
  version: pin,
12117
12187
  channel,
12118
12188
  cdnOrigin,
12119
- setActive: false
12189
+ setActive: false,
12190
+ protectVersions: opts.protectVersions
12120
12191
  });
12121
12192
  return {
12122
12193
  checked: true,
@@ -12164,7 +12235,8 @@ function createRuntimeDelivery(product) {
12164
12235
  version: latestVersion,
12165
12236
  channel,
12166
12237
  cdnOrigin,
12167
- setActive: false
12238
+ setActive: false,
12239
+ protectVersions: opts.protectVersions
12168
12240
  });
12169
12241
  return {
12170
12242
  checked: true,
@@ -12306,13 +12378,14 @@ function extractTarball(tarPath, destDir) {
12306
12378
  });
12307
12379
  });
12308
12380
  }
12309
- var import_node_child_process2, import_node_crypto, import_node_fs4, import_node_path4, import_node_stream, import_promises2, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
12381
+ var import_node_child_process2, import_node_crypto, import_node_fs4, import_node_os2, import_node_path4, import_node_stream, import_promises2, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
12310
12382
  var init_src = __esm({
12311
12383
  "../contrast-runtime-delivery/src/index.ts"() {
12312
12384
  "use strict";
12313
12385
  import_node_child_process2 = require("node:child_process");
12314
12386
  import_node_crypto = __toESM(require("node:crypto"), 1);
12315
12387
  import_node_fs4 = __toESM(require("node:fs"), 1);
12388
+ import_node_os2 = __toESM(require("node:os"), 1);
12316
12389
  import_node_path4 = __toESM(require("node:path"), 1);
12317
12390
  import_node_stream = require("node:stream");
12318
12391
  import_promises2 = require("node:stream/promises");
@@ -12322,6 +12395,63 @@ var init_src = __esm({
12322
12395
  }
12323
12396
  });
12324
12397
 
12398
+ // src/runtime-assets.ts
12399
+ function findMonorepoRoot() {
12400
+ if (_monorepoRootCache !== void 0) return _monorepoRootCache;
12401
+ let dir = import_path.default.dirname((0, import_url.fileURLToPath)(__sootsim_import_meta_url));
12402
+ for (let i = 0; i < 12 && dir !== import_path.default.dirname(dir); i++) {
12403
+ if (import_fs.default.existsSync(import_path.default.join(dir, "packages/sootsim-engine/package.json")) && import_fs.default.existsSync(import_path.default.join(dir, "public/sootsim"))) {
12404
+ _monorepoRootCache = dir;
12405
+ return dir;
12406
+ }
12407
+ dir = import_path.default.dirname(dir);
12408
+ }
12409
+ _monorepoRootCache = null;
12410
+ return null;
12411
+ }
12412
+ function resolveDevCheckoutRuntimeRoot() {
12413
+ if (process.env.SOOTSIM_HOME && readActiveRuntime()) return null;
12414
+ const root = findMonorepoRoot();
12415
+ if (!root) return null;
12416
+ const devBuild = import_path.default.join(root, "public/sootsim");
12417
+ if (import_fs.default.existsSync(import_path.default.join(devBuild, "index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine/index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine-tenant/worker.js"))) {
12418
+ return devBuild;
12419
+ }
12420
+ return null;
12421
+ }
12422
+ function runtimeVersionFromHostname(hostname) {
12423
+ const suffix = ".runtime.localhost";
12424
+ if (!hostname.toLowerCase().endsWith(suffix)) return null;
12425
+ const labels = hostname.slice(0, -suffix.length).split(".");
12426
+ if (labels.length === 0 || labels.some((label) => !/^[0-9a-f]{1,60}$/i.test(label))) {
12427
+ return null;
12428
+ }
12429
+ const encoded = labels.join("");
12430
+ if (encoded.length % 2 !== 0) return null;
12431
+ try {
12432
+ const version = Buffer.from(encoded, "hex").toString("utf8");
12433
+ return isValidRuntimeVersion(version) ? version : null;
12434
+ } catch {
12435
+ return null;
12436
+ }
12437
+ }
12438
+ function resolveInstalledRuntimeRoot(version) {
12439
+ const dir = runtimeDir(version);
12440
+ if (!import_fs.default.existsSync(import_path.default.join(dir, "index.html"))) return null;
12441
+ return dir;
12442
+ }
12443
+ var import_fs, import_path, import_url, _monorepoRootCache;
12444
+ var init_runtime_assets = __esm({
12445
+ "src/runtime-assets.ts"() {
12446
+ "use strict";
12447
+ import_fs = __toESM(require("fs"), 1);
12448
+ import_path = __toESM(require("path"), 1);
12449
+ import_url = require("url");
12450
+ init_src();
12451
+ init_home_paths();
12452
+ }
12453
+ });
12454
+
12325
12455
  // src/runtime-delivery.ts
12326
12456
  var DEFAULT_RUNTIME_CDN_ORIGIN, SOOTSIM_RUNTIME_PRODUCT, sootsimRuntime;
12327
12457
  var init_runtime_delivery = __esm({
@@ -12341,8 +12471,7 @@ var init_runtime_delivery = __esm({
12341
12471
  installedMarker: "index.html",
12342
12472
  env: {
12343
12473
  cdnOrigin: "SOOTSIM_CDN_ORIGIN",
12344
- channel: "SOOTSIM_RUNTIME_CHANNEL",
12345
- pin: "SOOTSIM_RUNTIME_VERSION"
12474
+ channel: "SOOTSIM_RUNTIME_CHANNEL"
12346
12475
  },
12347
12476
  ensureHome: ensureSootsimHome,
12348
12477
  configFile: configFilePath,
@@ -14735,6 +14864,7 @@ var init_bridge_host = __esm({
14735
14864
  recentActions: []
14736
14865
  };
14737
14866
  this.sims.set(sim.id, sim);
14867
+ this.writeConnectedRuntimeSnapshot();
14738
14868
  if (this.shouldPromoteSim(sim)) {
14739
14869
  this.primarySimId = sim.id;
14740
14870
  }
@@ -14826,6 +14956,7 @@ var init_bridge_host = __esm({
14826
14956
  sim = null;
14827
14957
  role = "cli";
14828
14958
  this.cliIdentityKeyBySocket.set(ws, `ws-${this.nextCliFallbackId++}`);
14959
+ this.writeConnectedRuntimeSnapshot();
14829
14960
  this.broadcastSimAssignments();
14830
14961
  this.broadcastSimClientStates();
14831
14962
  }
@@ -14839,6 +14970,7 @@ var init_bridge_host = __esm({
14839
14970
  sim.url = registration.url;
14840
14971
  sim.title = registration.title;
14841
14972
  sim.userAgent = registration.userAgent;
14973
+ this.writeConnectedRuntimeSnapshot();
14842
14974
  if (typeof registration.kind === "string" && registration.kind.trim()) {
14843
14975
  sim.kind = registration.kind.trim();
14844
14976
  }
@@ -14863,11 +14995,14 @@ var init_bridge_host = __esm({
14863
14995
  return;
14864
14996
  }
14865
14997
  if (msg.type === "bridge:write-shared-config") {
14866
- const patch = msg.patch && typeof msg.patch === "object" ? msg.patch : null;
14867
- if (!patch) return;
14868
- let next;
14998
+ if (!msg.patch || typeof msg.patch !== "object" || Array.isArray(msg.patch)) {
14999
+ return;
15000
+ }
15001
+ const patch = Object.fromEntries(
15002
+ Object.entries(msg.patch)
15003
+ );
14869
15004
  try {
14870
- next = writeSharedConfig(patch);
15005
+ this.writeAndBroadcastSharedConfig(patch);
14871
15006
  } catch (err) {
14872
15007
  process.stderr.write(
14873
15008
  `sootsim: bridge:write-shared-config failed: ${err instanceof Error ? err.message : String(err)}
@@ -14875,17 +15010,6 @@ var init_bridge_host = __esm({
14875
15010
  );
14876
15011
  return;
14877
15012
  }
14878
- const payload = JSON.stringify({
14879
- type: "bridge:shared-config-changed",
14880
- config: next
14881
- });
14882
- for (const peer of this.sims.values()) {
14883
- if (peer.ws.readyState !== import_ws2.WebSocket.OPEN) continue;
14884
- try {
14885
- peer.ws.send(payload);
14886
- } catch {
14887
- }
14888
- }
14889
15013
  return;
14890
15014
  }
14891
15015
  if (msg.type === "bridge:open-path") {
@@ -15200,6 +15324,18 @@ var init_bridge_host = __esm({
15200
15324
  void this.agentHost.seedOnBoot();
15201
15325
  }
15202
15326
  bootstrapping = true;
15327
+ connectedRuntimeVersions() {
15328
+ const versions = /* @__PURE__ */ new Set();
15329
+ for (const sim of this.sims.values()) {
15330
+ try {
15331
+ const url = new URL(sim.url || sim.origin || "http://localhost");
15332
+ const version = runtimeVersionFromHostname(url.hostname);
15333
+ if (version) versions.add(version);
15334
+ } catch {
15335
+ }
15336
+ }
15337
+ return [...versions].sort();
15338
+ }
15203
15339
  buildLockfileSnapshot() {
15204
15340
  return {
15205
15341
  schema: 1,
@@ -15209,6 +15345,7 @@ var init_bridge_host = __esm({
15209
15345
  runtimePort: this.effectivePort,
15210
15346
  activeRuntime: this.activeRuntimeVersion,
15211
15347
  activeRuntimeDir: this.activeRuntimeDirPath,
15348
+ servedRuntimes: this.connectedRuntimeVersions(),
15212
15349
  startedAt: this.startedAt,
15213
15350
  heartbeatAt: Date.now(),
15214
15351
  bootstrapping: this.bootstrapping
@@ -15226,7 +15363,8 @@ var init_bridge_host = __esm({
15226
15363
  cwd: process.cwd(),
15227
15364
  startedAt: this.startedAt,
15228
15365
  heartbeatAt: Date.now(),
15229
- source: "vite-dev"
15366
+ source: "vite-dev",
15367
+ servedRuntimes: this.connectedRuntimeVersions()
15230
15368
  };
15231
15369
  }
15232
15370
  writeLockfileSnapshot() {
@@ -15235,6 +15373,20 @@ var init_bridge_host = __esm({
15235
15373
  writeDevLockfileSnapshot() {
15236
15374
  writeDevBridgeLockfile(this.buildDevLockfileSnapshot());
15237
15375
  }
15376
+ writeConnectedRuntimeSnapshot() {
15377
+ if (this.shouldWriteLockfile && this.httpServer) {
15378
+ try {
15379
+ this.writeLockfileSnapshot();
15380
+ } catch {
15381
+ }
15382
+ }
15383
+ if (this.shouldWriteDevLockfile && this.httpServer) {
15384
+ try {
15385
+ this.writeDevLockfileSnapshot();
15386
+ } catch {
15387
+ }
15388
+ }
15389
+ }
15238
15390
  refreshActiveRuntime() {
15239
15391
  this.activeRuntimeVersion = readActiveRuntime();
15240
15392
  this.activeRuntimeDirPath = resolveDevCheckoutRuntimeRoot() ?? activeRuntimeDir();
@@ -15286,6 +15438,7 @@ var init_bridge_host = __esm({
15286
15438
  }
15287
15439
  startRuntimeUpdater() {
15288
15440
  if (!this.shouldWriteLockfile || this.runtimeUpdateTimer) return;
15441
+ if (import_fs3.default.existsSync(import_path3.default.join(sootsimHomeDir(), "runtime-update-disabled"))) return;
15289
15442
  if (process.env.SOOTSIM_HOME && isSootsimDevCheckout() && !readActiveRuntime()) {
15290
15443
  return;
15291
15444
  }
@@ -15303,7 +15456,9 @@ var init_bridge_host = __esm({
15303
15456
  if (reason === "startup") {
15304
15457
  process.stderr.write("sootsim: checking for runtime updates\u2026\n");
15305
15458
  }
15306
- const result = await sootsimRuntime.updateToLatest();
15459
+ const result = await sootsimRuntime.updateToLatest({
15460
+ protectVersions: this.connectedRuntimeVersions()
15461
+ });
15307
15462
  if (!result.updated || !result.latestVersion) {
15308
15463
  if (reason === "startup") {
15309
15464
  process.stderr.write(
@@ -15364,6 +15519,11 @@ var init_bridge_host = __esm({
15364
15519
  runtimeDir: this.activeRuntimeDirPath
15365
15520
  });
15366
15521
  for (const sim of this.sims.values()) {
15522
+ try {
15523
+ const simUrl = new URL(sim.url || sim.origin || "http://localhost");
15524
+ if (runtimeVersionFromHostname(simUrl.hostname)) continue;
15525
+ } catch {
15526
+ }
15367
15527
  if (sim.ws.readyState === import_ws2.WebSocket.OPEN) {
15368
15528
  try {
15369
15529
  sim.ws.send(payload);
@@ -15407,12 +15567,77 @@ var init_bridge_host = __esm({
15407
15567
  return;
15408
15568
  }
15409
15569
  const method = (req.method || "GET").toUpperCase();
15570
+ let url;
15571
+ try {
15572
+ url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
15573
+ } catch {
15574
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
15575
+ res.end("invalid request URL or host");
15576
+ return;
15577
+ }
15578
+ if (url.pathname === "/__sootsim/shared-config") {
15579
+ res.setHeader("Access-Control-Allow-Origin", "*");
15580
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
15581
+ res.setHeader("Cache-Control", "no-store");
15582
+ if (method === "OPTIONS") {
15583
+ res.writeHead(204, { Allow: "GET, HEAD, POST, OPTIONS" });
15584
+ res.end();
15585
+ return;
15586
+ }
15587
+ if (method === "GET" || method === "HEAD") {
15588
+ let body = "{}";
15589
+ try {
15590
+ body = JSON.stringify(readSharedConfig());
15591
+ } catch {
15592
+ }
15593
+ res.writeHead(200, { "Content-Type": "application/json" });
15594
+ if (method === "HEAD") res.end();
15595
+ else res.end(body);
15596
+ return;
15597
+ }
15598
+ if (method === "POST") {
15599
+ void (async () => {
15600
+ try {
15601
+ const chunks = [];
15602
+ let size = 0;
15603
+ for await (const chunk of req) {
15604
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
15605
+ size += buffer.byteLength;
15606
+ if (size > 8 * 1024 * 1024) {
15607
+ res.writeHead(413, { "Content-Type": "text/plain; charset=utf-8" });
15608
+ res.end("shared config patch is too large");
15609
+ return;
15610
+ }
15611
+ chunks.push(buffer);
15612
+ }
15613
+ const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
15614
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15615
+ throw new Error("shared config patch must be an object");
15616
+ }
15617
+ const patch = Object.fromEntries(
15618
+ Object.entries(parsed)
15619
+ );
15620
+ const next = this.writeAndBroadcastSharedConfig(patch);
15621
+ res.writeHead(200, { "Content-Type": "application/json" });
15622
+ res.end(JSON.stringify(next));
15623
+ } catch (error) {
15624
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
15625
+ res.end(
15626
+ `invalid shared config patch: ${error instanceof Error ? error.message : String(error)}`
15627
+ );
15628
+ }
15629
+ })();
15630
+ return;
15631
+ }
15632
+ res.writeHead(405, { Allow: "GET, HEAD, POST, OPTIONS" });
15633
+ res.end("method not allowed");
15634
+ return;
15635
+ }
15410
15636
  if (method !== "GET" && method !== "HEAD") {
15411
15637
  res.writeHead(405, { Allow: "GET, HEAD" });
15412
15638
  res.end("method not allowed");
15413
15639
  return;
15414
15640
  }
15415
- const url = new URL(req.url || "/", "http://localhost");
15416
15641
  if (url.pathname === "/__bundle-proxy") {
15417
15642
  const target = url.searchParams.get("url");
15418
15643
  if (!target) {
@@ -15489,30 +15714,13 @@ var init_bridge_host = __esm({
15489
15714
  );
15490
15715
  return;
15491
15716
  }
15492
- if (url.pathname === "/__sootsim/shared-config") {
15493
- res.setHeader("Access-Control-Allow-Origin", "*");
15494
- res.setHeader("Cache-Control", "no-store");
15495
- if (method === "GET" || method === "HEAD") {
15496
- let body = "{}";
15497
- try {
15498
- body = JSON.stringify(readSharedConfig());
15499
- } catch {
15500
- }
15501
- res.writeHead(200, { "Content-Type": "application/json" });
15502
- if (method === "HEAD") res.end();
15503
- else res.end(body);
15504
- return;
15505
- }
15506
- res.writeHead(405, { Allow: "GET, HEAD" });
15507
- res.end("method not allowed (use the bridge over WS for writes)");
15508
- return;
15509
- }
15510
- this.refreshActiveRuntime();
15511
- const baseDir = this.activeRuntimeDirPath;
15717
+ const requestedRuntime = runtimeVersionFromHostname(url.hostname);
15718
+ if (!requestedRuntime) this.refreshActiveRuntime();
15719
+ const baseDir = requestedRuntime ? resolveInstalledRuntimeRoot(requestedRuntime) : this.activeRuntimeDirPath;
15512
15720
  if (!baseDir) {
15513
15721
  res.writeHead(503, { "Content-Type": "text/plain; charset=utf-8" });
15514
15722
  res.end(
15515
- "sootsim: no active runtime installed. run `sootsim runtime install` to fetch one."
15723
+ requestedRuntime ? `sootsim: runtime ${requestedRuntime} is not installed. run \`sootsim open\` from that app again.` : "sootsim: no active runtime installed. run `sootsim runtime install` to fetch one."
15516
15724
  );
15517
15725
  return;
15518
15726
  }
@@ -16119,6 +16327,21 @@ var init_bridge_host = __esm({
16119
16327
  );
16120
16328
  }
16121
16329
  }
16330
+ writeAndBroadcastSharedConfig(patch) {
16331
+ const next = writeSharedConfig(patch);
16332
+ const payload = JSON.stringify({
16333
+ type: "bridge:shared-config-changed",
16334
+ config: next
16335
+ });
16336
+ for (const peer of this.sims.values()) {
16337
+ if (peer.ws.readyState !== import_ws2.WebSocket.OPEN) continue;
16338
+ try {
16339
+ peer.ws.send(payload);
16340
+ } catch {
16341
+ }
16342
+ }
16343
+ return next;
16344
+ }
16122
16345
  broadcastSimClientStates() {
16123
16346
  for (const sim of this.sims.values()) {
16124
16347
  if (sim.ws.readyState !== import_ws2.WebSocket.OPEN) continue;
@@ -16257,6 +16480,7 @@ var init_bridge_host = __esm({
16257
16480
  expiresAt: Date.now() + _SootSimBridgeHost.SIM_RECONNECT_TTL_MS
16258
16481
  });
16259
16482
  this.sims.delete(sim.id);
16483
+ this.writeConnectedRuntimeSnapshot();
16260
16484
  }
16261
16485
  getRestorableSimState(simId) {
16262
16486
  const snapshot = this.restorableSims.get(simId);
@@ -18383,6 +18607,13 @@ var init_auth = __esm({
18383
18607
  }
18384
18608
  });
18385
18609
 
18610
+ // cli/app-project.ts
18611
+ var init_app_project = __esm({
18612
+ "cli/app-project.ts"() {
18613
+ "use strict";
18614
+ }
18615
+ });
18616
+
18386
18617
  // cli/app-fonts.ts
18387
18618
  var import_node_module, import_config_plugin_fonts, require2;
18388
18619
  var init_app_fonts = __esm({
@@ -18390,6 +18621,7 @@ var init_app_fonts = __esm({
18390
18621
  "use strict";
18391
18622
  import_node_module = require("node:module");
18392
18623
  import_config_plugin_fonts = require("sootsim-engine/engine/config-plugin-fonts");
18624
+ init_app_project();
18393
18625
  require2 = (0, import_node_module.createRequire)(__sootsim_import_meta_url);
18394
18626
  }
18395
18627
  });
@@ -18401,6 +18633,7 @@ var init_app_splash = __esm({
18401
18633
  "use strict";
18402
18634
  import_node_module2 = require("node:module");
18403
18635
  import_config_plugin_splash = require("sootsim-engine/engine/config-plugin-splash");
18636
+ init_app_project();
18404
18637
  require3 = (0, import_node_module2.createRequire)(__sootsim_import_meta_url);
18405
18638
  }
18406
18639
  });
@@ -20593,11 +20826,6 @@ var skill = {
20593
20826
  };
20594
20827
 
20595
20828
  // src/skills/builtin/compat-check.ts
20596
- function getCompatStatus(entry) {
20597
- if (entry.stubType === "works") return "full";
20598
- if (entry.stubType === "build-only") return "full";
20599
- return entry.versions.some((version) => version.coverage >= 1) ? "full" : "auto-stub";
20600
- }
20601
20829
  var skill2 = {
20602
20830
  name: "compat-check",
20603
20831
  description: "Check package compatibility with sootsim",
@@ -20615,62 +20843,46 @@ var skill2 = {
20615
20843
  }
20616
20844
  },
20617
20845
  async execute(params, context) {
20618
- const fs11 = await import("fs");
20619
- const path11 = await import("path");
20620
- const { POLYFILL_REGISTRY: POLYFILL_REGISTRY2 } = await Promise.resolve().then(() => (init_web(), web_exports));
20621
- if (params.packageName) {
20622
- const entry = POLYFILL_REGISTRY2[params.packageName];
20623
- if (!entry) {
20624
- return {
20625
- success: true,
20626
- message: `${params.packageName}: not in registry (may be auto-stubbed if native)`,
20627
- data: { package: params.packageName, status: "unknown" }
20628
- };
20629
- }
20630
- const status = getCompatStatus(entry);
20631
- return {
20632
- success: true,
20633
- message: `${params.packageName}: ${status}`,
20634
- data: { package: params.packageName, status, ...entry }
20635
- };
20636
- }
20637
- const pkgPath = path11.join(context.projectDir, "package.json");
20638
- if (!fs11.existsSync(pkgPath)) {
20846
+ const fs11 = await import("node:fs");
20847
+ const path11 = await import("node:path");
20848
+ const { COMPAT_RESULT_CAVEAT: COMPAT_RESULT_CAVEAT2, formatPackageStatus: formatPackageStatus2, formatScanCounts: formatScanCounts2, scanDeps: scanDeps2 } = await Promise.resolve().then(() => (init_web(), web_exports));
20849
+ const packagePath = path11.join(context.projectDir, "package.json");
20850
+ if (!fs11.existsSync(packagePath)) {
20639
20851
  return { success: false, message: "No package.json found" };
20640
20852
  }
20641
- const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf8"));
20642
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
20643
- const results = {
20644
- full: [],
20645
- partial: [],
20646
- "auto-stub": [],
20647
- unsupported: [],
20648
- unknown: []
20649
- };
20650
- for (const dep of Object.keys(allDeps)) {
20651
- const entry = POLYFILL_REGISTRY2[dep];
20652
- if (entry) {
20653
- const status = getCompatStatus(entry);
20654
- if (results[status]) results[status].push(dep);
20655
- } else if (isNative(dep)) {
20656
- results.unknown.push(dep);
20657
- }
20853
+ const parsed = JSON.parse(fs11.readFileSync(packagePath, "utf8"));
20854
+ if (!parsed || typeof parsed !== "object") {
20855
+ return { success: false, message: "package.json is not an object" };
20658
20856
  }
20857
+ const dependencies = readDependencies(parsed);
20858
+ const scan = params.packageName ? scanDeps2({
20859
+ dependencies: {
20860
+ [params.packageName]: dependencies[params.packageName] ?? "*"
20861
+ }
20862
+ }) : scanDeps2(parsed);
20863
+ const packageResult = params.packageName ? scan.packages[0] : void 0;
20864
+ const message = packageResult ? `${params.packageName}: ${formatPackageStatus2(packageResult)}. ${COMPAT_RESULT_CAVEAT2}` : `${formatScanCounts2(scan)}. ${COMPAT_RESULT_CAVEAT2}`;
20659
20865
  const reportPath = path11.join(context.outputDir, "compat-report.json");
20660
20866
  fs11.mkdirSync(path11.dirname(reportPath), { recursive: true });
20661
- fs11.writeFileSync(reportPath, JSON.stringify(results, null, 2));
20867
+ fs11.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
20868
+ `);
20662
20869
  return {
20663
- success: results.unsupported.length === 0,
20664
- message: `${results.full.length} full, ${results.partial.length} partial, ${results.unsupported.length} unsupported, ${results.unknown.length} unknown`,
20870
+ success: true,
20871
+ message,
20665
20872
  artifacts: [{ type: "report", name: "compat-report", path: reportPath }],
20666
- data: results
20873
+ data: scan
20667
20874
  };
20668
20875
  }
20669
20876
  }
20670
20877
  ]
20671
20878
  };
20672
- function isNative(name) {
20673
- return name.startsWith("expo-") || name.startsWith("react-native-") || name.startsWith("@react-native/") || name.startsWith("@react-native-community/") || name.startsWith("@expo/") || name === "expo";
20879
+ function readDependencies(value) {
20880
+ const dependencies = Reflect.get(value, "dependencies");
20881
+ const devDependencies = Reflect.get(value, "devDependencies");
20882
+ return {
20883
+ ...dependencies && typeof dependencies === "object" ? dependencies : {},
20884
+ ...devDependencies && typeof devDependencies === "object" ? devDependencies : {}
20885
+ };
20674
20886
  }
20675
20887
 
20676
20888
  // src/skills/builtin/maestro-test.ts