sootsim 0.1.84 → 0.1.86

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 (153) hide show
  1. package/detox/element-types.ts +36 -0
  2. package/detox/expectations.ts +1 -1
  3. package/detox/index.ts +2 -35
  4. package/dist-cli/bin.js +3 -3
  5. package/dist-cli/chunks/{agent-2CWD6W6P.js → agent-S3WLX5Z4.js} +2 -2
  6. package/dist-cli/chunks/{agent-wrapper-5W3LOX6S.js → agent-wrapper-PFPEQTPG.js} +2 -2
  7. package/dist-cli/chunks/{assert-ZOMAMKRT.js → assert-RQD66YGE.js} +2 -2
  8. package/dist-cli/chunks/auto-bootstrap-PWF7OYCV.js +2 -0
  9. package/dist-cli/chunks/beta-2HH7F2DQ.js +2 -0
  10. package/dist-cli/chunks/{chunk-D4HUVLZR.js → chunk-2IYMBWHL.js} +1 -1
  11. package/dist-cli/chunks/{chunk-DUUSJDES.js → chunk-3GCSX5H5.js} +1 -1
  12. package/dist-cli/chunks/{chunk-EQCKGC4B.js → chunk-3TNIXR6J.js} +1 -1
  13. package/dist-cli/chunks/{chunk-4OWVPRZV.js → chunk-535UNERF.js} +2 -2
  14. package/dist-cli/chunks/{chunk-4K7BH2D4.js → chunk-5DFVKWYQ.js} +3 -3
  15. package/dist-cli/chunks/{chunk-AJVTY6KY.js → chunk-5NW6W7YF.js} +1 -1
  16. package/dist-cli/chunks/{chunk-XQ2OBHBE.js → chunk-5ZYANOOI.js} +2 -2
  17. package/dist-cli/chunks/{chunk-ELJLF4SG.js → chunk-6A7IWFXR.js} +63 -62
  18. package/dist-cli/chunks/{chunk-73UZXB4B.js → chunk-6LG7WJMD.js} +2 -2
  19. package/dist-cli/chunks/{chunk-OOOR7NT2.js → chunk-7YZHI7V6.js} +1 -1
  20. package/dist-cli/chunks/{chunk-TL7SIZ7S.js → chunk-CMAANHYQ.js} +1 -1
  21. package/dist-cli/chunks/{chunk-7NWNTUJF.js → chunk-CQAQTU5K.js} +1 -1
  22. package/dist-cli/chunks/{chunk-C3DPQZ4J.js → chunk-CSJS4MRN.js} +2 -2
  23. package/dist-cli/chunks/{chunk-QMSJR5R2.js → chunk-DKW7Q4F3.js} +2 -2
  24. package/dist-cli/chunks/{chunk-EQ7TFQ2F.js → chunk-EDWDFOPL.js} +1 -1
  25. package/dist-cli/chunks/{chunk-WNVNU2OW.js → chunk-FF5KD3BS.js} +2 -2
  26. package/dist-cli/chunks/{chunk-PPKKA5VW.js → chunk-FO52BFW4.js} +2 -2
  27. package/dist-cli/chunks/chunk-I3JMONYJ.js +2 -0
  28. package/dist-cli/chunks/{chunk-7YHDJLO2.js → chunk-JB467MUR.js} +45 -45
  29. package/dist-cli/chunks/{chunk-HYPJW65U.js → chunk-JBYW57OA.js} +2 -2
  30. package/dist-cli/chunks/{chunk-SQX5CAYG.js → chunk-JITAVV2G.js} +1 -1
  31. package/dist-cli/chunks/{chunk-V2GQ4WXJ.js → chunk-JMILXXI4.js} +2 -2
  32. package/dist-cli/chunks/chunk-KGYC4SZA.js +2 -0
  33. package/dist-cli/chunks/{chunk-BKBL6K2G.js → chunk-KQYOS5SM.js} +1 -1
  34. package/dist-cli/chunks/{chunk-VH7F45CN.js → chunk-M6GOCS27.js} +1 -1
  35. package/dist-cli/chunks/{chunk-3HXQ7MJK.js → chunk-NBBH2PVW.js} +2 -2
  36. package/dist-cli/chunks/chunk-NIRJVBXJ.js +1 -0
  37. package/dist-cli/chunks/{chunk-SQZAC7C4.js → chunk-NLH7FNSG.js} +1 -1
  38. package/dist-cli/chunks/{chunk-RIXUH3NK.js → chunk-OBHJKTWA.js} +2 -2
  39. package/dist-cli/chunks/{chunk-KU6MSPAH.js → chunk-OCTDP37S.js} +2 -2
  40. package/dist-cli/chunks/{chunk-SFGUPL2X.js → chunk-P7IKKZTG.js} +2 -2
  41. package/dist-cli/chunks/{chunk-5XCXOLG2.js → chunk-PVMX5UNR.js} +2 -2
  42. package/dist-cli/chunks/chunk-QUYC7CVV.js +1 -0
  43. package/dist-cli/chunks/{chunk-YCIA4BHJ.js → chunk-STHMWSVN.js} +2 -2
  44. package/dist-cli/chunks/{chunk-AWSQUOAS.js → chunk-TSNBQ4ZV.js} +10 -10
  45. package/dist-cli/chunks/{chunk-BCBNVJVG.js → chunk-UWSP2AT7.js} +1 -1
  46. package/dist-cli/chunks/{chunk-RF4R2U46.js → chunk-V7CFSKMC.js} +2 -2
  47. package/dist-cli/chunks/{chunk-TK3OJSEO.js → chunk-VFGAEMSI.js} +2 -2
  48. package/dist-cli/chunks/{chunk-4OPRODFA.js → chunk-VUYCS6QI.js} +2 -2
  49. package/dist-cli/chunks/chunk-XB4QIINM.js +24 -0
  50. package/dist-cli/chunks/{chunk-P7WDNKOS.js → chunk-XFJYEKYK.js} +3 -3
  51. package/dist-cli/chunks/{chunk-SV7FOGJ3.js → chunk-XZE53P4L.js} +2 -2
  52. package/dist-cli/chunks/chunk-Y7BXSTVX.js +1 -0
  53. package/dist-cli/chunks/cli-version-TGWWTYQX.js +2 -0
  54. package/dist-cli/chunks/{compat-FWSEEGEH.js → compat-I2U3P4KP.js} +3 -3
  55. package/dist-cli/chunks/{config-CYI2WAGP.js → config-S73CCGP5.js} +2 -2
  56. package/dist-cli/chunks/{control-UXY7YQVX.js → control-QR6MY7RA.js} +2 -2
  57. package/dist-cli/chunks/{cpu-profile-IKAE3KTY.js → cpu-profile-RFYCTVAF.js} +2 -2
  58. package/dist-cli/chunks/{daemon-ZUMF53YB.js → daemon-D5MV2B22.js} +2 -2
  59. package/dist-cli/chunks/{debug-P6KULKKS.js → debug-ZYEI75AG.js} +3 -3
  60. package/dist-cli/chunks/{detox-SPWAZCYG.js → detox-J5IH52RV.js} +2 -2
  61. package/dist-cli/chunks/{device-JWEPK6I2.js → device-NOBLSUOD.js} +2 -2
  62. package/dist-cli/chunks/{diagnose-IZODTXV2.js → diagnose-B6J5ZUHV.js} +2 -2
  63. package/dist-cli/chunks/drivers-RRHVOU6S.js +2 -0
  64. package/dist-cli/chunks/{electron-R5GP6RVB.js → electron-PSX4KDCC.js} +3 -3
  65. package/dist-cli/chunks/flow-FWNVFKMP.js +2 -0
  66. package/dist-cli/chunks/{hints-DYDNYX7N.js → hints-ZE4I3YO3.js} +2 -2
  67. package/dist-cli/chunks/{home-paths-GLMX5OKL.js → home-paths-N76MJE3D.js} +2 -2
  68. package/dist-cli/chunks/{inspect-FJOPCTY2.js → inspect-V2TXTDOG.js} +3 -3
  69. package/dist-cli/chunks/install-4PINRR2O.js +2 -0
  70. package/dist-cli/chunks/{install-desktop-YPJZMZM5.js → install-desktop-6ZRTRRCU.js} +3 -3
  71. package/dist-cli/chunks/{keys-GSYPHWNY.js → keys-L2RN4URM.js} +2 -2
  72. package/dist-cli/chunks/{launch-4G2PKW5X.js → launch-BJGXPNZR.js} +3 -3
  73. package/dist-cli/chunks/{login-KJQGHA64.js → login-HYNEMAYR.js} +4 -4
  74. package/dist-cli/chunks/{logout-XM2SYH5C.js → logout-AO4YS27T.js} +2 -2
  75. package/dist-cli/chunks/{maestro-EOWGI7DG.js → maestro-PRACYFKV.js} +2 -2
  76. package/dist-cli/chunks/{preview-F73TKK37.js → preview-ZTANXVEK.js} +2 -2
  77. package/dist-cli/chunks/{profile-22FDKBUO.js → profile-FNMAGUDB.js} +2 -2
  78. package/dist-cli/chunks/{react-5L6VPFUP.js → react-6ZV2FQIM.js} +2 -2
  79. package/dist-cli/chunks/{record-JZXCQ4IN.js → record-MLFVJZ6Y.js} +2 -2
  80. package/dist-cli/chunks/runtime-5762IE56.js +2 -0
  81. package/dist-cli/chunks/{runtime-delivery-LXUM3R4A.js → runtime-delivery-ATYW2SQR.js} +2 -2
  82. package/dist-cli/chunks/{screenshot-HDRRG33Q.js → screenshot-UOMYMFZ4.js} +2 -2
  83. package/dist-cli/chunks/{screenshot-mode-WY63LZIX.js → screenshot-mode-MWSVD4YG.js} +2 -2
  84. package/dist-cli/chunks/{screenshots-MPV2ENL5.js → screenshots-GSA3VCWB.js} +2 -2
  85. package/dist-cli/chunks/server-YPFC6POG.js +40 -0
  86. package/dist-cli/chunks/setup-repo-QBQ4VWFO.js +2 -0
  87. package/dist-cli/chunks/{skills-BQ73YOBF.js → skills-YE5OPWMQ.js} +2 -2
  88. package/dist-cli/chunks/{start-2WU4W6ZU.js → start-BSSQ5U2V.js} +4 -4
  89. package/dist-cli/chunks/store-EG4SONAH.js +2 -0
  90. package/dist-cli/chunks/telemetry-XXN4LRDS.js +2 -0
  91. package/dist-cli/chunks/{test-OVO4CQTG.js → test-5JMLBH2O.js} +3 -3
  92. package/dist-cli/chunks/{three-mode-BKM3KFM7.js → three-mode-TRBWZJQY.js} +2 -2
  93. package/dist-cli/chunks/{timeline-MDXGEDQL.js → timeline-YMZPIEB4.js} +2 -2
  94. package/dist-cli/chunks/{upgrade-JGQABWVF.js → upgrade-JLAS7FIF.js} +2 -2
  95. package/dist-cli/chunks/upload-K6UNCFQH.js +2 -0
  96. package/dist-cli/chunks/{web-WYFAYQ72.js → web-D6S5UXOO.js} +2 -2
  97. package/dist-cli/chunks/{what-happened-PZW2KW6A.js → what-happened-65NXWU2S.js} +2 -2
  98. package/dist-cli/chunks/{whoami-7ATWJQS6.js → whoami-6BSB6FQC.js} +2 -2
  99. package/dist-lib/agent-daemon-client.cjs +1 -1
  100. package/dist-lib/agent-events.cjs +1 -1
  101. package/dist-lib/agent-sessions.cjs +1 -1
  102. package/dist-lib/attached-projects.cjs +1 -1
  103. package/dist-lib/auth/shared-session.cjs +1 -1
  104. package/dist-lib/backend-origin.cjs +1 -1
  105. package/dist-lib/beta.cjs +1 -1
  106. package/dist-lib/beta.mjs +15 -0
  107. package/dist-lib/bridge-constants.cjs +1 -1
  108. package/dist-lib/cli-constants.cjs +1 -1
  109. package/dist-lib/config.cjs +1 -1
  110. package/dist-lib/detox/index.cjs +1 -1
  111. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  112. package/dist-lib/home-paths.cjs +1 -1
  113. package/dist-lib/host/bridge-host.cjs +244 -35
  114. package/dist-lib/host/fetch-proxy-handler.cjs +24 -4
  115. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  116. package/dist-lib/host/fetch-proxy-overrides.mjs +33 -0
  117. package/dist-lib/host/websocket-proxy.cjs +207 -0
  118. package/dist-lib/index.cjs +136 -138
  119. package/dist-lib/metro.cjs +31 -26
  120. package/dist-lib/profiles.cjs +1 -1
  121. package/dist-lib/render-mode.cjs +1 -1
  122. package/dist-lib/scripts/demo-app-registry.cjs +14 -3
  123. package/dist-lib/scripts/dev-server-scanner.cjs +14 -3
  124. package/dist-lib/skills.cjs +9737 -76
  125. package/dist-lib/vite.cjs +129 -39
  126. package/package.json +8 -6
  127. package/scripts/demo-app-registry.ts +17 -1
  128. package/src/host/bridge-host.ts +8 -1
  129. package/src/host/fetch-proxy-handler.ts +26 -3
  130. package/src/host/websocket-proxy.ts +201 -0
  131. package/src/metro-plugin.ts +9 -77
  132. package/src/runtime-assets.ts +84 -0
  133. package/src/skills/builtin/compat-check.ts +1 -1
  134. package/src/vite-plugin-one.ts +60 -58
  135. package/dist-cli/chunks/auto-bootstrap-NYYSMTIM.js +0 -2
  136. package/dist-cli/chunks/beta-4K2SQACK.js +0 -2
  137. package/dist-cli/chunks/chunk-67ZZ2CM5.js +0 -1
  138. package/dist-cli/chunks/chunk-D3ZSBIIY.js +0 -2
  139. package/dist-cli/chunks/chunk-FUCGLWNN.js +0 -1
  140. package/dist-cli/chunks/chunk-IILJQCZA.js +0 -2
  141. package/dist-cli/chunks/chunk-PS2G44GT.js +0 -24
  142. package/dist-cli/chunks/chunk-ZSMMJMPA.js +0 -1
  143. package/dist-cli/chunks/cli-version-QB4VH24H.js +0 -2
  144. package/dist-cli/chunks/drivers-MK6WJKBC.js +0 -2
  145. package/dist-cli/chunks/flow-6O4GEOPJ.js +0 -2
  146. package/dist-cli/chunks/install-A3TUGGHN.js +0 -2
  147. package/dist-cli/chunks/runtime-EEBX7CFV.js +0 -2
  148. package/dist-cli/chunks/server-5LBMCJ3G.js +0 -35
  149. package/dist-cli/chunks/setup-repo-SZSYNKNI.js +0 -2
  150. package/dist-cli/chunks/store-RE45SUBF.js +0 -2
  151. package/dist-cli/chunks/telemetry-DG6GJLCP.js +0 -2
  152. package/dist-cli/chunks/upload-UJNUA4ZV.js +0 -2
  153. package/dist-lib/vite-base.cjs +0 -6937
@@ -10,77 +10,16 @@
10
10
 
11
11
  import fs from 'fs'
12
12
  import path from 'path'
13
- import { readActiveRuntime, runtimeDir } from './home-paths'
13
+ import {
14
+ isRootRuntimeAssetPath,
15
+ resolveActiveRuntimeRoot,
16
+ resolveRuntimeFilePath,
17
+ serveRuntimeFile,
18
+ SOOTSIM_RUNTIME_MISSING_MESSAGE,
19
+ } from './runtime-assets'
14
20
 
15
21
  const prefix = '/__soot'
16
22
 
17
- function resolveRuntimeRoot(): string | null {
18
- const active = readActiveRuntime()
19
- if (!active) return null
20
- const dir = runtimeDir(active)
21
- if (!fs.existsSync(path.join(dir, 'index.html'))) return null
22
- return dir
23
- }
24
-
25
- const RUNTIME_MISSING_MESSAGE =
26
- '[sootsim] no engine runtime installed — run `sootsim setup-repo` in your project, or `sootsim runtime install`'
27
-
28
- const MIME_TYPES: Record<string, string> = {
29
- '.js': 'application/javascript',
30
- '.css': 'text/css',
31
- '.html': 'text/html',
32
- '.wasm': 'application/wasm',
33
- '.json': 'application/json',
34
- '.jpg': 'image/jpeg',
35
- '.jpeg': 'image/jpeg',
36
- '.png': 'image/png',
37
- '.svg': 'image/svg+xml',
38
- '.webp': 'image/webp',
39
- '.glb': 'model/gltf-binary',
40
- '.ttf': 'font/ttf',
41
- '.otf': 'font/otf',
42
- '.mp3': 'audio/mpeg',
43
- '.wav': 'audio/wav',
44
- }
45
-
46
- const ROOT_RUNTIME_PATHS = [
47
- '/assets/',
48
- '/engine/',
49
- '/engine-tenant/',
50
- '/photos/',
51
- '/three-mode/',
52
- '/canvaskit.wasm',
53
- '/fonts/',
54
- '/icons/',
55
- '/sounds/',
56
- '/spike/',
57
- '/test-wallpaper.jpg',
58
- '/preview-sw.js',
59
- ]
60
-
61
- export function isRootRuntimeAssetPath(pathname: string): boolean {
62
- return ROOT_RUNTIME_PATHS.some((p) => pathname === p || pathname.startsWith(p))
63
- }
64
-
65
- export function resolveRuntimeFilePath(
66
- runtimeRoot: string,
67
- pathname: string,
68
- ): string | null {
69
- if (!pathname.startsWith('/')) return null
70
- if (pathname.includes('\0') || pathname.includes('\\')) return null
71
- for (const segment of pathname.split('/')) {
72
- if (segment === '..') return null
73
- }
74
-
75
- const fullPath = path.resolve(runtimeRoot, pathname.replace(/^\/+/, ''))
76
- const rootWithSep = runtimeRoot.endsWith(path.sep)
77
- ? runtimeRoot
78
- : runtimeRoot + path.sep
79
- if (!fullPath.startsWith(rootWithSep) && fullPath !== runtimeRoot) return null
80
- if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) return null
81
- return fullPath
82
- }
83
-
84
23
  export interface SootsimMetroOptions {
85
24
  bundleUrl?: string
86
25
  enabled?: boolean
@@ -128,12 +67,12 @@ export function withSootsim<T extends Record<string, unknown>>(
128
67
  server.use((req: any, res: any, next: any) => {
129
68
  const url = req.url || ''
130
69
  const pathname = url.split('?')[0]
131
- const runtimeRoot = resolveRuntimeRoot()
70
+ const runtimeRoot = resolveActiveRuntimeRoot()
132
71
 
133
72
  if (!runtimeRoot) {
134
73
  if (pathname === prefix || pathname === prefix + '/') {
135
74
  res.statusCode = 500
136
- res.end(RUNTIME_MISSING_MESSAGE)
75
+ res.end(SOOTSIM_RUNTIME_MISSING_MESSAGE)
137
76
  return
138
77
  }
139
78
  next()
@@ -198,10 +137,3 @@ export function withSootsim<T extends Record<string, unknown>>(
198
137
  }
199
138
 
200
139
  export default withSootsim
201
-
202
- function serveRuntimeFile(res: any, fullPath: string) {
203
- const ext = path.extname(fullPath)
204
- res.setHeader('content-type', MIME_TYPES[ext] || 'application/octet-stream')
205
- res.setHeader('cache-control', 'max-age=31536000,immutable')
206
- fs.createReadStream(fullPath).pipe(res)
207
- }
@@ -0,0 +1,84 @@
1
+ // helpers for serving the installed sootsim engine runtime from framework
2
+ // dev servers. the runtime is managed by `sootsim runtime install` and lives
3
+ // under ~/.sootsim/runtimes/<version>.
4
+
5
+ import fs from 'fs'
6
+ import path from 'path'
7
+ import { readActiveRuntime, runtimeDir } from './home-paths'
8
+
9
+ export const SOOTSIM_RUNTIME_MISSING_MESSAGE =
10
+ '[sootsim] no engine runtime installed — run `sootsim setup-repo` in your project, or `sootsim runtime install`'
11
+
12
+ const MIME_TYPES: Record<string, string> = {
13
+ '.js': 'application/javascript',
14
+ '.mjs': 'application/javascript',
15
+ '.css': 'text/css',
16
+ '.html': 'text/html',
17
+ '.wasm': 'application/wasm',
18
+ '.json': 'application/json',
19
+ '.jpg': 'image/jpeg',
20
+ '.jpeg': 'image/jpeg',
21
+ '.png': 'image/png',
22
+ '.svg': 'image/svg+xml',
23
+ '.webp': 'image/webp',
24
+ '.glb': 'model/gltf-binary',
25
+ '.ttf': 'font/ttf',
26
+ '.otf': 'font/otf',
27
+ '.woff': 'font/woff',
28
+ '.woff2': 'font/woff2',
29
+ '.mp3': 'audio/mpeg',
30
+ '.wav': 'audio/wav',
31
+ }
32
+
33
+ const ROOT_RUNTIME_PATHS = [
34
+ '/assets/',
35
+ '/engine/',
36
+ '/engine-tenant/',
37
+ '/photos/',
38
+ '/three-mode/',
39
+ '/canvaskit.wasm',
40
+ '/fonts/',
41
+ '/icons/',
42
+ '/sounds/',
43
+ '/spike/',
44
+ '/test-wallpaper.jpg',
45
+ '/preview-sw.js',
46
+ ]
47
+
48
+ export function resolveActiveRuntimeRoot(): string | null {
49
+ const active = readActiveRuntime()
50
+ if (!active) return null
51
+ const dir = runtimeDir(active)
52
+ if (!fs.existsSync(path.join(dir, 'index.html'))) return null
53
+ return dir
54
+ }
55
+
56
+ export function isRootRuntimeAssetPath(pathname: string): boolean {
57
+ return ROOT_RUNTIME_PATHS.some((p) => pathname === p || pathname.startsWith(p))
58
+ }
59
+
60
+ export function resolveRuntimeFilePath(
61
+ runtimeRoot: string,
62
+ pathname: string,
63
+ ): string | null {
64
+ if (!pathname.startsWith('/')) return null
65
+ if (pathname.includes('\0') || pathname.includes('\\')) return null
66
+ for (const segment of pathname.split('/')) {
67
+ if (segment === '..') return null
68
+ }
69
+
70
+ const fullPath = path.resolve(runtimeRoot, pathname.replace(/^\/+/, ''))
71
+ const rootWithSep = runtimeRoot.endsWith(path.sep)
72
+ ? runtimeRoot
73
+ : runtimeRoot + path.sep
74
+ if (!fullPath.startsWith(rootWithSep) && fullPath !== runtimeRoot) return null
75
+ if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) return null
76
+ return fullPath
77
+ }
78
+
79
+ export function serveRuntimeFile(res: any, fullPath: string) {
80
+ const ext = path.extname(fullPath)
81
+ res.setHeader('content-type', MIME_TYPES[ext] || 'application/octet-stream')
82
+ res.setHeader('cache-control', 'max-age=31536000,immutable')
83
+ fs.createReadStream(fullPath).pipe(res)
84
+ }
@@ -31,7 +31,7 @@ export const skill: SootSimSkill = {
31
31
  const fs = await import('fs')
32
32
  const path = await import('path')
33
33
 
34
- const { POLYFILL_REGISTRY } = await import('@soot/compat/web')
34
+ const { POLYFILL_REGISTRY } = await import('../../../../compat/src/web.ts')
35
35
 
36
36
  if (params.packageName) {
37
37
  const entry = POLYFILL_REGISTRY[params.packageName]
@@ -1,5 +1,5 @@
1
- // sootsim plugin for One/Vite — serves pre-built sootsim at /__soot/
2
- // no runtime vite server — just static files from dist-plugin/
1
+ // sootsim plugin for One/Vite — serves the installed sootsim runtime at /__soot/
2
+ // from ~/.sootsim/runtimes/<version>.
3
3
  //
4
4
  // usage in vite.config.ts:
5
5
  // import { sootsimPlugin } from 'sootsim/vite'
@@ -7,11 +7,16 @@
7
7
 
8
8
  import fs from 'fs'
9
9
  import path from 'path'
10
+ import {
11
+ isRootRuntimeAssetPath,
12
+ resolveActiveRuntimeRoot,
13
+ resolveRuntimeFilePath,
14
+ serveRuntimeFile,
15
+ SOOTSIM_RUNTIME_MISSING_MESSAGE,
16
+ } from './runtime-assets'
10
17
  import type { Plugin } from 'vite'
11
18
 
12
19
  const sootsimRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..')
13
- const distDir = path.join(sootsimRoot, 'dist-plugin')
14
- const publicDir = path.join(sootsimRoot, 'public')
15
20
 
16
21
  export interface SootPluginOptions {
17
22
  // custom bundle URL (default: auto-detect from One's metro)
@@ -20,25 +25,8 @@ export interface SootPluginOptions {
20
25
  prefix?: string
21
26
  // disable sootsim
22
27
  enabled?: boolean
23
- }
24
-
25
- const MIME_TYPES: Record<string, string> = {
26
- '.js': 'application/javascript',
27
- '.mjs': 'application/javascript',
28
- '.css': 'text/css',
29
- '.html': 'text/html',
30
- '.wasm': 'application/wasm',
31
- '.json': 'application/json',
32
- '.png': 'image/png',
33
- '.svg': 'image/svg+xml',
34
- '.ttf': 'font/ttf',
35
- '.otf': 'font/otf',
36
- '.woff': 'font/woff',
37
- '.woff2': 'font/woff2',
38
- '.mp3': 'audio/mpeg',
39
- '.wav': 'audio/wav',
40
- '.jpg': 'image/jpeg',
41
- '.webp': 'image/webp',
28
+ // open the desktop app after the dev server starts (default true)
29
+ open?: boolean
42
30
  }
43
31
 
44
32
  export function sootsimPlugin(options: SootPluginOptions = {}): Plugin[] {
@@ -77,15 +65,36 @@ export function sootsimPlugin(options: SootPluginOptions = {}): Plugin[] {
77
65
 
78
66
  server.middlewares.use((req, res, next) => {
79
67
  const url = req.url || ''
80
-
81
- // serve the sootsim HTML shell — inject bundle URL
82
- if (url === prefix || url === prefix + '/' || url.startsWith(prefix + '/?')) {
83
- const htmlPath = path.join(distDir, 'index.html')
84
- if (!fs.existsSync(htmlPath)) {
68
+ const pathname = url.split('?')[0]
69
+ const runtimeRoot = resolveActiveRuntimeRoot()
70
+
71
+ if (!runtimeRoot) {
72
+ if (
73
+ pathname === prefix ||
74
+ pathname === prefix + '/' ||
75
+ pathname.startsWith(prefix + '/')
76
+ ) {
85
77
  res.statusCode = 500
86
- res.end('[sootsim] dist-plugin not built. run: bun scripts/build-plugin.ts')
78
+ res.end(SOOTSIM_RUNTIME_MISSING_MESSAGE)
79
+ return
80
+ }
81
+ next()
82
+ return
83
+ }
84
+
85
+ // root-relative runtime assets emitted by the shell and engine vite
86
+ // builds. only intercept when the asset exists in the active runtime.
87
+ if (isRootRuntimeAssetPath(pathname)) {
88
+ const fullPath = resolveRuntimeFilePath(runtimeRoot, pathname)
89
+ if (fullPath) {
90
+ serveRuntimeFile(res, fullPath)
87
91
  return
88
92
  }
93
+ }
94
+
95
+ // serve the sootsim HTML shell — inject bundle URL
96
+ if (pathname === prefix || pathname === prefix + '/') {
97
+ const htmlPath = path.join(runtimeRoot, 'index.html')
89
98
  let html = fs.readFileSync(htmlPath, 'utf8')
90
99
  // inject bundle URL as a query param so main.tsx picks it up
91
100
  html = html.replace(
@@ -97,37 +106,28 @@ export function sootsimPlugin(options: SootPluginOptions = {}): Plugin[] {
97
106
  return
98
107
  }
99
108
 
100
- // serve static files from dist-plugin/
101
- if (url.startsWith(prefix + '/')) {
102
- const filePath = url.slice(prefix.length).split('?')[0]
103
- const fullPath = path.join(distDir, filePath)
104
-
105
- if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
106
- const ext = path.extname(fullPath)
107
- res.setHeader('content-type', MIME_TYPES[ext] || 'application/octet-stream')
108
- res.setHeader('cache-control', 'max-age=31536000,immutable')
109
- fs.createReadStream(fullPath).pipe(res)
110
- return
111
- }
112
-
113
- // try public/ for wasm, fonts, icons, sounds
114
- const publicPath = path.join(publicDir, filePath)
115
- if (fs.existsSync(publicPath) && fs.statSync(publicPath).isFile()) {
116
- const ext = path.extname(publicPath)
117
- res.setHeader('content-type', MIME_TYPES[ext] || 'application/octet-stream')
118
- fs.createReadStream(publicPath).pipe(res)
109
+ // serve runtime files under /__soot/*.
110
+ if (pathname.startsWith(prefix + '/')) {
111
+ const fullPath = resolveRuntimeFilePath(
112
+ runtimeRoot,
113
+ pathname.slice(prefix.length),
114
+ )
115
+ if (fullPath) {
116
+ serveRuntimeFile(res, fullPath)
119
117
  return
120
118
  }
121
- }
122
119
 
123
- // also serve canvaskit.wasm etc. from public/ at root (some code references /canvaskit.wasm)
124
- const staticRoots = ['/canvaskit.wasm', '/fonts/', '/icons/', '/sounds/']
125
- if (staticRoots.some((p) => url.startsWith(p))) {
126
- const fullPath = path.join(publicDir, url.split('?')[0])
127
- if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
128
- const ext = path.extname(fullPath)
129
- res.setHeader('content-type', MIME_TYPES[ext] || 'application/octet-stream')
130
- fs.createReadStream(fullPath).pipe(res)
120
+ // extensionless in-shell routes should still load the shell.
121
+ const ext = path.extname(pathname)
122
+ if (!ext) {
123
+ const htmlPath = path.join(runtimeRoot, 'index.html')
124
+ let html = fs.readFileSync(htmlPath, 'utf8')
125
+ html = html.replace(
126
+ '</head>',
127
+ `<script>history.replaceState(null,'','${prefix}/?bundle=${encodeURIComponent(bundleUrl)}')</script></head>`,
128
+ )
129
+ res.setHeader('content-type', 'text/html')
130
+ res.end(html)
131
131
  return
132
132
  }
133
133
  }
@@ -140,7 +140,9 @@ export function sootsimPlugin(options: SootPluginOptions = {}): Plugin[] {
140
140
  const sootsimUrl = `http://localhost:${port}${prefix}/`
141
141
  console.log(`[sootsim] serving at ${sootsimUrl}`)
142
142
 
143
- openElectronApp(sootsimUrl)
143
+ if (options.open !== false) {
144
+ openElectronApp(sootsimUrl)
145
+ }
144
146
  },
145
147
  },
146
148
  ]
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d}from"./chunk-4K7BH2D4.js";import"./chunk-XQ2OBHBE.js";import"./chunk-EQCKGC4B.js";import"./chunk-IILJQCZA.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";export{c as ensureDaemonRunning,a as ensureRuntimeInstalled,d as ensureSootsimReady,b as resolveBootstrapPort};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d,e}from"./chunk-SQX5CAYG.js";import"./chunk-BKBL6K2G.js";export{e as BETA_ASK_HEADLINE,c as BETA_LABEL,d as BETA_TAGLINE,b as BETA_VERSION_TARGET,a as IS_BETA};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- var t="http://localhost:5173/";export{t as a};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- var O="sootsim close";export{O as a};
@@ -1,24 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as r}from"./chunk-QMSJR5R2.js";import{a as s,b as i,c as l}from"./chunk-7YHDJLO2.js";import{a as t}from"./chunk-D3ZSBIIY.js";import{a as u,d}from"./chunk-SQX5CAYG.js";import{a as p}from"./chunk-TL7SIZ7S.js";import{z as a}from"./chunk-D4HUVLZR.js";function m(){let o=a();return`sootsim CLI v${p()} \xB7 runtime ${o?`v${o}`:"not installed"}`}function I(){let o=i({bridgePort:7668,defaultShellUrl:t},r()),e=`
3
- ${d}
4
- `;console.log(`${m()}
5
- ${e}${o}
6
-
7
- auth:
8
- sootsim login sign in for desktop uploads
9
- sootsim logout clear the shared desktop session
10
- sootsim whoami show the current shared desktop account
11
- sootsim keys manage api keys for headless CI preview uploads
12
- `)}function S(o){let e=s(o,{bridgePort:7668,defaultShellUrl:t},r());return e?(console.log(`${e}
13
- `),!0):!1}function c(o){if(o==="3d-mode"){c("three-mode");return}if(o==="login"){console.log(`sootsim login
14
-
15
- sign in for desktop uploads using the shared desktop session.
16
- opens the browser and stores the resulting bearer token for both the CLI and electron.
17
- `);return}if(o==="logout"){console.log(`sootsim logout
18
-
19
- clear the shared desktop session used by the CLI and electron.
20
- `);return}if(o==="whoami"){console.log(`sootsim whoami
21
-
22
- show the current shared desktop account used by the CLI and electron.
23
- `);return}let e=l(o,{bridgePort:7668,defaultShellUrl:t});if(!e){console.log(`unknown command: ${o}`);return}console.log(`${e}
24
- `)}export{I as a,S as b,c};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-TL7SIZ7S.js";import"./chunk-BKBL6K2G.js";export{a as getCliVersion};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import"./chunk-ZSMMJMPA.js";import{a,b,c,d,e,f,g,h,i}from"./chunk-YCIA4BHJ.js";import"./chunk-AJVTY6KY.js";import"./chunk-4OPRODFA.js";import"./chunk-67ZZ2CM5.js";import"./chunk-7NWNTUJF.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";export{e as ALL_DRIVERS,i as buildDriverListRows,a as chromiumDriver,b as electronDriver,f as getAllDrivers,g as getDriver,c as playwrightDriver,h as resolveDriver,d as systemDriver};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as c,b as d,c as e,d as f}from"./chunk-ELJLF4SG.js";import"./chunk-HYPJW65U.js";import"./chunk-FUCGLWNN.js";import"./chunk-KU6MSPAH.js";import{d as a,e as b}from"./chunk-V2GQ4WXJ.js";import"./chunk-TK3OJSEO.js";import"./chunk-AWSQUOAS.js";import"./chunk-C3DPQZ4J.js";import"./chunk-EQ7TFQ2F.js";import"./chunk-YCIA4BHJ.js";import"./chunk-AJVTY6KY.js";import"./chunk-4OPRODFA.js";import"./chunk-3HXQ7MJK.js";import"./chunk-4OWVPRZV.js";import"./chunk-67ZZ2CM5.js";import"./chunk-7NWNTUJF.js";import"./chunk-QMSJR5R2.js";import"./chunk-D3ZSBIIY.js";import"./chunk-RIXUH3NK.js";import"./chunk-SFGUPL2X.js";import"./chunk-73UZXB4B.js";import"./chunk-SQZAC7C4.js";import"./chunk-XQ2OBHBE.js";import"./chunk-EQCKGC4B.js";import"./chunk-IILJQCZA.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";export{c as discoverSootsimUrl,e as hoistLeadingSimFlag,a as parseFlowFile,f as runFlow,d as runFlowPlayback,b as validateFlowFile};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as o}from"./chunk-RF4R2U46.js";import"./chunk-DUUSJDES.js";import"./chunk-4K7BH2D4.js";import"./chunk-RIXUH3NK.js";import"./chunk-SFGUPL2X.js";import"./chunk-XQ2OBHBE.js";import"./chunk-EQCKGC4B.js";import"./chunk-IILJQCZA.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";async function t(n){console.error(" note: `sootsim install` is now `sootsim setup-repo`. forwarding\u2026\n"),await o(n)}export{t as runInstall};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-P7WDNKOS.js";import"./chunk-XQ2OBHBE.js";import"./chunk-EQCKGC4B.js";import"./chunk-IILJQCZA.js";import"./chunk-SV7FOGJ3.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";export{a as runRuntime};
@@ -1,35 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{b as ee,c as D,d as te,e as H,f as ie,h as re,i as A,j as ne,k as se,l as oe,p as ae,s as k,t as ce,u as de,v as le,w as ue}from"./chunk-WNVNU2OW.js";import"./chunk-BCBNVJVG.js";import{f as E}from"./chunk-AWSQUOAS.js";import{a as Q}from"./chunk-3HXQ7MJK.js";import"./chunk-4OWVPRZV.js";import"./chunk-67ZZ2CM5.js";import{d as Y}from"./chunk-7NWNTUJF.js";import{a as F}from"./chunk-TL7SIZ7S.js";import{a as z,b as X}from"./chunk-RIXUH3NK.js";import"./chunk-SFGUPL2X.js";import"./chunk-XQ2OBHBE.js";import"./chunk-EQCKGC4B.js";import{a as U}from"./chunk-IILJQCZA.js";import{k as Z}from"./chunk-SV7FOGJ3.js";import{A as $,B,D as J,E as K,F as V,G,H as q,I as N,u as L,v as W,y as T,z as j}from"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";import{spawn as Le}from"child_process";import w from"fs";import{createServer as Be}from"http";import S from"path";import{WebSocket as h,WebSocketServer as Ne}from"ws";import pe from"node:fs";import I from"node:path";var P=1;function Ae(){return[Number(process.env.VITE_PORT_WEB||process.env.PORT||3e3),Number(process.env.VITE_PORT_ZERO||7849),Number(process.env.VITE_PORT_POSTGRES||7432),Number(process.env.VITE_PORT_R2||9500)].filter(a=>Number.isFinite(a)&&a>0)}var R=class{subscriptions=new Map;sessionsBySocket=new Map;allSockets=new Set;pendingPromptEchoes=new Map;pendingTurns=new Map;opts;constructor(e={}){this.opts=e}registerSocket(e){this.allSockets.add(e)}unregisterSocket(e){let t=this.sessionsBySocket.get(e);if(t){for(let i of t)this.decrementSubscription(i);this.sessionsBySocket.delete(e)}this.allSockets.delete(e)}async handleMessage(e,t){let i=t?.type;if(typeof i!="string"||!i.startsWith("agent:"))return!1;let s=t.id;try{let n=await this.dispatch(e,i,t);this.respond(e,s,n)}catch(n){n instanceof k?this.respondError(e,s,n.message,n.code):this.respondError(e,s,n instanceof Error?n.message:String(n))}return!0}async seedOnBoot(){try{await oe()}catch(e){process.stderr.write(`[sootsim-agent] seedFromDemoAppRegistry failed: ${e instanceof Error?e.message:String(e)}
3
- `)}}close(){for(let e of this.subscriptions.values())try{e.unsubscribe()}catch{}this.subscriptions.clear(),this.sessionsBySocket.clear(),this.allSockets.clear()}async dispatch(e,t,i){switch(t){case"agent:list-projects":return H();case"agent:upsert-project":return D(i.input??{});case"agent:delete-project":return re(String(i.projectId)),{ok:!0};case"agent:auto-attach-for-url":return this.autoAttachForUrl(i.input??{});case"agent:list-sessions":return ne(i.projectId?String(i.projectId):void 0);case"agent:start-session":return this.doStartSession(i.input??{});case"agent:send-prompt":{let n=String(i.sessionId),o=A(n);if(!o)throw new k("NO_SESSION",`no session: ${n}`);let r=this.normalizePromptEnvelope(i);return await de(n,r),this.notePromptAccepted(n,r,o.status==="working")}case"agent:end-session":this.dropSessionFanout(String(i.sessionId)),await le(String(i.sessionId));let s=A(String(i.sessionId));return s&&this.broadcastSessionStatus(s),{ok:!0};case"agent:get-transcript":return this.getTranscript(String(i.sessionId));case"agent:get-paths":return this.getPaths();case"agent:subscribe-events":return this.subscribeSocket(e,String(i.sessionId));case"agent:unsubscribe-events":return this.unsubscribeSocket(e,String(i.sessionId));default:throw new k("UNKNOWN_AGENT_MSG",`unknown agent message: ${t}`)}}async doStartSession(e){if(!te(e.projectId))throw new k("NO_PROJECT",`no project: ${e.projectId}`);let i=await ce(e);return this.broadcastSessionStatus(i.session),i}async autoAttachForUrl(e){let t=e.bundleUrl??"",i=(()=>{try{return new URL(t).port||null}catch{return null}})();if(!i)return{project:null};let s=this.opts.getExcludePorts?.()??Ae(),o=(await E({excludePorts:s})).find(d=>String(d.port)===i);if(!o||!o.cwd)return{project:null};let r=H().find(d=>d.cwd===o.cwd)??null,c=Array.from(new Set([...r?.knownBundleUrls??[],o.bundleUrl,t]));return{project:D({cwd:o.cwd,name:o.projectName??I.basename(o.cwd),preferredProvider:e.provider??r?.preferredProvider,sourceRoots:r?.sourceRoots??[o.cwd],knownBundleUrls:c,framework:r?.framework??ke(o.framework),bundleId:o.bundleId??r?.bundleId})}}getTranscript(e){let t=ae(e);return pe.existsSync(t)?pe.readFileSync(t,"utf8"):{error:"transcript not found",code:"NO_TRANSCRIPT"}}getPaths(){let e=ee();return{userDataDir:e,storeFile:I.join(e,"attached-projects.json"),sessionsDir:I.join(e,"sessions"),transcriptsDir:I.join(e,"transcripts")}}subscribeSocket(e,t){let i=this.sessionsBySocket.get(e);if(i||(i=new Set,this.sessionsBySocket.set(e,i)),i.has(t))return{ok:!0,refCount:this.subscriptions.get(t)?.refCount??1};i.add(t);let s=this.subscriptions.get(t);if(s)return s.refCount++,{ok:!0,refCount:s.refCount};let n=ue(t,o=>{let r=this.coalescePromptEcho(t,o);if(r&&(this.applySessionEvent(t,r),this.fanOutEvent(t,r)),o.type==="turn-completed"){let c=A(t);if(c)try{ie(c.projectId,{usd:o.costUsd,ts:o.ts})}catch(l){process.stderr.write(`[sootsim-agent] recordTurnTelemetry failed: ${l instanceof Error?l.message:String(l)}
4
- `)}}});return this.subscriptions.set(t,{unsubscribe:n,refCount:1}),{ok:!0,refCount:1}}unsubscribeSocket(e,t){let i=this.sessionsBySocket.get(e);return!i||!i.has(t)?{ok:!0,refCount:0}:(i.delete(t),this.decrementSubscription(t))}decrementSubscription(e){let t=this.subscriptions.get(e);if(!t)return{ok:!0,refCount:0};if(t.refCount--,t.refCount<=0){try{t.unsubscribe()}catch{}return this.subscriptions.delete(e),{ok:!0,refCount:0}}return{ok:!0,refCount:t.refCount}}dropSessionFanout(e){let t=this.subscriptions.get(e);if(t){try{t.unsubscribe()}catch{}this.subscriptions.delete(e)}for(let i of this.sessionsBySocket.values())i.delete(e);this.clearPromptTracking(e)}normalizePromptEnvelope(e){if(e?.prompt&&typeof e.prompt=="object"){let t=e.prompt;return{text:String(t.text??""),...typeof t.displayText=="string"?{displayText:t.displayText}:{},...typeof t.inspectSummary=="string"?{inspectSummary:t.inspectSummary}:{},...typeof t.inspectTrace=="string"?{inspectTrace:t.inspectTrace}:{}}}return{text:String(e?.text??""),...typeof e?.displayText=="string"?{displayText:e.displayText}:{},...typeof e?.inspectSummary=="string"?{inspectSummary:e.inspectSummary}:{},...typeof e?.inspectTrace=="string"?{inspectTrace:e.inspectTrace}:{}}}notePromptAccepted(e,t,i){let s=Date.now(),n=this.pendingPromptEchoes.get(e)??[];n.push({sentAt:s}),this.pendingPromptEchoes.set(e,n);let o=Math.max(this.pendingTurns.get(e)??0,i?1:0)+1;this.pendingTurns.set(e,o);let r=t.displayText??t.text;return this.patchSession(e,{lastPrompt:r,status:"working",needsAttention:!1}),this.fanOutEvent(e,{type:"prompt-received",text:r,...t.inspectSummary?{inspectSummary:t.inspectSummary}:{},...t.inspectTrace?{inspectTrace:t.inspectTrace}:{},ts:s}),{ok:!0,queued:o>1,pendingTurns:o,queueDepth:Math.max(0,o-1)}}applySessionEvent(e,t){switch(t.type){case"prompt-received":case"turn-started":this.patchSession(e,{status:"working",needsAttention:!1});return;case"turn-completed":{let i=this.consumeSettledTurn(e);this.patchSession(e,{status:i>0?"working":"idle",needsAttention:!1,lastTurnFiles:t.filesTouched,currentlyEditing:void 0});return}case"approval-needed":this.patchSession(e,{status:"needs-attention",needsAttention:!0});return;case"error":{let i=this.consumeSettledTurn(e);this.patchSession(e,{status:i>0?"working":"needs-attention",needsAttention:i<=0,currentlyEditing:void 0});return}case"exited":this.clearPromptTracking(e),this.patchSession(e,{status:"ended",needsAttention:!1,wrapperPid:void 0,currentlyEditing:void 0});return;case"ready":case"turn-reasoning":case"turn-message":case"turn-plan":case"tool-call":case"file-edited":case"file-diff-delta":return}}patchSession(e,t){se(e,t);let i=A(e);i&&this.broadcastSessionStatus(i)}coalescePromptEcho(e,t){if(t.type!=="prompt-received")return t;let i=this.pendingPromptEchoes.get(e);if(!i||i.length===0)return t;for(;i.length>0&&Date.now()-i[0].sentAt>15e3;)i.shift();return i.length===0?(this.pendingPromptEchoes.delete(e),t):(i.shift(),i.length===0?this.pendingPromptEchoes.delete(e):this.pendingPromptEchoes.set(e,i),null)}consumeSettledTurn(e){let t=Math.max(0,(this.pendingTurns.get(e)??1)-1);return t>0?this.pendingTurns.set(e,t):this.pendingTurns.delete(e),t}clearPromptTracking(e){this.pendingPromptEchoes.delete(e),this.pendingTurns.delete(e)}fanOutEvent(e,t){let i=JSON.stringify({type:"agent:event",sessionId:e,event:t});for(let[s,n]of this.sessionsBySocket)if(n.has(e)&&s.readyState===P)try{s.send(i)}catch{}}broadcastSessionStatus(e){let t=JSON.stringify({type:"agent:session-status",session:e});for(let i of this.allSockets)if(i.readyState===P)try{i.send(t)}catch{}}respond(e,t,i){if(e.readyState===P)try{e.send(JSON.stringify({id:t,result:i}))}catch{}}respondError(e,t,i,s){if(e.readyState===P)try{e.send(JSON.stringify({id:t,error:i,...s?{code:s}:{}}))}catch{}}};function ke(a){return a==="expo"?"expo":a==="one"||a==="vxrn"?"one":"unknown"}import Ee from"http";import Ie from"https";var M="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",Ce=[{hostSuffix:"uniswap.org",headers:{origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/"}}];function Te(a){let e=a.toLowerCase();for(let t of Ce)if(e===t.hostSuffix||e.endsWith(`.${t.hostSuffix}`))return t.headers;return{}}function me(a){return{"accept-encoding":"identity","user-agent":M,...Te(a.hostname)}}var Pe=new Set(["host","origin","referer","user-agent","accept-encoding","cookie","connection","keep-alive","transfer-encoding","upgrade","content-length","sec-fetch-site","sec-fetch-mode","sec-fetch-dest","sec-ch-ua","sec-ch-ua-mobile","sec-ch-ua-platform"]),Re={"access-control-allow-origin":"*","access-control-allow-methods":"GET,POST,PUT,DELETE,PATCH,OPTIONS","access-control-allow-headers":"*","access-control-expose-headers":"*","access-control-max-age":"3600"};function C(a){for(let[e,t]of Object.entries(Re))a.setHeader(e,t)}function Oe(a,e){let t=[],i=e;i?.code&&t.push(i.code),i?.message&&t.push(i.message),i?.cause?.code&&t.push(i.cause.code),i?.cause?.message&&t.push(i.cause.message);let n=[...new Set(t.filter(Boolean))].join(" | ")||String(e);return a.includes("stored-in-.env.local")?`${n} | upstream url still contains placeholder env values`:n}function _e(a,e){let t={};for(let[i,s]of Object.entries(a))s&&(Pe.has(i.toLowerCase())||(t[i]=Array.isArray(s)?s.join(", "):s));return Object.assign(t,e?me(e):{"user-agent":M}),t}function he(a){return a?.startsWith("/__fetch-proxy?")||a?.startsWith("/__proxy?")||!1}function fe(a){return a?!!(a.startsWith("/__app-api?")||a.startsWith("/__app-api/")):!1}async function ge(a,e){if(a.method==="OPTIONS"){C(e),e.writeHead(204),e.end();return}let i=new URLSearchParams((a.url||"").split("?")[1]||"").get("url");if(!i){C(e),e.writeHead(400,{"Content-Type":"text/plain"}),e.end("missing url param");return}let s;try{s=new URL(i)}catch{C(e),e.writeHead(400,{"Content-Type":"text/plain"}),e.end("invalid url param");return}let n=_e(a.headers,s),o;if(a.method!=="GET"&&a.method!=="HEAD"){let d=[];for await(let u of a)d.push(Buffer.isBuffer(u)?u:Buffer.from(u));d.length>0&&(o=Buffer.concat(d))}let r;try{r=await fetch(s.href,{method:a.method,headers:n,body:o,redirect:"follow"})}catch(d){C(e),e.writeHead(502,{"Content-Type":"text/plain"}),e.end(`fetch proxy error: ${Oe(s.href,d)}`);return}r.headers.forEach((d,u)=>{let p=u.toLowerCase();p==="content-encoding"||p==="transfer-encoding"||p==="content-length"||p==="set-cookie"||p.startsWith("access-control-")||e.setHeader(u,d)}),C(e);let c=r.headers.getSetCookie?.()??[];c.length>0&&e.setHeader("x-sootsim-set-cookie",c.join(", ")),e.statusCode=r.status;let l=Buffer.from(await r.arrayBuffer());e.end(l)}function Se(a,e){let t=a.url||"",i="",s="";if(t.startsWith("/__app-api?")){let l=new URL(t,"http://sootsim.local");i=l.searchParams.get("path")||"",s=l.searchParams.get("origin")?.trim()||""}else if(t.startsWith("/__app-api/"))i=t.slice(10);else return!1;if(!s)return e.writeHead(400,{"Content-Type":"text/plain"}),e.end("app-api: missing origin query param"),!0;if(a.method==="OPTIONS")return e.writeHead(204,{"Access-Control-Allow-Origin":a.headers.origin||"*","Access-Control-Allow-Methods":"GET,POST,PUT,PATCH,DELETE,OPTIONS","Access-Control-Allow-Headers":a.headers["access-control-request-headers"]||"*","Access-Control-Allow-Credentials":"true","Access-Control-Max-Age":"86400"}),e.end(),!0;let n;try{n=new URL(i,s)}catch{return e.writeHead(400,{"Content-Type":"text/plain"}),e.end("app-api: invalid origin or path"),!0}let o=n.protocol==="https:"?Ie:Ee,r={...a.headers};delete r.host,r.host=n.host;let c=o.request({hostname:n.hostname,port:n.port||(n.protocol==="https:"?443:80),path:n.pathname+n.search,method:a.method,headers:r},l=>{let d=Object.keys(l.headers).filter(u=>{let p=u.toLowerCase();return!p.startsWith("access-control-")&&p!=="set-cookie"}).join(", ");e.writeHead(l.statusCode??502,{...l.headers,"access-control-allow-origin":a.headers.origin||"*","access-control-allow-credentials":"true","access-control-expose-headers":d}),l.pipe(e)});return c.on("error",l=>{e.statusCode=502,e.end(`app proxy error: ${l.message}`)}),a.pipe(c),!0}var De=new Set(["tap","keyboard"]),He=2e3,Me=1e3;function Ue(a){return!a||typeof a.type!="string"?!1:a.acquireLock===!0?!0:a.readOnly===!0?!1:De.has(a.type)}var Fe=5e3,We=3600*1e3,je="SOOTSIM_RUNTIME_UPDATE_INTERVAL_MS",$e={".html":"text/html; charset=utf-8",".js":"application/javascript",".cjs":"application/javascript",".mjs":"application/javascript",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".svg":"image/svg+xml",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon",".wasm":"application/wasm",".ttf":"font/ttf",".otf":"font/otf",".woff":"font/woff",".woff2":"font/woff2",".map":"application/json",".txt":"text/plain; charset=utf-8"};function ye(a,e,t){let i;try{let c=L();i=JSON.stringify(c)}catch{i="{}"}let s=e>0?`window.__sootsimBridgePort=${e};`:"",n=t?`window.__sootsimSootbeanOrigin=${JSON.stringify(t)};`:"",o=`<script>window.__sootsimSharedConfig=${i};`+s+n+`window.__sootsimCliVersion=${JSON.stringify(F())};</script>`,r=a.toString("utf8");return r.includes("</head>")?r.replace("</head>",o+"</head>"):r.includes("</body>")?r.replace("</body>",o+"</body>"):o+r}function ve(a){typeof a=="object"&&a!==null&&"unref"in a&&a.unref()}var O=class a{port;openUrlHandler;httpServer=null;wss=null;nextCommandId=1;nextSimNumber=161;sims=new Map;primarySimId=null;pendingCommands=new Map;cliBySentId=new Map;cliSimBySocket=new Map;cliLastCommandAt=new Map;cliIdentityKeyBySocket=new Map;cliLabelBySocket=new Map;restorableSims=new Map;nextCliFallbackId=1;cliIdleTimer=null;agentHost;static CLI_IDLE_TIMEOUT_MS=6e4;static CLI_LEASE_TTL_MS=6e5;static USER_ACTIVE_LEASE_TTL_MS=8e3;static USER_BOOT_LEASE_TTL_MS=6e4;static SIM_RECONNECT_TTL_MS=3e4;static SIM_IDLE_REAP_TTL_MS=30*6e4;preferredPort;portFallbackCount;simIdleReapTtlMs;shouldWriteLockfile;sootbeanOrigin=null;effectivePort=0;startedAt=0;heartbeatTimer=null;wsHeartbeatTimer=null;wsIsAlive=new WeakMap;static WS_HEARTBEAT_INTERVAL_MS=3e4;runtimeUpdateTimer=null;runtimeUpdateInFlight=null;activeRuntimeVersion=null;activeRuntimeDirPath=null;scanCache=null;scanCacheAt=0;inflightScan=null;static SCAN_FRESH_MS=2e3;constructor(e={}){this.preferredPort=e.port||7668,this.port=this.preferredPort,this.shouldWriteLockfile=e.writeLockfile===!0,this.portFallbackCount=Math.max(1,e.portFallbackCount??10),this.openUrlHandler=e.openUrl,this.agentHost=new R({getExcludePorts:e.agentScanExcludes}),this.sootbeanOrigin=e.sootbeanOrigin?.replace(/\/$/,"")||null,this.simIdleReapTtlMs=e.simIdleReapTtlMs??a.SIM_IDLE_REAP_TTL_MS}getAgentHost(){return this.agentHost}reapIdleSimsForTest(e=Date.now()){this.reapIdleSims(e)}start(e){this.startAsync(e)}async startAsync(e){if(this.httpServer||this.wss)return this.effectivePort;this.refreshActiveRuntime();for(let t=0;t<this.portFallbackCount;t++){let i=this.preferredPort+t;try{return await this.bindOnce(i,e?.silent===!0),this.effectivePort=i,this.port=i,this.startedAt=Date.now(),t>0&&!e?.silent&&process.stderr.write(`ws bridge bound to port ${i} (preferred ${this.preferredPort} was taken)
5
- `),this.afterBind(),i}catch(s){if(s?.code!=="EADDRINUSE")throw s;e?.silent||process.stderr.write(`ws bridge port ${i} already in use, trying ${i+1}
6
- `)}}throw new Error(`could not bind ws bridge after ${this.portFallbackCount} attempts starting at ${this.preferredPort}`)}bindOnce(e,t){return new Promise((i,s)=>{let n=Be((c,l)=>this.handleHttpRequest(c,l)),o=!1,r=c=>{if(!o){o=!0;try{n.close()}catch{}this.httpServer=null,this.wss=null,s(c)}};n.once("error",r),n.listen(e,"127.0.0.1",()=>{o||(o=!0,n.removeListener("error",r),n.on("error",c=>{process.stderr.write(`ws bridge http error: ${String(c)}
7
- `)}),this.httpServer=n,this.wss=new Ne({server:n}),this.wireWebSocketServer(),i())})})}wireWebSocketServer(){this.wss&&this.wss.on("connection",(e,t)=>{let i=t.headers.origin,s=i?"sim":"cli",n=null;if(e.on("error",()=>{}),this.wsIsAlive.set(e,!0),e.on("pong",()=>{this.wsIsAlive.set(e,!0)}),this.agentHost.registerSocket(e),s==="sim")n={id:this.allocateSimId(),ws:e,origin:i,connectedAt:Date.now(),lastSeenAt:Date.now(),lastActiveAt:0,recentActions:[]},this.sims.set(n.id,n),this.shouldPromoteSim(n)&&(this.primarySimId=n.id),this.broadcastSimAssignments(),this.broadcastSimClientStates();else{let o=`ws-${this.nextCliFallbackId++}`;this.cliIdentityKeyBySocket.set(e,o)}e.on("message",o=>{let r;try{r=JSON.parse(o.toString())}catch{return}if(!(!r||typeof r!="object")){if(typeof r.type=="string"&&r.type.startsWith("agent:")){this.agentHost.handleMessage(e,r);return}if(r.type==="runtime:list"){let c=B(),l=this.getActiveRuntime(),d={type:"runtime:list:ok",id:r.id,installed:c,active:l.version,activeRuntimeDir:l.runtimeDir};try{e.send(JSON.stringify(d))}catch{}return}if(r.type==="runtime:use"){let c=typeof r.version=="string"?r.version:"";if(!B().includes(c)){try{e.send(JSON.stringify({type:"runtime:use:error",id:r.id,error:`runtime ${c||"(missing)"} is not installed`}))}catch{}return}let d=this.setActiveRuntime(c);try{e.send(JSON.stringify({type:"runtime:use:ok",id:r.id,version:d.version,runtimeDir:d.runtimeDir}))}catch{}return}if(r.type==="runtime:get"){let c=this.getActiveRuntime();try{e.send(JSON.stringify({type:"runtime:get:ok",id:r.id,active:c.version,activeRuntimeDir:c.runtimeDir}))}catch{}return}if(s==="sim"){if(n&&(n.lastSeenAt=Date.now()),r.type==="bridge:register"&&n){let d=r,u=this.tryRestoreSimId(n,d.simId);n.url=d.url,n.title=d.title,n.userAgent=d.userAgent,typeof d.kind=="string"&&d.kind.trim()&&(n.kind=d.kind.trim()),d.meta&&typeof d.meta=="object"&&(n.meta=d.meta);let p=this.primarySimId!==n.id&&this.shouldPromoteSim(n);p&&(this.primarySimId=n.id),(u||p)&&(this.broadcastSimAssignments(),this.broadcastSimClientStates());return}if(r.type==="bridge:user-focus-state"&&n){let d=r;this.updateUserFocusLease(n,d);return}if(r.type==="bridge:user-interact"&&n){this.updateUserActivity(n);return}if(r.type==="bridge:write-shared-config"){let d=r.patch&&typeof r.patch=="object"?r.patch:null;if(!d)return;let u;try{u=W(d)}catch(m){process.stderr.write(`sootsim: bridge:write-shared-config failed: ${m instanceof Error?m.message:String(m)}
8
- `);return}let p=JSON.stringify({type:"bridge:shared-config-changed",config:u});for(let m of this.sims.values())if(m.ws.readyState===h.OPEN)try{m.ws.send(p)}catch{}return}if(r.type==="bridge:open-path"){let d=typeof r.path=="string"?r.path:"",u=typeof r.line=="number"&&Number.isFinite(r.line)?r.line:void 0,p=typeof r.column=="number"&&Number.isFinite(r.column)?r.column:void 0;d&&this.openPathInEditor(d,u,p);return}if(r.type==="bridge:boot-clients"&&n){let d=[];for(let[p,m]of this.cliSimBySocket)m===n.id&&d.push(p);for(let p of d){this.cliSimBySocket.delete(p);try{p.close(1e3,"booted by sim")}catch{}}let u=!!n.cliLease;n.cliLease={kind:"user-active",cliIdentityKey:"__user-active__",cliLabel:"active user",expiresAt:Date.now()+a.USER_BOOT_LEASE_TTL_MS},process.stderr.write(`sootsim booted ${d.length} cli client(s)${u?" (overrode prior lease)":""}; held sim for user [${n.id}]
9
- `),this.recordSimAction(n.id,"sim booted cli clients"),this.broadcastSimClientStates();return}let c=this.pendingCommands.get(r.id);if(c){this.pendingCommands.delete(r.id),r.error?c.reject(new Error(r.error)):c.resolve(r.result);return}let l=this.cliBySentId.get(r.id);if(l&&(this.cliBySentId.delete(r.id),l.ws.readyState===h.OPEN)){let d=this.getOtherCliIdentityCount(l.ws,l.simId),u=d>0?{...r,id:l.originalId,i:d}:{...r,id:l.originalId};l.ws.send(JSON.stringify(u))}return}(async()=>{this.cliLastCommandAt.set(e,Date.now());try{if(r.type==="bridge:bye"){let p=this.cliSimBySocket.delete(e);this.cliLastCommandAt.delete(e),this.cliIdentityKeyBySocket.delete(e),this.cliLabelBySocket.delete(e);for(let[m,f]of this.cliBySentId)f.ws===e&&this.cliBySentId.delete(m);p&&this.broadcastSimClientStates();return}if(r.type==="bridge:hello"){let p=typeof r.cliIdentityKey=="string"&&r.cliIdentityKey.trim()?r.cliIdentityKey.trim():this.cliIdentityKeyBySocket.get(e)||`ws-${this.nextCliFallbackId++}`;this.cliIdentityKeyBySocket.set(e,p),typeof r.cliLabel=="string"&&r.cliLabel.trim()&&this.cliLabelBySocket.set(e,r.cliLabel.trim()),e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,result:{cliIdentityKey:p,leaseTtlMs:a.CLI_LEASE_TTL_MS,leasing:!0}}));return}if(r.type==="bridge:list-sims"){e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,result:this.listSims()}));return}if(r.type==="bridge:open"){if(typeof r.url!="string"||!r.url)throw new Error("bridge:open requires a url");await this.openUrl(r.url,{newWindow:r.newWindow===!0}),e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,result:{ok:!0,url:r.url}}));return}if(r.type==="bridge:claim"){let p=await this.waitForSim(r.simId),m=this.tryAcquireLease(e,p,{force:r.force===!0});if(!m.granted){e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,error:`sim ${p.id} is locked by another cli`,o:m.lock}));return}this.setCliSimTarget(e,p.id),this.recordSimAction(p.id,m.bootedCount>0?`cli force-claimed sim (booted ${m.bootedCount})`:"cli claimed sim"),e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,result:{simId:p.id,lockedBy:m.lease.cliIdentityKey,lockExpiresAt:m.lease.expiresAt,bootedCount:m.bootedCount}}));return}let c=await this.waitForSim(r.simId);if(Ue(r)){let p=this.tryAcquireLease(e,c);if(!p.granted){e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,error:`sim ${c.id} is locked by another cli \u2014 use \`sootsim claim ${c.id} --force\` or \`sootsim open --new\``,o:p.lock}));return}}else this.ensureCliIdentityKey(e);this.setCliSimTarget(e,c.id),this.recordSimAction(c.id,this.describeForwardedCommand(r));let l=this.nextCommandId++;this.cliBySentId.set(l,{simId:c.id,ws:e,originalId:r.id});let{simId:d,...u}=r;if(c.ws.send(JSON.stringify({...u,id:l})),u.type==="close"){this.cliBySentId.delete(l),e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,result:{requested:!0,simId:c.id}}));let p=c.ws,m=setTimeout(()=>{this.closeSimSocketFromHost(p)},He);ve(m)}}catch(c){e.readyState===h.OPEN&&e.send(JSON.stringify({id:r.id,error:c instanceof Error?c.message:String(c)}))}})()}}),e.on("close",()=>{if(this.agentHost.unregisterSocket(e),s==="sim"&&n){this.rememberDisconnectedSim(n),this.primarySimId===n.id&&(this.primarySimId=this.getOpenSim()?.id??null);for(let[o,r]of this.pendingCommands)r.simId===n.id&&(r.reject(new Error("sim disconnected")),this.pendingCommands.delete(o));for(let[o,r]of this.cliBySentId)r.simId===n.id&&(r.ws.readyState===h.OPEN&&r.ws.send(JSON.stringify({id:r.originalId,error:"sim disconnected before responding"})),this.cliBySentId.delete(o));this.broadcastSimAssignments(),this.broadcastSimClientStates()}else if(s==="cli"){let o=this.cliSimBySocket.delete(e);this.cliLastCommandAt.delete(e),this.cliIdentityKeyBySocket.delete(e),this.cliLabelBySocket.delete(e);for(let[r,c]of this.cliBySentId)c.ws===e&&this.cliBySentId.delete(r);o&&this.broadcastSimClientStates()}})})}afterBind(){if(process.stderr.write(`ws bridge listening on port ${this.port}
10
- `),this.cliIdleTimer=setInterval(()=>this.sweepIdleCliClients(),3e4),this.cliIdleTimer.unref(),this.wsHeartbeatTimer=setInterval(()=>this.sweepDeadWebSockets(),a.WS_HEARTBEAT_INTERVAL_MS),this.wsHeartbeatTimer.unref(),this.shouldWriteLockfile){try{if(T(),!q(this.buildLockfileSnapshot()))throw new Error("another sootsim daemon wrote the lockfile during startup \u2014 aborting")}catch(e){throw process.stderr.write(`ws bridge failed to claim daemon lockfile: ${String(e)}
11
- `),e}this.heartbeatTimer=setInterval(()=>{try{this.writeLockfileSnapshot()}catch{}},Fe),this.heartbeatTimer.unref(),this.startRuntimeUpdater()}this.agentHost.seedOnBoot()}bootstrapping=!0;buildLockfileSnapshot(){return{schema:1,pid:process.pid,platform:process.platform,bridgePort:this.effectivePort,runtimePort:this.effectivePort,activeRuntime:this.activeRuntimeVersion,activeRuntimeDir:this.activeRuntimeDirPath,startedAt:this.startedAt,heartbeatAt:Date.now(),bootstrapping:this.bootstrapping}}writeLockfileSnapshot(){G(this.buildLockfileSnapshot())}refreshActiveRuntime(){this.activeRuntimeVersion=j(),this.activeRuntimeDirPath=J()}runServerScan(){if(this.inflightScan)return this.inflightScan;let e=this.effectivePort>0?[this.effectivePort]:[];return this.inflightScan=E({excludePorts:e,buildIconProxyUrl:t=>`/__bundle-proxy?url=${encodeURIComponent(t)}`}).then(t=>(this.scanCache=t,this.scanCacheAt=Date.now(),t)).catch(t=>{let i=t instanceof Error?t.message:String(t);return console.error("[sootsim] /__server-scan failed:",i),this.scanCache??[]}).finally(()=>{this.inflightScan=null}),this.inflightScan}handleServerScan(e){let t=s=>{e.writeHead(200,{"Content-Type":"application/json; charset=utf-8","Cache-Control":"no-store"}),e.end(JSON.stringify(s))},i=Date.now()-this.scanCacheAt;if(this.scanCache&&i<a.SCAN_FRESH_MS){t(this.scanCache);return}if(this.scanCache){t(this.scanCache),this.runServerScan().catch(()=>{});return}this.runServerScan().then(s=>t(s))}resolveRuntimeUpdateIntervalMs(){let e=Number(process.env[je]);return Number.isFinite(e)&&e>0?Math.max(100,Math.round(e)):We}startRuntimeUpdater(){if(!this.shouldWriteLockfile||this.runtimeUpdateTimer)return;this.runRuntimeUpdate("startup");let e=this.resolveRuntimeUpdateIntervalMs();this.runtimeUpdateTimer=setInterval(()=>{this.runRuntimeUpdate("periodic")},e),this.runtimeUpdateTimer.unref()}runRuntimeUpdate(e){return this.runtimeUpdateInFlight?this.runtimeUpdateInFlight:(this.runtimeUpdateInFlight=(async()=>{try{e==="startup"&&process.stderr.write(`sootsim: checking for runtime updates\u2026
12
- `);let t=await Z();if(!t.updated||!t.latestVersion){e==="startup"&&process.stderr.write(`sootsim: runtime ${this.activeRuntimeVersion??"(none)"} is current
13
- `);return}let i=this.setActiveRuntime(t.latestVersion);process.stderr.write(`sootsim runtime updated to ${i.version} (${e})
14
- `)}catch(t){process.stderr.write(`sootsim runtime update failed (${e}): ${t instanceof Error?t.message:String(t)}
15
- `)}finally{if(this.runtimeUpdateInFlight=null,e==="startup"&&this.bootstrapping){if(this.bootstrapping=!1,this.shouldWriteLockfile&&this.httpServer)try{this.writeLockfileSnapshot()}catch{}process.stderr.write(`sootsim: ready
16
- `)}}})(),this.runtimeUpdateInFlight)}setActiveRuntime(e){if($(e),this.refreshActiveRuntime(),this.shouldWriteLockfile&&this.httpServer)try{this.writeLockfileSnapshot()}catch{}let t=JSON.stringify({type:"runtime:changed",version:e,runtimeDir:this.activeRuntimeDirPath});for(let i of this.sims.values())if(i.ws.readyState===h.OPEN)try{i.ws.send(t)}catch{}return{version:e,runtimeDir:this.activeRuntimeDirPath}}getActiveRuntime(){return{version:this.activeRuntimeVersion,runtimeDir:this.activeRuntimeDirPath}}removeLockfile(){if(this.shouldWriteLockfile)try{N()}catch{}}handleHttpRequest(e,t){if(t.setHeader("Cross-Origin-Opener-Policy","same-origin"),t.setHeader("Cross-Origin-Embedder-Policy","credentialless"),t.setHeader("Cross-Origin-Resource-Policy","cross-origin"),t.setHeader("Document-Policy","js-profiling"),he(e.url)){ge(e,t);return}if(fe(e.url)&&Se(e,t))return;let i=(e.method||"GET").toUpperCase();if(i!=="GET"&&i!=="HEAD"){t.writeHead(405,{Allow:"GET, HEAD"}),t.end("method not allowed");return}let s=new URL(e.url||"/","http://localhost");if(s.pathname==="/__bundle-proxy"){let l=s.searchParams.get("url");if(!l){t.writeHead(400,{"Content-Type":"text/plain"}),t.end("bundle-proxy: missing url query param");return}let d;try{d=new URL(l)}catch{t.writeHead(400,{"Content-Type":"text/plain"}),t.end("bundle-proxy: invalid url");return}let u=d.hostname;if(!(u==="localhost"||u==="127.0.0.1"||u==="::1"||u.endsWith(".localhost"))){t.writeHead(403,{"Content-Type":"text/plain"}),t.end("bundle-proxy: only loopback targets allowed");return}(async()=>{try{let m=await fetch(d.toString(),{redirect:"follow"}),f={},y=m.headers.get("content-type");if(y&&(f["Content-Type"]=y),f["Cache-Control"]="no-store",t.writeHead(m.status,f),!m.body){t.end();return}let g=m.body.getReader();for(;;){let{done:v,value:b}=await g.read();if(v)break;t.write(Buffer.from(b))}t.end()}catch(m){t.writeHead(502,{"Content-Type":"text/plain"}),t.end(`bundle-proxy: upstream fetch failed: ${m instanceof Error?m.message:String(m)}`)}})();return}if(s.pathname==="/__server-scan"){this.handleServerScan(t);return}if(s.pathname==="/healthz"){t.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-store"}),t.end(JSON.stringify({ok:!0,pid:process.pid,platform:process.platform,bridgePort:this.effectivePort,runtimePort:this.effectivePort,activeRuntime:this.activeRuntimeVersion,startedAt:this.startedAt,uptimeMs:this.startedAt>0?Date.now()-this.startedAt:0}));return}if(s.pathname==="/__sootsim/shared-config"){if(t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Cache-Control","no-store"),i==="GET"||i==="HEAD"){let l="{}";try{l=JSON.stringify(L())}catch{}t.writeHead(200,{"Content-Type":"application/json"}),i==="HEAD"?t.end():t.end(l);return}t.writeHead(405,{Allow:"GET, HEAD"}),t.end("method not allowed (use the bridge over WS for writes)");return}this.refreshActiveRuntime();let n=this.activeRuntimeDirPath;if(!n){t.writeHead(503,{"Content-Type":"text/plain; charset=utf-8"}),t.end("sootsim: no active runtime installed. run `sootsim runtime install` to fetch one.");return}let o=s.pathname;if(o==="/runtime"||o==="/runtime/"?o="/":o.startsWith("/runtime/")?o=o.slice(8):o==="/sootsim"||o==="/sootsim/"?o="/":o.startsWith("/sootsim/")&&(o=o.slice(8)),(o===""||o==="/")&&(o="/index.html"),o.includes("\0")){t.writeHead(400),t.end("bad request");return}if(process.platform!=="win32"&&o.includes("\\")){t.writeHead(400),t.end("bad request");return}for(let l of o.split("/"))if(l===".."){t.writeHead(403),t.end("forbidden");return}let r=S.resolve(n,"."+o),c=n.endsWith(S.sep)?n:n+S.sep;if(!r.startsWith(c)&&r!==n){t.writeHead(403),t.end("forbidden");return}w.realpath(r,(l,d)=>{let u=l?r:d,p=u.endsWith(S.sep)?u:u+S.sep;if(!l){let m=(()=>{try{let f=w.realpathSync(n);return f.endsWith(S.sep)?f:f+S.sep}catch{return c}})();if(!p.startsWith(m)&&u+S.sep!==m){t.writeHead(403),t.end("forbidden");return}}w.stat(u,(m,f)=>{if(m||!f?.isFile()){let b=S.extname(o).toLowerCase();if(b&&b!==".html"){t.writeHead(404),t.end("not found");return}if(o.startsWith("/__")||o.startsWith("/api/")||o==="/api"){t.writeHead(404,{"Content-Type":"text/plain; charset=utf-8"}),t.end("not found");return}let x=S.join(n,"index.html");w.readFile(x,(be,we)=>{if(be){t.writeHead(404),t.end("not found");return}if(t.writeHead(200,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}),i==="HEAD"){t.end();return}t.end(ye(we,this.effectivePort,this.sootbeanOrigin))});return}let y=S.extname(u).toLowerCase(),g=$e[y]||"application/octet-stream";if(t.writeHead(200,{"Content-Type":g,"Cache-Control":"no-store"}),i==="HEAD"){t.end();return}if(y===".html"){w.readFile(u,(b,x)=>{if(b){try{t.end()}catch{}return}t.end(ye(x,this.effectivePort,this.sootbeanOrigin))});return}let v=w.createReadStream(u);v.pipe(t),v.on("error",()=>{try{t.end()}catch{}})})})}sweepIdleCliClients(){let e=Date.now(),t=!1;for(let[i,s]of this.cliSimBySocket){let n=this.cliLastCommandAt.get(i)??0;if(!(e-n<a.CLI_IDLE_TIMEOUT_MS)){this.cliSimBySocket.delete(i),this.cliLastCommandAt.delete(i);for(let[o,r]of this.cliBySentId)r.ws===i&&this.cliBySentId.delete(o);try{i.close(1e3,"idle timeout")}catch{}t=!0}}t&&this.broadcastSimClientStates(),this.sweepRestorableSims(e),this.reapIdleSims(e)}reapIdleSims(e=Date.now()){let t=new Set(this.cliSimBySocket.values());for(let i of this.sims.values()){if(i.id===this.primarySimId||t.has(i.id)||this.getActiveLease(i))continue;let s=Math.max(i.lastActiveAt,i.connectedAt);e-s<this.simIdleReapTtlMs||this.closeSimSocketFromHost(i.ws)}}sweepDeadWebSockets(){if(this.wss)for(let e of this.wss.clients){if(e.readyState!==h.OPEN)continue;if(this.wsIsAlive.get(e)===!1){try{e.terminate()}catch{}continue}this.wsIsAlive.set(e,!1);try{e.ping()}catch{try{e.terminate()}catch{}}}}closeSimSocketFromHost(e){if(e.readyState!==h.OPEN)return;try{e.close(4001,U)}catch{try{e.terminate()}catch{}return}let t=setTimeout(()=>{if(e.readyState!==h.CLOSED)try{e.terminate()}catch{}},Me);ve(t)}listSims(){return Array.from(this.sims.values()).sort((e,t)=>e.id===this.primarySimId?-1:t.id===this.primarySimId?1:e.connectedAt-t.connectedAt).map(e=>this.describeSim(e))}async sendCommand(e){let t=await this.waitForSim(e.simId),i=this.nextCommandId++;return new Promise((s,n)=>{let o=setTimeout(()=>{this.pendingCommands.delete(i),this.broadcastSimClientStates(),n(new Error("command timed out after 30s"))},3e4);this.pendingCommands.set(i,{simId:t.id,resolve:l=>{clearTimeout(o),this.pendingCommands.delete(i),this.broadcastSimClientStates(),s(l)},reject:l=>{clearTimeout(o),this.pendingCommands.delete(i),this.broadcastSimClientStates(),n(l)}}),this.broadcastSimClientStates();let{simId:r,...c}=e;t.ws.send(JSON.stringify({...c,id:i}))})}async evaluate(e,t){return this.sendCommand({type:"evaluate",code:e,simId:t})}async focusSim(e){return this.sendCommand({type:"focus",simId:e})}async closeSim(e){return this.sendCommand({type:"close",simId:e})}async openPathInEditor(e,t,i){let s=t!=null?`:${t}${i!=null?`:${i}`:""}`:"",n=`${e}${s}`,o=(c,l)=>new Promise(d=>{try{let u=Le(c,l,{detached:!0,stdio:"ignore"}),p=!1;u.on("error",()=>{p||(p=!0,d(!1))}),u.on("spawn",()=>{p||(p=!0,u.unref(),d(!0))})}catch{d(!1)}}),r=process.env.REACT_EDITOR||process.env.EDITOR;if(r){let c=r.split(" ").filter(Boolean);if(c.length&&await o(c[0],[...c.slice(1),"-g",n]))return}await o("cursor",["-g",n])||await o("code",["-g",n])||await this.openUrl(e)}async openUrl(e,t={}){if(this.openUrlHandler){await this.openUrlHandler(e,t);return}await Y(e,t)}async close(){if(this.cliIdleTimer&&(clearInterval(this.cliIdleTimer),this.cliIdleTimer=null),this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.wsHeartbeatTimer&&(clearInterval(this.wsHeartbeatTimer),this.wsHeartbeatTimer=null),this.runtimeUpdateTimer&&(clearInterval(this.runtimeUpdateTimer),this.runtimeUpdateTimer=null),this.shouldWriteLockfile)try{N()}catch{}this.effectivePort=0,this.startedAt=0,this.agentHost.close();for(let[i,s]of this.pendingCommands)s.reject(new Error("server closing")),this.pendingCommands.delete(i);for(let i of this.sims.values())i.ws.close();this.sims.clear(),this.primarySimId=null;let e=this.wss,t=this.httpServer;if(this.wss=null,this.httpServer=null,e)try{e.close()}catch{}if(t)try{t.close()}catch{}}describeSim(e){let t;try{t=e.ws.readyState}catch{t=h.CLOSED}let i=this.getActiveLease(e);return{id:e.id,origin:e.origin,url:e.url,title:e.title,userAgent:e.userAgent,connectedAt:e.connectedAt,lastSeenAt:e.lastSeenAt,lastActiveAt:e.lastActiveAt||void 0,isPrimary:e.id===this.primarySimId,readyState:t===h.OPEN?"open":t===h.CLOSING?"closing":"closed",attachedCliCount:this.getAttachedCliCount(e.id),lockedBy:i?i.cliLabel||i.cliIdentityKey:void 0,lockedByKind:i?i.kind:void 0,lockExpiresAt:i?i.expiresAt:void 0,userFocused:e.userFocused||void 0,userVisible:e.userVisible,visibilityState:e.visibilityState,documentFocused:e.documentFocused,kind:e.kind,meta:e.meta}}getActiveLease(e){let t=e.cliLease;return t?Date.now()>=t.expiresAt?(e.cliLease=void 0,null):t:null}tryAcquireLease(e,t,i={}){let s=this.cliIdentityKeyBySocket.get(e)??(()=>{let u=`ws-${this.nextCliFallbackId++}`;return this.cliIdentityKeyBySocket.set(e,u),u})(),n=this.cliLabelBySocket.get(e),o=Date.now(),r=this.getActiveLease(t),c=r&&r.cliIdentityKey===s,l=0;if(r&&!c&&!i.force)return{granted:!1,lease:r,lock:{by:r.cliLabel||r.cliIdentityKey,expiresInMs:Math.max(0,r.expiresAt-o)},bootedCount:0};if(r&&!c&&i.force)for(let[u,p]of this.cliSimBySocket){if(p!==t.id)continue;let m=this.cliIdentityKeyBySocket.get(u);if(m&&m!==s){this.cliSimBySocket.delete(u);try{u.close(1e3,"lease claimed by another cli")}catch{}l++}}let d={kind:"cli",cliIdentityKey:s,cliLabel:n,expiresAt:o+a.CLI_LEASE_TTL_MS};return t.cliLease=d,{granted:!0,lease:d,bootedCount:l}}updateUserFocusLease(e,t){let i=t.focused===!0,s=typeof t.visible=="boolean"?t.visible:void 0,n=typeof t.visibilityState=="string"?t.visibilityState:void 0,o=typeof t.documentFocused=="boolean"?t.documentFocused:void 0;e.userFocused===i&&e.userVisible===s&&e.visibilityState===n&&e.documentFocused===o||(e.userFocused=i,e.userVisible=s,e.visibilityState=n,e.documentFocused=o,this.broadcastSimClientStates())}updateUserActivity(e){let t=this.getActiveLease(e);if(t&&t.kind==="cli")return;let s=Date.now()+a.USER_ACTIVE_LEASE_TTL_MS,n=t&&t.kind==="user-active"?Math.max(t.expiresAt,s):s;e.cliLease={kind:"user-active",cliIdentityKey:"__user-active__",cliLabel:"active user",expiresAt:n},this.broadcastSimClientStates()}ensureCliIdentityKey(e){let t=this.cliIdentityKeyBySocket.get(e);if(t)return t;let i=`ws-${this.nextCliFallbackId++}`;return this.cliIdentityKeyBySocket.set(e,i),i}getOpenSim(e){if(e){let s=this.sims.get(e);return s?.ws.readyState===h.OPEN?s:null}let t=this.primarySimId!=null?this.sims.get(this.primarySimId):null;if(t?.ws.readyState===h.OPEN&&t.url)return t;let i=null;for(let s of this.sims.values())if(s.ws.readyState===h.OPEN){if(s.url)return s;i??=s}return t?.ws.readyState===h.OPEN?t:i}async waitForSim(e,t={}){let i=t.attempts??10,s=t.intervalMs??200;for(let n=0;n<i;n++){let o=this.getOpenSim(e);if(o)return o;await new Promise(r=>setTimeout(r,s))}throw new Error(e?`no sim connected with id ${e}`:"no sim connected")}shouldPromoteSim(e){let t=this.primarySimId?this.sims.get(this.primarySimId):null;if(!e.url)return!t;let i=t?.ws.readyState===h.OPEN;if(!t||!i||!t.url)return!0;let s=e.origin?.includes(":5173"),n=t.origin?.includes(":5173");return!!s||!n}broadcastSimAssignments(){for(let e of this.sims.values())e.ws.readyState===h.OPEN&&e.ws.send(JSON.stringify({type:"bridge:welcome",simId:e.id,isPrimary:e.id===this.primarySimId}))}broadcastSimClientStates(){for(let e of this.sims.values()){if(e.ws.readyState!==h.OPEN)continue;let t=this.getActiveLease(e),i={type:"bridge:client-state",attachedCliCount:this.getAttachedCliCount(e.id),activeAgentCommandCount:this.getActiveAgentCommandCount(e.id),recentActions:e.recentActions,lockedBy:t?t.cliLabel||t.cliIdentityKey:void 0,lockedByKind:t?t.kind:void 0,lockExpiresAt:t?t.expiresAt:void 0,userFocused:e.userFocused||void 0,userVisible:e.userVisible,visibilityState:e.visibilityState,documentFocused:e.documentFocused};e.ws.send(JSON.stringify(i))}}setCliSimTarget(e,t){let i=this.cliSimBySocket.get(e);i!==t&&(this.cliSimBySocket.set(e,t),this.recordSimAction(t,i?"cli switched sims":"cli connected",!1),this.broadcastSimClientStates())}recordSimAction(e,t,i=!0){let s=t?.trim();if(!s)return;let n=this.sims.get(e);if(!n)return;let o=Date.now();n.lastActiveAt=o,n.recentActions=[{label:s,at:o},...n.recentActions.filter(r=>r.label!==s)].slice(0,4),i&&this.broadcastSimClientStates()}describeForwardedCommand(e){switch(e?.type){case"evaluate":return"evaluated page state";case"screenshot":return"captured screenshot";case"tap":return"sent tap event";case"keyboard":return e?.action==="type"?"typed text":"used keyboard";case"tree":return"dumped tree";case"focus":return"focused sim";case"close":return"requested close";default:return typeof e?.type=="string"?e.type:null}}getAttachedCliCount(e){let t=new Set;for(let[i,s]of this.cliSimBySocket){if(s!==e||i.readyState!==h.OPEN)continue;let n=this.cliIdentityKeyBySocket.get(i);t.add(n??`ws-unknown-${t.size}`)}return t.size}getOtherCliIdentityCount(e,t){let i=this.cliIdentityKeyBySocket.get(e),s=new Set;for(let[n,o]of this.cliSimBySocket){if(o!==t||n.readyState!==h.OPEN)continue;let r=this.cliIdentityKeyBySocket.get(n);r&&r===i||s.add(r??`ws-unknown-${s.size}`)}return s.size}getActiveAgentCommandCount(e){let t=0;for(let i of this.pendingCommands.values())i.simId===e&&t++;return t}allocateSimId(){for(;;){let e=this.nextSimNumber.toString(16);if(this.nextSimNumber++,!this.sims.has(e)&&!this.restorableSims.has(e))return e}}tryRestoreSimId(e,t){let i=t?.trim();if(!i||i===e.id)return!1;let s=this.sims.get(i);if(s&&s!==e&&s.ws.readyState===h.OPEN)return!1;let n=this.getRestorableSimState(i),o=e.id;this.sims.delete(o),e.id=i,n&&(e.recentActions=n.recentActions.map(r=>({...r})),e.lastActiveAt=n.lastActiveAt,e.cliLease=n.cliLease?{...n.cliLease}:void 0,this.restorableSims.delete(i)),this.sims.set(e.id,e),this.primarySimId===o&&(this.primarySimId=e.id);for(let[r,c]of this.cliSimBySocket)c===o&&this.cliSimBySocket.set(r,e.id);return!0}rememberDisconnectedSim(e){let t=this.getActiveLease(e);this.restorableSims.set(e.id,{recentActions:e.recentActions.map(i=>({...i})),lastActiveAt:e.lastActiveAt,cliLease:t&&t.kind==="cli"?{...t}:void 0,expiresAt:Date.now()+a.SIM_RECONNECT_TTL_MS}),this.sims.delete(e.id)}getRestorableSimState(e){let t=this.restorableSims.get(e);return t?t.expiresAt<=Date.now()?(this.restorableSims.delete(e),null):(t.cliLease&&t.cliLease.expiresAt<=Date.now()&&(t.cliLease=void 0),t):null}sweepRestorableSims(e=Date.now()){for(let[t,i]of this.restorableSims)if(!(i.expiresAt>e)){this.restorableSims.delete(t);for(let[s,n]of this.cliSimBySocket)n===t&&this.cliSimBySocket.delete(s)}}resetServerState(){this.cliIdleTimer&&(clearInterval(this.cliIdleTimer),this.cliIdleTimer=null),this.wsHeartbeatTimer&&(clearInterval(this.wsHeartbeatTimer),this.wsHeartbeatTimer=null),this.runtimeUpdateTimer&&(clearInterval(this.runtimeUpdateTimer),this.runtimeUpdateTimer=null);let e=this.wss,t=this.httpServer;if(this.wss=null,this.httpServer=null,e)try{e.close()}catch{}if(t)try{t.close()}catch{}}};function Je(){let a=process.env.XPC_SERVICE_NAME||"";return!!(a.includes("dev.sootsim.daemon")||a.includes("dev.sootsim.server")||process.env.INVOCATION_ID)}async function wt(a,e={}){(a.includes("--help")||a.includes("-h"))&&(console.log(`
17
- sootsim server \u2014 run the sootsim bridge daemon in the foreground
18
-
19
- hosts the WS bridge that CLI commands talk to. once running, any sootsim
20
- renderer (browser, electron, headless playwright) that connects to port 7668
21
- becomes drivable from 'sootsim describe', 'sootsim tap', etc.
22
-
23
- usage:
24
- sootsim server [options]
25
-
26
- options:
27
- --port <n> bridge port (defaults to ${7668})
28
- --quiet suppress per-connection logging
29
-
30
- examples:
31
- sootsim server
32
- sootsim server --port 7668 --quiet
33
- `),process.exit(0));let t=a.indexOf("--port"),i=t>=0&&a[t+1]?Number(a[t+1]):e.port??7668;Number.isNaN(i)&&(console.error(` invalid --port value: ${a[t+1]}`),process.exit(1));let s=a.includes("--quiet")||a.includes("-q"),n=K();n&&V(n)&&(console.error(` a sootsim daemon is already running (pid ${n.pid}, port ${n.bridgePort})`),console.error(" stop it with 'sootsim daemon stop' first"),process.exit(1)),T();let o=await Q(),r=new O({port:i,writeLockfile:!0,sootbeanOrigin:o}),c=await r.startAsync({silent:s}),l=Date.now(),d=f=>{s||process.stdout.write(`${f}
34
- `)},u=new Set,p=setInterval(()=>{let f=r.listSims(),y=new Set(f.map(g=>g.id));for(let g of f)if(!u.has(g.id)){let v=g.title||g.url||g.origin||"(unknown)";d(` + ${g.id} ${v}`)}for(let g of u)y.has(g)||d(` - ${g}`);u.clear();for(let g of y)u.add(g)},500);z({event:"daemon_heartbeat",properties:{bridge_port:c,under_daemon:Je(),platform:process.platform,subsource:"daemon"}}),X(),d(`sootsim bridge listening on ws://localhost:${c} (runtime http on same port)`),c!==i&&d(` (preferred port ${i} was taken \u2014 fell back to ${c})`),d(" ready for browser, electron, or headless playwright sims to connect"),d(" (ctrl-c to stop)");let m=async f=>{clearInterval(p),d(`
35
- ${f} received \u2014 shutting down after ${Math.round((Date.now()-l)/1e3)}s`);try{await r.close()}catch{}process.exit(0)};process.on("SIGINT",()=>m("SIGINT")),process.on("SIGTERM",()=>m("SIGTERM")),process.on("SIGHUP",()=>m("SIGHUP")),process.on("exit",()=>{try{r.removeLockfile()}catch{}}),await new Promise(()=>{})}export{wt as runServer};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-RF4R2U46.js";import"./chunk-DUUSJDES.js";import"./chunk-4K7BH2D4.js";import"./chunk-RIXUH3NK.js";import"./chunk-SFGUPL2X.js";import"./chunk-XQ2OBHBE.js";import"./chunk-EQCKGC4B.js";import"./chunk-IILJQCZA.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";export{a as runSetupRepo};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-73UZXB4B.js";import"./chunk-SQZAC7C4.js";import"./chunk-BKBL6K2G.js";export{a as settingsStore};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b}from"./chunk-RIXUH3NK.js";import"./chunk-SFGUPL2X.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";export{b as flushCliTelemetry,a as trackCliEvent};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.84 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c}from"./chunk-3HXQ7MJK.js";import"./chunk-4OWVPRZV.js";import"./chunk-67ZZ2CM5.js";import"./chunk-7NWNTUJF.js";import"./chunk-RIXUH3NK.js";import"./chunk-SFGUPL2X.js";import"./chunk-XQ2OBHBE.js";import"./chunk-EQCKGC4B.js";import"./chunk-IILJQCZA.js";import"./chunk-D4HUVLZR.js";import"./chunk-BKBL6K2G.js";export{a as resolveDefaultUploadOrigin,b as resolvePublicPreviewOrigin,c as runUpload};