sootsim 0.1.85 → 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 (145) hide show
  1. package/dist-cli/bin.js +3 -3
  2. package/dist-cli/chunks/{agent-T3DUH5YJ.js → agent-S3WLX5Z4.js} +2 -2
  3. package/dist-cli/chunks/{agent-wrapper-NSBF4THI.js → agent-wrapper-PFPEQTPG.js} +2 -2
  4. package/dist-cli/chunks/{assert-X3F7TRCZ.js → assert-RQD66YGE.js} +2 -2
  5. package/dist-cli/chunks/auto-bootstrap-PWF7OYCV.js +2 -0
  6. package/dist-cli/chunks/beta-2HH7F2DQ.js +2 -0
  7. package/dist-cli/chunks/{chunk-WGDL5V6C.js → chunk-2IYMBWHL.js} +1 -1
  8. package/dist-cli/chunks/{chunk-5DIGWOY7.js → chunk-3GCSX5H5.js} +1 -1
  9. package/dist-cli/chunks/{chunk-OISHLFON.js → chunk-3TNIXR6J.js} +1 -1
  10. package/dist-cli/chunks/{chunk-5N3V7OCG.js → chunk-535UNERF.js} +2 -2
  11. package/dist-cli/chunks/{chunk-G7CIZ5S3.js → chunk-5DFVKWYQ.js} +3 -3
  12. package/dist-cli/chunks/{chunk-GTAD6IUV.js → chunk-5NW6W7YF.js} +1 -1
  13. package/dist-cli/chunks/{chunk-EKXK3SWK.js → chunk-5ZYANOOI.js} +2 -2
  14. package/dist-cli/chunks/{chunk-BZL6D4TV.js → chunk-6A7IWFXR.js} +63 -62
  15. package/dist-cli/chunks/{chunk-BHZJ6RIH.js → chunk-6LG7WJMD.js} +2 -2
  16. package/dist-cli/chunks/{chunk-EFM53PZ5.js → chunk-7YZHI7V6.js} +1 -1
  17. package/dist-cli/chunks/{chunk-QLJNSOS7.js → chunk-CMAANHYQ.js} +1 -1
  18. package/dist-cli/chunks/{chunk-KUSQ4NNJ.js → chunk-CQAQTU5K.js} +1 -1
  19. package/dist-cli/chunks/{chunk-AFNDVS4E.js → chunk-CSJS4MRN.js} +2 -2
  20. package/dist-cli/chunks/{chunk-7LKUN46F.js → chunk-DKW7Q4F3.js} +2 -2
  21. package/dist-cli/chunks/{chunk-ZO3VHP6W.js → chunk-EDWDFOPL.js} +1 -1
  22. package/dist-cli/chunks/{chunk-EBEL6TTJ.js → chunk-FF5KD3BS.js} +2 -2
  23. package/dist-cli/chunks/{chunk-BESAZ2HA.js → chunk-FO52BFW4.js} +2 -2
  24. package/dist-cli/chunks/chunk-I3JMONYJ.js +2 -0
  25. package/dist-cli/chunks/{chunk-UYRGCJ4N.js → chunk-JB467MUR.js} +45 -45
  26. package/dist-cli/chunks/{chunk-SBGOUA6F.js → chunk-JBYW57OA.js} +2 -2
  27. package/dist-cli/chunks/{chunk-NVTL3JQG.js → chunk-JITAVV2G.js} +1 -1
  28. package/dist-cli/chunks/{chunk-O6N2CEET.js → chunk-JMILXXI4.js} +2 -2
  29. package/dist-cli/chunks/chunk-KGYC4SZA.js +2 -0
  30. package/dist-cli/chunks/{chunk-H44IQHKZ.js → chunk-KQYOS5SM.js} +1 -1
  31. package/dist-cli/chunks/{chunk-HQDJ5BOF.js → chunk-M6GOCS27.js} +1 -1
  32. package/dist-cli/chunks/{chunk-DWTLRPEN.js → chunk-NBBH2PVW.js} +2 -2
  33. package/dist-cli/chunks/chunk-NIRJVBXJ.js +1 -0
  34. package/dist-cli/chunks/{chunk-PHPXGLME.js → chunk-NLH7FNSG.js} +1 -1
  35. package/dist-cli/chunks/{chunk-Y5PLPEEU.js → chunk-OBHJKTWA.js} +2 -2
  36. package/dist-cli/chunks/{chunk-5S6D7K4L.js → chunk-OCTDP37S.js} +2 -2
  37. package/dist-cli/chunks/{chunk-OUNLJM56.js → chunk-P7IKKZTG.js} +2 -2
  38. package/dist-cli/chunks/{chunk-3WGHC7JN.js → chunk-PVMX5UNR.js} +2 -2
  39. package/dist-cli/chunks/chunk-QUYC7CVV.js +1 -0
  40. package/dist-cli/chunks/{chunk-CF2LPRXD.js → chunk-STHMWSVN.js} +2 -2
  41. package/dist-cli/chunks/{chunk-OXOARRKR.js → chunk-TSNBQ4ZV.js} +10 -10
  42. package/dist-cli/chunks/{chunk-E2QE5FFP.js → chunk-UWSP2AT7.js} +1 -1
  43. package/dist-cli/chunks/{chunk-4EVSIUNB.js → chunk-V7CFSKMC.js} +2 -2
  44. package/dist-cli/chunks/{chunk-4QZHZ6BC.js → chunk-VFGAEMSI.js} +2 -2
  45. package/dist-cli/chunks/{chunk-RZHREO3M.js → chunk-VUYCS6QI.js} +2 -2
  46. package/dist-cli/chunks/chunk-XB4QIINM.js +24 -0
  47. package/dist-cli/chunks/{chunk-MAO7F5PH.js → chunk-XFJYEKYK.js} +3 -3
  48. package/dist-cli/chunks/{chunk-AC6QGW22.js → chunk-XZE53P4L.js} +2 -2
  49. package/dist-cli/chunks/chunk-Y7BXSTVX.js +1 -0
  50. package/dist-cli/chunks/cli-version-TGWWTYQX.js +2 -0
  51. package/dist-cli/chunks/{compat-PCXGGZBZ.js → compat-I2U3P4KP.js} +3 -3
  52. package/dist-cli/chunks/{config-LULEVEYL.js → config-S73CCGP5.js} +2 -2
  53. package/dist-cli/chunks/{control-6P6HY7UF.js → control-QR6MY7RA.js} +2 -2
  54. package/dist-cli/chunks/{cpu-profile-NOK73ZYW.js → cpu-profile-RFYCTVAF.js} +2 -2
  55. package/dist-cli/chunks/{daemon-4A3DMUYL.js → daemon-D5MV2B22.js} +2 -2
  56. package/dist-cli/chunks/{debug-74BWB2ZG.js → debug-ZYEI75AG.js} +3 -3
  57. package/dist-cli/chunks/{detox-HEOMINSC.js → detox-J5IH52RV.js} +2 -2
  58. package/dist-cli/chunks/{device-TTXXBJFZ.js → device-NOBLSUOD.js} +2 -2
  59. package/dist-cli/chunks/{diagnose-QZ3GOHSE.js → diagnose-B6J5ZUHV.js} +2 -2
  60. package/dist-cli/chunks/drivers-RRHVOU6S.js +2 -0
  61. package/dist-cli/chunks/{electron-QVOWV44R.js → electron-PSX4KDCC.js} +3 -3
  62. package/dist-cli/chunks/flow-FWNVFKMP.js +2 -0
  63. package/dist-cli/chunks/{hints-YKWRNMJC.js → hints-ZE4I3YO3.js} +2 -2
  64. package/dist-cli/chunks/{home-paths-SFADSTJM.js → home-paths-N76MJE3D.js} +2 -2
  65. package/dist-cli/chunks/{inspect-LEWGQCIU.js → inspect-V2TXTDOG.js} +3 -3
  66. package/dist-cli/chunks/install-4PINRR2O.js +2 -0
  67. package/dist-cli/chunks/{install-desktop-22HYQZ2G.js → install-desktop-6ZRTRRCU.js} +3 -3
  68. package/dist-cli/chunks/{keys-3ZT3MICU.js → keys-L2RN4URM.js} +2 -2
  69. package/dist-cli/chunks/{launch-ZXW2NFLG.js → launch-BJGXPNZR.js} +3 -3
  70. package/dist-cli/chunks/{login-NJKJ7GZO.js → login-HYNEMAYR.js} +4 -4
  71. package/dist-cli/chunks/{logout-VMMQL7CB.js → logout-AO4YS27T.js} +2 -2
  72. package/dist-cli/chunks/{maestro-OJY4MTI7.js → maestro-PRACYFKV.js} +2 -2
  73. package/dist-cli/chunks/{preview-QU2GXTEV.js → preview-ZTANXVEK.js} +2 -2
  74. package/dist-cli/chunks/{profile-7APWK47T.js → profile-FNMAGUDB.js} +2 -2
  75. package/dist-cli/chunks/{react-RSVO5JZZ.js → react-6ZV2FQIM.js} +2 -2
  76. package/dist-cli/chunks/{record-UWH4MDEO.js → record-MLFVJZ6Y.js} +2 -2
  77. package/dist-cli/chunks/runtime-5762IE56.js +2 -0
  78. package/dist-cli/chunks/{runtime-delivery-QMKGRV7N.js → runtime-delivery-ATYW2SQR.js} +2 -2
  79. package/dist-cli/chunks/{screenshot-43M27ALE.js → screenshot-UOMYMFZ4.js} +2 -2
  80. package/dist-cli/chunks/{screenshot-mode-EBYYN6TY.js → screenshot-mode-MWSVD4YG.js} +2 -2
  81. package/dist-cli/chunks/{screenshots-7TQZL6Z6.js → screenshots-GSA3VCWB.js} +2 -2
  82. package/dist-cli/chunks/server-YPFC6POG.js +40 -0
  83. package/dist-cli/chunks/setup-repo-QBQ4VWFO.js +2 -0
  84. package/dist-cli/chunks/{skills-RQA6EJQL.js → skills-YE5OPWMQ.js} +2 -2
  85. package/dist-cli/chunks/{start-ZT6MBYND.js → start-BSSQ5U2V.js} +4 -4
  86. package/dist-cli/chunks/store-EG4SONAH.js +2 -0
  87. package/dist-cli/chunks/telemetry-XXN4LRDS.js +2 -0
  88. package/dist-cli/chunks/{test-RNRX5SWV.js → test-5JMLBH2O.js} +3 -3
  89. package/dist-cli/chunks/{three-mode-TQZH25ZO.js → three-mode-TRBWZJQY.js} +2 -2
  90. package/dist-cli/chunks/{timeline-GGN3AY6P.js → timeline-YMZPIEB4.js} +2 -2
  91. package/dist-cli/chunks/{upgrade-XT22D67C.js → upgrade-JLAS7FIF.js} +2 -2
  92. package/dist-cli/chunks/upload-K6UNCFQH.js +2 -0
  93. package/dist-cli/chunks/{web-KEHVF5MB.js → web-D6S5UXOO.js} +2 -2
  94. package/dist-cli/chunks/{what-happened-PATQRJ5T.js → what-happened-65NXWU2S.js} +2 -2
  95. package/dist-cli/chunks/{whoami-CXVY26VV.js → whoami-6BSB6FQC.js} +2 -2
  96. package/dist-lib/agent-daemon-client.cjs +1 -1
  97. package/dist-lib/agent-events.cjs +1 -1
  98. package/dist-lib/agent-sessions.cjs +1 -1
  99. package/dist-lib/attached-projects.cjs +1 -1
  100. package/dist-lib/auth/shared-session.cjs +1 -1
  101. package/dist-lib/backend-origin.cjs +1 -1
  102. package/dist-lib/beta.cjs +1 -1
  103. package/dist-lib/beta.mjs +15 -0
  104. package/dist-lib/bridge-constants.cjs +1 -1
  105. package/dist-lib/cli-constants.cjs +1 -1
  106. package/dist-lib/config.cjs +1 -1
  107. package/dist-lib/detox/index.cjs +1 -1
  108. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  109. package/dist-lib/home-paths.cjs +1 -1
  110. package/dist-lib/host/bridge-host.cjs +244 -35
  111. package/dist-lib/host/fetch-proxy-handler.cjs +24 -4
  112. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  113. package/dist-lib/host/fetch-proxy-overrides.mjs +33 -0
  114. package/dist-lib/host/websocket-proxy.cjs +207 -0
  115. package/dist-lib/index.cjs +1 -1
  116. package/dist-lib/metro.cjs +1 -1
  117. package/dist-lib/profiles.cjs +1 -1
  118. package/dist-lib/render-mode.cjs +1 -1
  119. package/dist-lib/scripts/demo-app-registry.cjs +14 -3
  120. package/dist-lib/scripts/dev-server-scanner.cjs +14 -3
  121. package/dist-lib/skills.cjs +290 -73
  122. package/dist-lib/vite.cjs +1 -1
  123. package/package.json +7 -1
  124. package/scripts/demo-app-registry.ts +17 -1
  125. package/src/host/bridge-host.ts +8 -1
  126. package/src/host/fetch-proxy-handler.ts +26 -3
  127. package/src/host/websocket-proxy.ts +201 -0
  128. package/dist-cli/chunks/auto-bootstrap-47RN2V5G.js +0 -2
  129. package/dist-cli/chunks/beta-BRCGAF2N.js +0 -2
  130. package/dist-cli/chunks/chunk-36RPD6JI.js +0 -2
  131. package/dist-cli/chunks/chunk-4DBPNLGI.js +0 -1
  132. package/dist-cli/chunks/chunk-PQFFUJR6.js +0 -24
  133. package/dist-cli/chunks/chunk-QQAECG5B.js +0 -2
  134. package/dist-cli/chunks/chunk-SSCA2AEA.js +0 -1
  135. package/dist-cli/chunks/chunk-ZFAM4N5B.js +0 -1
  136. package/dist-cli/chunks/cli-version-WPFDM2A6.js +0 -2
  137. package/dist-cli/chunks/drivers-QRPWNOIT.js +0 -2
  138. package/dist-cli/chunks/flow-QMA7GVN6.js +0 -2
  139. package/dist-cli/chunks/install-7N2N7Q32.js +0 -2
  140. package/dist-cli/chunks/runtime-3FUENRHM.js +0 -2
  141. package/dist-cli/chunks/server-VCFM25Z6.js +0 -35
  142. package/dist-cli/chunks/setup-repo-HFH4VKJQ.js +0 -2
  143. package/dist-cli/chunks/store-BJBTDSZE.js +0 -2
  144. package/dist-cli/chunks/telemetry-ZZZKTILZ.js +0 -2
  145. package/dist-cli/chunks/upload-NC2AYLC5.js +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sootsim",
3
- "version": "0.1.85",
3
+ "version": "0.1.86",
4
4
  "description": "sootsim CLI + vite/metro plugins + skills registry. bridge client for driving the proprietary sootsim-engine over WebSocket.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "MIT",
@@ -35,6 +35,7 @@
35
35
  },
36
36
  "./beta": {
37
37
  "source": "./src/beta.ts",
38
+ "import": "./dist-lib/beta.mjs",
38
39
  "default": "./dist-lib/beta.cjs"
39
40
  },
40
41
  "./agent-events": {
@@ -91,8 +92,13 @@
91
92
  },
92
93
  "./host/fetch-proxy-overrides": {
93
94
  "source": "./src/host/fetch-proxy-overrides.ts",
95
+ "import": "./dist-lib/host/fetch-proxy-overrides.mjs",
94
96
  "default": "./dist-lib/host/fetch-proxy-overrides.cjs"
95
97
  },
98
+ "./host/websocket-proxy": {
99
+ "source": "./src/host/websocket-proxy.ts",
100
+ "default": "./dist-lib/host/websocket-proxy.cjs"
101
+ },
96
102
  "./scripts/dev-server-scanner": {
97
103
  "source": "./scripts/dev-server-scanner.ts",
98
104
  "default": "./dist-lib/scripts/dev-server-scanner.cjs"
@@ -11,6 +11,11 @@ export interface DemoApp {
11
11
  framework: 'expo' | 'one' | 'rock'
12
12
  runtimeConfig?: SootSimConfig
13
13
  sidecars?: DemoSidecar[]
14
+ // extra ports owned by the main demo command, derived from the chosen app
15
+ // port. unlike sidecars, these are not independently reusable services; the
16
+ // launcher clears/reserves them with the app command so stale child stacks
17
+ // cannot leave the app talking to the wrong backend.
18
+ managedPorts?: (port: number) => number[]
14
19
  prepare?: () => void | Promise<void>
15
20
  command: (port: number) => { cmd: string; env?: Record<string, string> }
16
21
  disabled?: boolean
@@ -775,11 +780,22 @@ export const APPS: DemoApp[] = [
775
780
  // be holding pglite locks on ~/takeout/.orez (soot can attach takeout as
776
781
  // a project and spin up its own orez against the same data dir).
777
782
  readyTimeoutMs: 240_000,
783
+ managedPorts: (p) => {
784
+ const offset = p - 8081
785
+ return [5433 + offset, 4848 + offset, 9200 + offset]
786
+ },
778
787
  command: (p) => ({
779
- cmd: 'bun lite',
788
+ cmd: 'bun lite:demo',
780
789
  env: {
790
+ TAKEOUT_ENV_MODE: 'development',
781
791
  PORT_OFFSET: String(p - 8081),
782
792
  OREZ_DATA_DIR: `${HOME}/.cache/sootsim-demo/takeout-orez`,
793
+ VITE_DEMO_MODE: '1',
794
+ ZERO_APP_ID: 'takeout',
795
+ ZERO_APP_PUBLICATIONS: 'zero_takeout',
796
+ ZERO_CVR_MAX_CONNS: '4',
797
+ ZERO_NUM_SYNC_WORKERS: '2',
798
+ ZERO_UPSTREAM_MAX_CONNS: '8',
783
799
  },
784
800
  }),
785
801
  },
@@ -36,6 +36,7 @@ import {
36
36
  isFetchProxyRequestUrl,
37
37
  } from './fetch-proxy-handler'
38
38
  import { openUrl as openUrlInBrowser, type OpenUrlOptions } from './open-url.ts'
39
+ import { handleWebSocketProxyUpgrade } from './websocket-proxy'
39
40
 
40
41
  export interface BridgeSimInfo {
41
42
  id: string
@@ -493,8 +494,14 @@ export class SootSimBridgeHost {
493
494
  process.stderr.write(`ws bridge http error: ${String(err)}\n`)
494
495
  })
495
496
  this.httpServer = server
496
- this.wss = new WebSocketServer({ server })
497
+ this.wss = new WebSocketServer({ noServer: true })
497
498
  this.wireWebSocketServer()
499
+ server.on('upgrade', (req, socket, head) => {
500
+ if (handleWebSocketProxyUpgrade(req, socket, head)) return
501
+ this.wss?.handleUpgrade(req, socket, head, (ws) => {
502
+ this.wss?.emit('connection', ws, req)
503
+ })
504
+ })
498
505
  resolve()
499
506
  })
500
507
  })
@@ -58,6 +58,15 @@ const FETCH_PROXY_CORS_HEADERS: Record<string, string> = {
58
58
  'access-control-max-age': '3600',
59
59
  }
60
60
 
61
+ const APP_API_HEADER_REWRITES = new Set([
62
+ 'host',
63
+ 'origin',
64
+ 'referer',
65
+ 'sec-fetch-site',
66
+ 'sec-fetch-mode',
67
+ 'sec-fetch-dest',
68
+ ])
69
+
61
70
  function applyFetchProxyCors(res: ServerResponse) {
62
71
  for (const [key, value] of Object.entries(FETCH_PROXY_CORS_HEADERS)) {
63
72
  res.setHeader(key, value)
@@ -108,6 +117,22 @@ export function buildFetchProxyHeaders(
108
117
  return headers
109
118
  }
110
119
 
120
+ export function buildAppApiProxyHeaders(
121
+ reqHeaders: Record<string, string | string[] | undefined>,
122
+ targetUrl: URL,
123
+ ): Record<string, string | string[]> {
124
+ const headers: Record<string, string | string[]> = {}
125
+ for (const [key, value] of Object.entries(reqHeaders)) {
126
+ if (!value) continue
127
+ if (APP_API_HEADER_REWRITES.has(key.toLowerCase())) continue
128
+ headers[key] = value
129
+ }
130
+ headers.host = targetUrl.host
131
+ headers.origin = targetUrl.origin
132
+ headers.referer = `${targetUrl.origin}/`
133
+ return headers
134
+ }
135
+
111
136
  export function isFetchProxyRequestUrl(rawUrl: string | undefined): boolean {
112
137
  return rawUrl?.startsWith('/__fetch-proxy?') || rawUrl?.startsWith('/__proxy?') || false
113
138
  }
@@ -251,9 +276,7 @@ export function handleAppApiRequest(req: IncomingMessage, res: ServerResponse):
251
276
 
252
277
  const transport = targetUrl.protocol === 'https:' ? https : http
253
278
 
254
- const fwdHeaders = { ...req.headers }
255
- delete fwdHeaders.host
256
- fwdHeaders.host = targetUrl.host
279
+ const fwdHeaders = buildAppApiProxyHeaders(req.headers, targetUrl)
257
280
 
258
281
  const proxyReq = transport.request(
259
282
  {
@@ -0,0 +1,201 @@
1
+ import { WebSocket, WebSocketServer } from 'ws'
2
+ import type { IncomingMessage } from 'http'
3
+ import type { Duplex } from 'stream'
4
+
5
+ export const WEBSOCKET_PROXY_PATH = '/__websocket-proxy'
6
+
7
+ const STRIP_UPSTREAM_HEADERS = new Set([
8
+ 'host',
9
+ 'connection',
10
+ 'upgrade',
11
+ 'transfer-encoding',
12
+ 'content-length',
13
+ 'sec-websocket-accept',
14
+ 'sec-websocket-extensions',
15
+ 'sec-websocket-key',
16
+ 'sec-websocket-protocol',
17
+ 'sec-websocket-version',
18
+ ])
19
+
20
+ function rejectUpgrade(socket: Duplex, status: number, message: string) {
21
+ try {
22
+ socket.write(
23
+ `HTTP/1.1 ${status} ${message}\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: ${message.length}\r\n\r\n${message}`,
24
+ )
25
+ } catch {}
26
+ socket.destroy()
27
+ }
28
+
29
+ function isSameOriginUpgrade(req: IncomingMessage): boolean {
30
+ const origin = req.headers.origin
31
+ const host = req.headers.host
32
+ if (!origin || !host) return false
33
+ try {
34
+ return new URL(origin).host === host
35
+ } catch {
36
+ return false
37
+ }
38
+ }
39
+
40
+ function decodeProxyHeaders(encoded: string | null): Record<string, string> {
41
+ if (!encoded) return {}
42
+ const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/')
43
+ const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4)
44
+ const parsed = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'))
45
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {}
46
+ const headers: Record<string, string> = {}
47
+ for (const [key, value] of Object.entries(parsed)) {
48
+ if (value == null) continue
49
+ if (STRIP_UPSTREAM_HEADERS.has(key.toLowerCase())) continue
50
+ headers[key] = Array.isArray(value) ? value.join(', ') : String(value)
51
+ }
52
+ return headers
53
+ }
54
+
55
+ function getDefaultWebSocketOrigin(targetUrl: URL): string {
56
+ const origin = new URL(targetUrl.href)
57
+ origin.protocol = targetUrl.protocol === 'wss:' ? 'https:' : 'http:'
58
+ return origin.origin
59
+ }
60
+
61
+ function getRequestedProtocols(req: IncomingMessage): string[] {
62
+ const header = req.headers['sec-websocket-protocol']
63
+ const value = Array.isArray(header) ? header.join(',') : header || ''
64
+ return value
65
+ .split(',')
66
+ .map((part) => part.trim())
67
+ .filter(Boolean)
68
+ }
69
+
70
+ function safeClose(ws: WebSocket, code: number, reason: string) {
71
+ if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
72
+ return
73
+ }
74
+ try {
75
+ ws.close(code, reason)
76
+ } catch {
77
+ ws.terminate()
78
+ }
79
+ }
80
+
81
+ function connectProxyPair(clientWs: WebSocket, upstream: WebSocket) {
82
+ let closing = false
83
+
84
+ const closeBoth = (
85
+ source: WebSocket,
86
+ target: WebSocket,
87
+ code: number,
88
+ reason: Buffer,
89
+ ) => {
90
+ if (closing) return
91
+ closing = true
92
+ safeClose(target, code, reason.toString())
93
+ if (source.readyState === WebSocket.OPEN) {
94
+ safeClose(source, code, reason.toString())
95
+ }
96
+ }
97
+
98
+ clientWs.on('message', (data, isBinary) => {
99
+ if (upstream.readyState === WebSocket.OPEN) {
100
+ upstream.send(data, { binary: isBinary })
101
+ }
102
+ })
103
+ upstream.on('message', (data, isBinary) => {
104
+ if (clientWs.readyState === WebSocket.OPEN) {
105
+ clientWs.send(data, { binary: isBinary })
106
+ }
107
+ })
108
+
109
+ clientWs.on('close', (code, reason) => closeBoth(clientWs, upstream, code, reason))
110
+ upstream.on('close', (code, reason) => closeBoth(upstream, clientWs, code, reason))
111
+ clientWs.on('error', () => safeClose(upstream, 1011, 'proxy client error'))
112
+ upstream.on('error', () => safeClose(clientWs, 1011, 'upstream websocket error'))
113
+ }
114
+
115
+ function createUpstreamWebSocket(
116
+ targetUrl: URL,
117
+ protocols: string[],
118
+ headers: Record<string, string>,
119
+ ): WebSocket {
120
+ const upstreamHeaders = { ...headers }
121
+ if (!Object.keys(upstreamHeaders).some((key) => key.toLowerCase() === 'origin')) {
122
+ upstreamHeaders.origin = getDefaultWebSocketOrigin(targetUrl)
123
+ }
124
+ return new WebSocket(targetUrl.href, protocols, {
125
+ headers: upstreamHeaders,
126
+ })
127
+ }
128
+
129
+ export function isWebSocketProxyRequestUrl(rawUrl: string | undefined): boolean {
130
+ if (!rawUrl) return false
131
+ try {
132
+ return new URL(rawUrl, 'http://localhost').pathname === WEBSOCKET_PROXY_PATH
133
+ } catch {
134
+ return false
135
+ }
136
+ }
137
+
138
+ export function handleWebSocketProxyUpgrade(
139
+ req: IncomingMessage,
140
+ socket: Duplex,
141
+ head: Buffer,
142
+ ): boolean {
143
+ if (!isWebSocketProxyRequestUrl(req.url)) return false
144
+ if (!isSameOriginUpgrade(req)) {
145
+ rejectUpgrade(socket, 403, 'forbidden websocket proxy origin')
146
+ return true
147
+ }
148
+
149
+ let targetUrl: URL
150
+ let headers: Record<string, string>
151
+ try {
152
+ const requestUrl = new URL(req.url || '/', 'http://localhost')
153
+ const target = requestUrl.searchParams.get('url')
154
+ if (!target) {
155
+ rejectUpgrade(socket, 400, 'missing websocket proxy url')
156
+ return true
157
+ }
158
+ targetUrl = new URL(target)
159
+ if (targetUrl.protocol !== 'ws:' && targetUrl.protocol !== 'wss:') {
160
+ rejectUpgrade(socket, 400, 'invalid websocket proxy protocol')
161
+ return true
162
+ }
163
+ headers = decodeProxyHeaders(requestUrl.searchParams.get('headers'))
164
+ } catch {
165
+ rejectUpgrade(socket, 400, 'invalid websocket proxy request')
166
+ return true
167
+ }
168
+
169
+ const protocols = getRequestedProtocols(req)
170
+ const upstream = createUpstreamWebSocket(targetUrl, protocols, headers)
171
+ let completed = false
172
+ socket.once('close', () => {
173
+ if (!completed) upstream.terminate()
174
+ })
175
+ upstream.once('open', () => {
176
+ if (completed) return
177
+ completed = true
178
+ const selectedProtocol = upstream.protocol
179
+ const proxyServer = new WebSocketServer({
180
+ noServer: true,
181
+ clientTracking: false,
182
+ handleProtocols(requestedProtocols) {
183
+ return selectedProtocol || requestedProtocols.values().next().value || false
184
+ },
185
+ })
186
+ proxyServer.handleUpgrade(req, socket, head, (clientWs) => {
187
+ connectProxyPair(clientWs, upstream)
188
+ })
189
+ })
190
+ upstream.once('error', () => {
191
+ if (completed) return
192
+ completed = true
193
+ rejectUpgrade(socket, 502, 'upstream websocket error')
194
+ })
195
+ upstream.once('close', () => {
196
+ if (completed) return
197
+ completed = true
198
+ rejectUpgrade(socket, 502, 'upstream websocket closed')
199
+ })
200
+ return true
201
+ }
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d}from"./chunk-G7CIZ5S3.js";import"./chunk-EKXK3SWK.js";import"./chunk-OISHLFON.js";import"./chunk-QQAECG5B.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.js";export{c as ensureDaemonRunning,a as ensureRuntimeInstalled,d as ensureSootsimReady,b as resolveBootstrapPort};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d,e}from"./chunk-NVTL3JQG.js";import"./chunk-H44IQHKZ.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,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- var t="http://localhost:5173/";export{t as a};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,24 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as r}from"./chunk-7LKUN46F.js";import{a as s,b as i,c as l}from"./chunk-UYRGCJ4N.js";import{a as t}from"./chunk-36RPD6JI.js";import{a as u,d}from"./chunk-NVTL3JQG.js";import{a as p}from"./chunk-QLJNSOS7.js";import{z as a}from"./chunk-WGDL5V6C.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,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- var O="sootsim close";export{O as a};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-QLJNSOS7.js";import"./chunk-H44IQHKZ.js";export{a as getCliVersion};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import"./chunk-SSCA2AEA.js";import{a,b,c,d,e,f,g,h,i}from"./chunk-CF2LPRXD.js";import"./chunk-GTAD6IUV.js";import"./chunk-RZHREO3M.js";import"./chunk-4DBPNLGI.js";import"./chunk-KUSQ4NNJ.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.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.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as c,b as d,c as e,d as f}from"./chunk-BZL6D4TV.js";import"./chunk-SBGOUA6F.js";import"./chunk-ZFAM4N5B.js";import"./chunk-5S6D7K4L.js";import{d as a,e as b}from"./chunk-O6N2CEET.js";import"./chunk-4QZHZ6BC.js";import"./chunk-OXOARRKR.js";import"./chunk-AFNDVS4E.js";import"./chunk-ZO3VHP6W.js";import"./chunk-CF2LPRXD.js";import"./chunk-GTAD6IUV.js";import"./chunk-RZHREO3M.js";import"./chunk-DWTLRPEN.js";import"./chunk-5N3V7OCG.js";import"./chunk-4DBPNLGI.js";import"./chunk-KUSQ4NNJ.js";import"./chunk-7LKUN46F.js";import"./chunk-36RPD6JI.js";import"./chunk-Y5PLPEEU.js";import"./chunk-OUNLJM56.js";import"./chunk-BHZJ6RIH.js";import"./chunk-PHPXGLME.js";import"./chunk-EKXK3SWK.js";import"./chunk-OISHLFON.js";import"./chunk-QQAECG5B.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.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.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as o}from"./chunk-4EVSIUNB.js";import"./chunk-5DIGWOY7.js";import"./chunk-G7CIZ5S3.js";import"./chunk-Y5PLPEEU.js";import"./chunk-OUNLJM56.js";import"./chunk-EKXK3SWK.js";import"./chunk-OISHLFON.js";import"./chunk-QQAECG5B.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.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.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-MAO7F5PH.js";import"./chunk-EKXK3SWK.js";import"./chunk-OISHLFON.js";import"./chunk-QQAECG5B.js";import"./chunk-AC6QGW22.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.js";export{a as runRuntime};
@@ -1,35 +0,0 @@
1
- /*! sootsim v0.1.85 | (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-EBEL6TTJ.js";import"./chunk-E2QE5FFP.js";import{f as E}from"./chunk-OXOARRKR.js";import{a as Q}from"./chunk-DWTLRPEN.js";import"./chunk-5N3V7OCG.js";import"./chunk-4DBPNLGI.js";import{d as Y}from"./chunk-KUSQ4NNJ.js";import{a as F}from"./chunk-QLJNSOS7.js";import{a as z,b as X}from"./chunk-Y5PLPEEU.js";import"./chunk-OUNLJM56.js";import"./chunk-EKXK3SWK.js";import"./chunk-OISHLFON.js";import{a as U}from"./chunk-QQAECG5B.js";import{k as Z}from"./chunk-AC6QGW22.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-WGDL5V6C.js";import"./chunk-H44IQHKZ.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.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-4EVSIUNB.js";import"./chunk-5DIGWOY7.js";import"./chunk-G7CIZ5S3.js";import"./chunk-Y5PLPEEU.js";import"./chunk-OUNLJM56.js";import"./chunk-EKXK3SWK.js";import"./chunk-OISHLFON.js";import"./chunk-QQAECG5B.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.js";export{a as runSetupRepo};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-BHZJ6RIH.js";import"./chunk-PHPXGLME.js";import"./chunk-H44IQHKZ.js";export{a as settingsStore};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b}from"./chunk-Y5PLPEEU.js";import"./chunk-OUNLJM56.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.js";export{b as flushCliTelemetry,a as trackCliEvent};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.85 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c}from"./chunk-DWTLRPEN.js";import"./chunk-5N3V7OCG.js";import"./chunk-4DBPNLGI.js";import"./chunk-KUSQ4NNJ.js";import"./chunk-Y5PLPEEU.js";import"./chunk-OUNLJM56.js";import"./chunk-EKXK3SWK.js";import"./chunk-OISHLFON.js";import"./chunk-QQAECG5B.js";import"./chunk-WGDL5V6C.js";import"./chunk-H44IQHKZ.js";export{a as resolveDefaultUploadOrigin,b as resolvePublicPreviewOrigin,c as runUpload};