@solidrt/cli 0.0.51 → 0.0.52

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 (93) hide show
  1. package/AGENTS.md +68 -38
  2. package/README.md +86 -15
  3. package/agents/assets.md +19 -5
  4. package/agents/debugging.md +169 -17
  5. package/dist/console.srtapp +94123 -73
  6. package/dist/server.js +3936 -0
  7. package/package.json +10 -11
  8. package/src/android/docs.md +21 -0
  9. package/src/android/main.ts +286 -0
  10. package/src/{bundler.ts → bundle/bundler.ts} +67 -55
  11. package/src/bundle/docs.md +12 -0
  12. package/src/{commands/bundle.ts → bundle/main.ts} +67 -25
  13. package/src/check/docs.md +10 -0
  14. package/src/check/main.ts +85 -0
  15. package/src/{commands/check.ts → check/typecheck.ts} +11 -41
  16. package/src/client/docs.md +9 -0
  17. package/src/client/main.ts +33 -0
  18. package/src/console/docs.md +14 -0
  19. package/src/console/main.ts +21 -0
  20. package/src/demo/docs.md +27 -0
  21. package/src/demo/main.ts +67 -0
  22. package/src/init/docs.md +11 -0
  23. package/src/{commands/init.ts → init/main.ts} +21 -15
  24. package/src/init/scaffold/AGENTS.md +98 -0
  25. package/src/init/scaffold/package.json +23 -0
  26. package/{scaffold → src/init/scaffold}/templates/components/index.tsx +2 -3
  27. package/{scaffold → src/init/scaffold}/templates/default/index.tsx +2 -3
  28. package/src/lib/args.ts +194 -0
  29. package/src/{artifacts.ts → lib/artifacts.ts} +41 -1
  30. package/src/{dev-dir.ts → lib/dev-dir.ts} +10 -8
  31. package/src/{fonts.ts → lib/fonts.ts} +12 -26
  32. package/src/lib/mode.ts +77 -0
  33. package/src/{project.ts → lib/project.ts} +109 -61
  34. package/src/lib/registry.ts +120 -0
  35. package/src/lib/server-bundle.ts +24 -0
  36. package/src/lib/usage.ts +117 -0
  37. package/src/lib/util.ts +36 -0
  38. package/src/main.ts +109 -31
  39. package/src/mcp/docs.md +22 -0
  40. package/src/mcp/main.ts +719 -0
  41. package/src/pack/docs.md +18 -0
  42. package/src/{pack-folder.ts → pack/layout.ts} +13 -22
  43. package/src/{commands/pack.ts → pack/main.ts} +28 -17
  44. package/src/{packer.ts → pack/trailer.ts} +32 -42
  45. package/src/render/docs.md +19 -0
  46. package/src/{commands/render.ts → render/main.ts} +17 -15
  47. package/src/server/args.ts +126 -0
  48. package/src/server/binaries.ts +47 -0
  49. package/src/server/config.ts +54 -0
  50. package/{server → src/server}/control.ts +239 -81
  51. package/src/server/docs.md +51 -0
  52. package/src/server/line-editor.ts +200 -0
  53. package/src/server/main.ts +473 -0
  54. package/src/server/mode.ts +92 -0
  55. package/src/server/rebuild.ts +90 -0
  56. package/src/server/registry.ts +138 -0
  57. package/src/server/repl.ts +223 -0
  58. package/src/server/state.ts +54 -0
  59. package/{server → src/server}/tsconfig.json +1 -1
  60. package/{server → src/server}/tunnel.ts +6 -6
  61. package/src/server/watcher.ts +121 -0
  62. package/src/tool/main.ts +70 -0
  63. package/src/types/bundle.d.ts +24 -0
  64. package/src/types/control.d.ts +90 -0
  65. package/src/types/registry.d.ts +16 -0
  66. package/scaffold/AGENTS.md +0 -185
  67. package/scaffold/package.json +0 -22
  68. package/scaffold/templates/components/icon.tsx +0 -48
  69. package/scaffold/templates/default/icon.tsx +0 -48
  70. package/server/main.ts +0 -308
  71. package/server/rebuild.ts +0 -76
  72. package/server/state.ts +0 -94
  73. package/src/args.ts +0 -210
  74. package/src/bundle-cli.ts +0 -13
  75. package/src/commands/client.ts +0 -34
  76. package/src/commands/mcp.ts +0 -617
  77. package/src/commands/server.ts +0 -73
  78. package/src/dev-android.ts +0 -176
  79. package/src/dev-client.ts +0 -29
  80. package/src/dev-server.ts +0 -302
  81. package/src/repl.ts +0 -249
  82. package/src/util.ts +0 -122
  83. package/src/watcher.ts +0 -73
  84. /package/src/{untyped-deps.d.ts → bundle/untyped-deps.d.ts} +0 -0
  85. /package/src/{prompt.ts → init/prompt.ts} +0 -0
  86. /package/{scaffold → src/init/scaffold}/gitignore +0 -0
  87. /package/{scaffold → src/init/scaffold}/icon.svg +0 -0
  88. /package/{scaffold → src/init/scaffold}/mcp.json +0 -0
  89. /package/{scaffold → src/init/scaffold}/tsconfig.json +0 -0
  90. /package/{server → src/server}/cache.ts +0 -0
  91. /package/{server → src/server}/proxy.ts +0 -0
  92. /package/{server → src/server}/qr.ts +0 -0
  93. /package/{server → src/server}/remap.ts +0 -0
package/server/main.ts DELETED
@@ -1,308 +0,0 @@
1
- // The srt dev server as a flux script. srt (Bun) spawns this with one JSON
2
- // config argument; bundling, file watching, and the repl stay in srt, which
3
- // drives this process over the loopback-only /__internal__/ routes. The
4
- // shutdown-when-empty policy also lives in srt (it polls /__internal__/clients);
5
- // this process runs until srt kills it.
6
-
7
- import { serve } from "flux:http"
8
- import type { FluxRequest, Server } from "flux:http"
9
- import { file } from "flux:fs"
10
- import { argv } from "flux:process"
11
- import { resolveWithin } from "flux:path"
12
- import { state, type Config } from "./state"
13
- import * as cache from "./cache"
14
- import { handleProxy } from "./proxy"
15
- import { appendLog, clientList, handleControl, resolveQuery } from "./control"
16
- import { printQr } from "./qr"
17
- import { createTunnelEndpoint, TUNNEL_PROTOCOL } from "./tunnel"
18
-
19
- let config: Config = JSON.parse(argv[0]!)
20
- state.config = config
21
- state.sourceDir = config.sourceDir
22
- state.projectDir = config.projectDir
23
- state.stats = config.stats
24
- state.serverUrl = `${config.address}:${config.port}`
25
-
26
- if (config.cache) {
27
- await cache.initCache({ dir: config.cacheDir })
28
- console.log("[cli] HTTP cache enabled")
29
- }
30
-
31
- if (config.capture) {
32
- // Start each capture from an empty file: appends would otherwise tack onto
33
- // whatever a previous run left behind.
34
- await file(config.capture).write("")
35
- state.captureStartMs = Date.now()
36
- }
37
-
38
- // Split an origin-form request URL ("/path?a=1&b=2") into its decoded path and
39
- // query parameters. flux has no URL global; this covers what the routes need.
40
- function splitQuery(url: string): { path: string; query: Map<string, string> } {
41
- let i = url.indexOf("?")
42
- let path = i < 0 ? url : url.slice(0, i)
43
- let query = new Map<string, string>()
44
- if (i >= 0) {
45
- for (let pair of url.slice(i + 1).split("&")) {
46
- if (!pair) continue
47
- let j = pair.indexOf("=")
48
- let k = j < 0 ? pair : pair.slice(0, j)
49
- let v = j < 0 ? "" : pair.slice(j + 1)
50
- query.set(decodeURIComponent(k), decodeURIComponent(v.replace(/\+/g, " ")))
51
- }
52
- }
53
- return { path: decodeURIComponent(path), query }
54
- }
55
-
56
- // Send `text` to the clients with the given ids, or to every client when
57
- // `ids` is omitted.
58
- function sendTo(ids: number[] | undefined, text: string) {
59
- for (let [ws, info] of state.clients) {
60
- if (!ids || ids.includes(info.id)) ws.send(text)
61
- }
62
- }
63
-
64
- // The srt -> server IPC under /__internal__/: only the srt process on this
65
- // machine may drive it, so reject any peer that is not loopback.
66
- async function handleInternal(req: FluxRequest, server: Server, path: string): Promise<Response> {
67
- let ip = server.requestIP(req)
68
- let loopback = ip && (ip.address === "127.0.0.1" || ip.address === "::1" || ip.address === "::ffff:127.0.0.1")
69
- if (!loopback) return new Response("Forbidden", { status: 403 })
70
-
71
- if (path === "/__internal__/clients") return Response.json(clientList(true))
72
- if (path === "/__internal__/watch" && req.method === "GET") return Response.json({ enabled: state.watch })
73
- if (req.method !== "POST") return new Response("Method not allowed", { status: 405 })
74
-
75
- switch (path) {
76
- case "/__internal__/reload": {
77
- // { message, clients?, latch?, sourceDir?, maps? }: send `message` (a
78
- // full client-protocol message, built by srt) to the listed client ids,
79
- // or to all when omitted. `latch` keeps it for late-joining clients
80
- // (code reloads latch, one-shot bytecode loads do not); `sourceDir`
81
- // moves the file-serving root (repl `load`; the project root - and with
82
- // it the /assets/ root - is fixed for the life of the run); `maps` is
83
- // the bundle's sourcemaps keyed by module name for log remapping,
84
- // replaced on every reload (absent means none).
85
- let body = await req.json()
86
- if (typeof body.sourceDir === "string") state.sourceDir = body.sourceDir
87
- // Keep the rebuild entry in sync when `load` moves it, so a later MCP
88
- // reload bundles the newly loaded file, not the launch-time one.
89
- if (typeof body.entry === "string") state.config.entry = body.entry
90
- state.currentMaps = body.maps && typeof body.maps === "object" ? body.maps : null
91
- let text = JSON.stringify(body.message)
92
- if (body.latch) state.currentReload = text
93
- sendTo(body.clients, text)
94
- return new Response("", { status: 204 })
95
- }
96
- case "/__internal__/stop": {
97
- let body = await req.json()
98
- // A broadcast stop also forgets the latched reload, so a client that
99
- // connects afterwards starts clean.
100
- if (!body.clients) {
101
- state.currentReload = null
102
- state.currentMaps = null
103
- }
104
- sendTo(body.clients, JSON.stringify({ type: "stop" }))
105
- return new Response("", { status: 204 })
106
- }
107
- case "/__internal__/watch": {
108
- // The repl's `watch on|off`; agents use /__control__/watch instead.
109
- let body = await req.json()
110
- state.watch = !!body.enabled
111
- return new Response("", { status: 204 })
112
- }
113
- case "/__internal__/stats": {
114
- let body = await req.json()
115
- state.stats = !!body.stats
116
- sendTo(undefined, JSON.stringify({ type: "stats", stats: state.stats }))
117
- return new Response("", { status: 204 })
118
- }
119
- default:
120
- return Response.json({ error: "Unknown internal endpoint" }, { status: 404 })
121
- }
122
- }
123
-
124
- // The file routes: GET file with single-range 206 support. All paths are
125
- // contained in `root` (the source directory, or the project dir for the
126
- // /assets/ convention route).
127
- async function handleFiles(req: FluxRequest, path: string, root: string): Promise<Response> {
128
- let filePath = resolveWithin(root, "." + path)
129
- if (!filePath) {
130
- return new Response("Forbidden", { status: 403 })
131
- }
132
-
133
- console.log("[cli] get " + path)
134
-
135
- let stat
136
- try {
137
- stat = await file(filePath).stat()
138
- } catch {
139
- console.log(`[cli] file not found ${path}`)
140
- return new Response("Not found", { status: 404 })
141
- }
142
- if (stat.type === "directory") {
143
- return new Response("Not found", { status: 404 })
144
- }
145
-
146
- let baseHeaders: Record<string, string> = { "Accept-Ranges": "bytes" }
147
-
148
- // Honor a single byte-range request (e.g. streaming audio decoding on the
149
- // client, which seeks and reads on demand). Only the common "bytes=a-b" /
150
- // "bytes=a-" / "bytes=-n" forms; anything else falls through to the whole
151
- // file. Range makes proxied streaming viable without pulling the whole
152
- // track over the wire.
153
- let range = req.headers.get("range")
154
- let match = range ? /^bytes=(\d*)-(\d*)$/.exec(range.trim()) : null
155
- if (match) {
156
- let size = stat.size
157
- let start: number
158
- let end: number
159
- if (match[1] === "") {
160
- // Suffix range: the last N bytes.
161
- let n = parseInt(match[2]!, 10)
162
- start = isNaN(n) ? 0 : Math.max(0, size - n)
163
- end = size - 1
164
- } else {
165
- start = parseInt(match[1]!, 10)
166
- end = match[2] === "" ? size - 1 : Math.min(parseInt(match[2]!, 10), size - 1)
167
- }
168
- if (start > end || start >= size) {
169
- return new Response("Range not satisfiable", {
170
- status: 416,
171
- headers: { ...baseHeaders, "Content-Range": `bytes */${size}` },
172
- })
173
- }
174
- return new Response(await file(filePath).read(start, end - start + 1), {
175
- status: 206,
176
- headers: {
177
- ...baseHeaders,
178
- "Content-Range": `bytes ${start}-${end}/${size}`,
179
- "Content-Length": String(end - start + 1),
180
- },
181
- })
182
- }
183
-
184
- return new Response(await file(filePath).bytes(), { headers: baseHeaders })
185
- }
186
-
187
- // Ticket-paired clients connect through this endpoint; serve() accepts its
188
- // connections directly alongside the TCP listener.
189
- let tunnel = config.tunnel ? await createTunnelEndpoint(config.port, config.keyDir) : null
190
-
191
- serve({
192
- port: config.port,
193
- p2p: tunnel ? { endpoint: tunnel, protocol: TUNNEL_PROTOCOL } : undefined,
194
- async fetch(req, server) {
195
- if (server.upgrade(req)) return
196
-
197
- let { path, query } = splitQuery(req.url)
198
-
199
- if (path === "/__proxy__") {
200
- return handleProxy(req)
201
- }
202
- if (path.startsWith("/__control__/")) {
203
- return handleControl(req, path, query)
204
- }
205
- if (path.startsWith("/__internal__/")) {
206
- return handleInternal(req, server, path)
207
- }
208
- // The assets/ convention roots at the project dir (package.json), which is
209
- // not necessarily the entry's dir the file routes serve; clients fetch
210
- // manifest asset paths here (live proxy reads and store installs alike).
211
- if (path === "/assets" || path.startsWith("/assets/")) {
212
- return handleFiles(req, path, state.projectDir)
213
- }
214
- // Isolate bundles are build outputs, not project files: srt (and the
215
- // rebuild here) write them under .srt-data/isolates/, and the manifest
216
- // lists them as isolates/<id>.js.
217
- if (path.startsWith("/isolates/")) {
218
- return handleFiles(req, path, config.cacheDir)
219
- }
220
- return handleFiles(req, path, state.sourceDir)
221
- },
222
- websocket: {
223
- open(ws) {
224
- let id = state.nextClientId++
225
- state.clients.set(ws, {
226
- platform: "unknown",
227
- version: "unknown",
228
- profile: "unknown",
229
- id,
230
- capabilities: [],
231
- queries: [],
232
- })
233
- console.log(`[cli] Client connected ${ws.remoteAddr ?? "unknown"}`)
234
- // Advertise our real LAN address so clients dialed over a loopback hop
235
- // can show/remember the directly reachable address (see connection.rs).
236
- ws.send(
237
- JSON.stringify({ type: "welcome", address: state.serverUrl, stats: state.stats, capture: !!config.capture }),
238
- )
239
- if (state.currentReload) {
240
- ws.send(state.currentReload)
241
- }
242
- },
243
- close(ws) {
244
- let info = state.clients.get(ws)
245
- state.clients.delete(ws)
246
- console.log(`[cli] Client disconnected: ${info?.platform ?? "unknown"}`)
247
- },
248
- message(ws, msg) {
249
- try {
250
- let data = JSON.parse(typeof msg === "string" ? msg : new TextDecoder().decode(msg))
251
- if (data.type === "info") {
252
- let existing = state.clients.get(ws)
253
- state.clients.set(ws, {
254
- platform: data.platform ?? "unknown",
255
- version: data.version ?? "unknown",
256
- profile: data.profile ?? "unknown",
257
- id: existing?.id ?? state.nextClientId++,
258
- capabilities: Array.isArray(data.capabilities) ? data.capabilities.map(String) : [],
259
- queries: Array.isArray(data.queries) ? data.queries.map(String) : [],
260
- })
261
- console.log(`[cli] Client info ${ws.remoteAddr ?? "unknown"} ${data.platform} (${data.version})`)
262
- } else if (data.type === "log") {
263
- // Forwarded console output / runtime errors from the client's
264
- // engine logger, buffered for the control API (see control.ts).
265
- // Not printed here: the local client already writes to this
266
- // terminal, so echoing would duplicate every line.
267
- let device = state.clients.get(ws)?.id ?? -1
268
- appendLog(device, String(data.level ?? "log"), String(data.text ?? ""))
269
- } else if (data.type === "result") {
270
- // Reply to a query the control API forwarded to this client.
271
- resolveQuery(data)
272
- } else if (data.type === "capture" && config.capture) {
273
- let device = state.clients.get(ws)?.id ?? -1
274
- // Milliseconds, integer: Date.now() is already integer ms, so the
275
- // delta needs no rounding.
276
- let at = Date.now() - state.captureStartMs
277
- let after = at - state.captureLastAt
278
- state.captureLastAt = at
279
- // JSON Lines: one event object per line, streamed to disk as it
280
- // arrives rather than buffered - no in-memory growth for a long
281
- // capture, and the file is always complete on disk mid-session.
282
- // Appends are chained so events land in arrival order.
283
- let line = JSON.stringify({ after, type: data.kind, key: data.key, device }) + "\n"
284
- state.captureChain = state.captureChain.then(() => file(config.capture!).append(line))
285
- }
286
- } catch {}
287
- },
288
- },
289
- })
290
-
291
- // One QR on screen: with the tunnel on, the ticket QR (printed by
292
- // createTunnelEndpoint) is the pairing story and the address stays text-only;
293
- // without it, the address QR is the scan target as before.
294
- if (!config.tunnel) {
295
- console.log("")
296
- printQr(state.serverUrl)
297
- console.log("")
298
- }
299
- console.log(`[cli] WebSocket server on ws://${state.serverUrl}`)
300
- // mDNS advertise is intentionally not implemented here: the p2p ticket is the
301
- // cross-device connect story (see okf/backlog/mdns-discovery.md).
302
-
303
- // Keepalive
304
- setInterval(() => {
305
- for (let ws of state.clients.keys()) {
306
- ws.ping()
307
- }
308
- }, 5000)
package/server/rebuild.ts DELETED
@@ -1,76 +0,0 @@
1
- import { command } from "flux:subprocess"
2
- import { dir, file } from "flux:fs"
3
- import { state } from "./state"
4
-
5
- // Server-owned "rebuild and push": the single place the running app is rebuilt
6
- // from source on demand (an MCP reload). The srt repl still bundles in-process
7
- // for its own keystroke reloads, but both routes call the same bundle-cli, so
8
- // the bundling logic cannot drift. Making the server the rebuild authority is
9
- // the interim step toward folding the whole CLI into flux (see
10
- // okf/backlog/cli-flux-migration.md).
11
-
12
- // Build the reload message the same way srt's buildReload does, so a
13
- // server-triggered reload is indistinguishable from a repl-triggered one to
14
- // clients. proxyHttp is a message flag, not a build input.
15
- function buildReload(code: string, manifest?: string) {
16
- let config = state.config
17
- return {
18
- type: "reload",
19
- proxyHttp: config.proxyHttp,
20
- args: config.args,
21
- ...(manifest ? { manifest } : {}),
22
- code,
23
- }
24
- }
25
-
26
- // Rebuild from state.config.entry via the external bundle-cli subprocess, then
27
- // latch (for late-joining clients) and broadcast the reload to every connected
28
- // client. Resolves with an error message on failure (no entry configured, or a
29
- // build error), or null on success.
30
- export async function rebuildAndBroadcast(): Promise<string | null> {
31
- let config = state.config
32
- if (!config.entry) {
33
- return "No app entry to rebuild. Start srt with a source file (srt run src/index.tsx)."
34
- }
35
-
36
- let params = JSON.stringify({
37
- entry: config.entry,
38
- devBase: state.serverUrl,
39
- dev: true,
40
- minify: config.minify,
41
- })
42
-
43
- let result = await command(config.bundlerCmd[0]!, [...config.bundlerCmd.slice(1), params]).output()
44
- if (!result.success) {
45
- let stderr = typeof result.stderr === "string" ? result.stderr : ""
46
- return `Rebuild failed:\n${stderr.trim()}`
47
- }
48
-
49
- // bundle-cli writes one JSON object { code, map, manifest, isolates } to stdout.
50
- let bundle: {
51
- code?: string
52
- map?: string | null
53
- manifest?: string
54
- isolates?: { id: string; code: string; map?: string | null }[]
55
- }
56
- try {
57
- bundle = JSON.parse(typeof result.stdout === "string" ? result.stdout : "")
58
- } catch {
59
- return "Rebuild failed: unreadable bundler output"
60
- }
61
- // Isolate bundles are manifest assets clients fetch from our /isolates/
62
- // route (served from cacheDir), so they must be on disk before the push.
63
- let maps: Record<string, string> = {}
64
- if (bundle.map) maps.main = bundle.map
65
- for (let isolate of bundle.isolates ?? []) {
66
- let path = `${config.cacheDir}/isolates/${isolate.id}.js`
67
- await dir(path.slice(0, path.lastIndexOf("/"))).create()
68
- await file(path).write(isolate.code)
69
- if (isolate.map) maps[isolate.id] = isolate.map
70
- }
71
- state.currentMaps = Object.keys(maps).length ? maps : null
72
- let text = JSON.stringify(buildReload(bundle.code ?? "", bundle.manifest))
73
- state.currentReload = text
74
- for (let ws of state.clients.keys()) ws.send(text)
75
- return null
76
- }
package/server/state.ts DELETED
@@ -1,94 +0,0 @@
1
- // Server-side state shared by the route handlers. The srt process keeps the
2
- // bundler, watcher, and repl; this state is only what the protocol needs.
3
-
4
- import type { ServerWebSocket } from "flux:http"
5
-
6
- export type Config = {
7
- port: number
8
- /** Directory served by the file routes (updatable via /__internal__/reload). */
9
- sourceDir: string
10
- /** Project root whose assets/ folder the /assets/ route serves (updatable
11
- * via /__internal__/reload, moves with the repl `load`). */
12
- projectDir: string
13
- /** The address clients can reach this machine on (LAN IP or 127.0.0.1). */
14
- address: string
15
- proxyHttp: boolean
16
- /** The app entry (absolute .tsx/.jsx path) the server rebuilds on an
17
- * MCP-triggered reload, or undefined when srt was started without a source.
18
- * Moved by the repl `load` command via /__internal__/reload. */
19
- entry?: string
20
- /** The session's app arguments (the srt command-line tail after a bare
21
- * "--"), included in every reload push as flux:process argv. */
22
- args: string[]
23
- /** Minify the rebuild output, mirroring the srt --minify flag. */
24
- minify: boolean
25
- /** How the server invokes the external bundler: [bunPath, bundleCliPath],
26
- * spawned with a JSON params argument appended (see rebuild.ts). */
27
- bundlerCmd: string[]
28
- /** Enable the sqlite-backed proxy cache. */
29
- cache: boolean
30
- /** Directory holding the proxy cache db (the project-local .srt-data). */
31
- cacheDir: string
32
- /** Directory holding tunnel.key (the server's ~/.solidrt/servers/<port>/ folder). */
33
- keyDir: string
34
- /** Destination for captured key events, or unset when off. */
35
- capture?: string
36
- stats: boolean
37
- /** Accept ticket-paired clients through the p2p tunnel. */
38
- tunnel: boolean
39
- }
40
-
41
- export type ClientInfo = {
42
- platform: string
43
- version: string
44
- profile: string
45
- id: number
46
- capabilities: string[]
47
- /** Query kinds this client's runtime answers (empty on runtimes that predate
48
- * the advertisement); dev tools plan their verification surface from it. */
49
- queries: string[]
50
- }
51
-
52
- export let state = {
53
- config: undefined as unknown as Config,
54
- clients: new Map<ServerWebSocket, ClientInfo>(),
55
- nextClientId: 0,
56
- /**
57
- * Identity of this server run, included in control responses that carry
58
- * cross-call state (client ids, log seq cursors). Both reset on restart, so
59
- * a consumer that sees the generation change knows its ids and cursors are
60
- * from a dead server and must be re-fetched.
61
- */
62
- generation: Date.now(),
63
- /**
64
- * The latched reload message (JSON text), replayed to late-joining clients.
65
- * Set by /__internal__/reload posts with `latch`, cleared by a broadcast stop.
66
- */
67
- currentReload: null as string | null,
68
- /**
69
- * The running bundle's sourcemaps (JSON text, bundle -> .tsx sources),
70
- * keyed by the module name stack frames cite ("main" for the app, the
71
- * isolate id for each isolate), used to remap stack traces in forwarded
72
- * client logs (see control.ts). Replaced on every reload; a reload without
73
- * maps clears them so frames are never remapped against a stale map.
74
- */
75
- currentMaps: null as Record<string, string> | null,
76
- sourceDir: "",
77
- projectDir: "",
78
- serverUrl: "",
79
- stats: false,
80
- /**
81
- * Whether srt's file watcher may auto-reload on source changes. Agents
82
- * pause it (MCP watch tool -> /__control__/watch) while creating or
83
- * editing files; a successful /reload or /load re-enables it. srt reads
84
- * it via /__internal__/watch before acting on a change event.
85
- */
86
- watch: true,
87
- // Capture events from all connected clients share one clock (captureStartMs,
88
- // integer milliseconds) so they merge into one coherent timeline, tagged by
89
- // `device`. Streamed to disk as JSON Lines - see main.ts's "capture" handling.
90
- captureStartMs: 0,
91
- captureLastAt: 0, // ms, same clock as captureStartMs
92
- /** Serializes capture appends so events land on disk in arrival order. */
93
- captureChain: Promise.resolve(),
94
- }