@nimbus-sh/worker 0.1.0

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 (408) hide show
  1. package/README.md +219 -0
  2. package/dist/_shared/bytes.d.ts +18 -0
  3. package/dist/_shared/bytes.d.ts.map +1 -0
  4. package/dist/_shared/bytes.js +17 -0
  5. package/dist/_shared/cache-stats.d.ts +146 -0
  6. package/dist/_shared/cache-stats.d.ts.map +1 -0
  7. package/dist/_shared/cache-stats.js +162 -0
  8. package/dist/_shared/exports-resolver.d.ts +76 -0
  9. package/dist/_shared/exports-resolver.d.ts.map +1 -0
  10. package/dist/_shared/exports-resolver.js +284 -0
  11. package/dist/_shared/install-phase.d.ts +47 -0
  12. package/dist/_shared/install-phase.d.ts.map +1 -0
  13. package/dist/_shared/install-phase.js +1 -0
  14. package/dist/_shared/real-node-imports.d.ts +38 -0
  15. package/dist/_shared/real-node-imports.d.ts.map +1 -0
  16. package/dist/_shared/real-node-imports.js +46 -0
  17. package/dist/_shared/retry.d.ts +93 -0
  18. package/dist/_shared/retry.d.ts.map +1 -0
  19. package/dist/_shared/retry.js +161 -0
  20. package/dist/_shared/session-id.d.ts +39 -0
  21. package/dist/_shared/session-id.d.ts.map +1 -0
  22. package/dist/_shared/session-id.js +127 -0
  23. package/dist/_shared/session-router.d.ts +104 -0
  24. package/dist/_shared/session-router.d.ts.map +1 -0
  25. package/dist/_shared/session-router.js +165 -0
  26. package/dist/_shared/w7-frame.d.ts +93 -0
  27. package/dist/_shared/w7-frame.d.ts.map +1 -0
  28. package/dist/_shared/w7-frame.js +402 -0
  29. package/dist/auth/index.d.ts +16 -0
  30. package/dist/auth/index.d.ts.map +1 -0
  31. package/dist/auth/index.js +15 -0
  32. package/dist/auth/middleware.d.ts +85 -0
  33. package/dist/auth/middleware.d.ts.map +1 -0
  34. package/dist/auth/middleware.js +150 -0
  35. package/dist/auth/token.d.ts +96 -0
  36. package/dist/auth/token.d.ts.map +1 -0
  37. package/dist/auth/token.js +226 -0
  38. package/dist/auth/types.d.ts +141 -0
  39. package/dist/auth/types.d.ts.map +1 -0
  40. package/dist/auth/types.js +114 -0
  41. package/dist/bindings/d1.d.ts +102 -0
  42. package/dist/bindings/d1.d.ts.map +1 -0
  43. package/dist/bindings/d1.js +549 -0
  44. package/dist/bindings/kv.d.ts +82 -0
  45. package/dist/bindings/kv.d.ts.map +1 -0
  46. package/dist/bindings/kv.js +290 -0
  47. package/dist/bindings/r2.d.ts +137 -0
  48. package/dist/bindings/r2.d.ts.map +1 -0
  49. package/dist/bindings/r2.js +379 -0
  50. package/dist/cirrus-npm-cjs.generated.d.ts +17 -0
  51. package/dist/cirrus-npm-cjs.generated.d.ts.map +1 -0
  52. package/dist/cirrus-npm-cjs.generated.js +41 -0
  53. package/dist/cirrus-plugin-react.generated.d.ts +34 -0
  54. package/dist/cirrus-plugin-react.generated.d.ts.map +1 -0
  55. package/dist/cirrus-plugin-react.generated.js +33 -0
  56. package/dist/constants.d.ts +34 -0
  57. package/dist/constants.d.ts.map +1 -0
  58. package/dist/constants.js +116 -0
  59. package/dist/esbuild-wasm-bundle.generated.d.ts +41 -0
  60. package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -0
  61. package/dist/esbuild-wasm-bundle.generated.js +40 -0
  62. package/dist/facets/cirrus-real.d.ts +147 -0
  63. package/dist/facets/cirrus-real.d.ts.map +1 -0
  64. package/dist/facets/cirrus-real.js +981 -0
  65. package/dist/facets/inner-do-registry.d.ts +41 -0
  66. package/dist/facets/inner-do-registry.d.ts.map +1 -0
  67. package/dist/facets/inner-do-registry.js +51 -0
  68. package/dist/facets/manager.d.ts +314 -0
  69. package/dist/facets/manager.d.ts.map +1 -0
  70. package/dist/facets/manager.js +2681 -0
  71. package/dist/facets/process.d.ts +318 -0
  72. package/dist/facets/process.d.ts.map +1 -0
  73. package/dist/facets/process.js +567 -0
  74. package/dist/facets/real-vite-fs-shim.d.ts +151 -0
  75. package/dist/facets/real-vite-fs-shim.d.ts.map +1 -0
  76. package/dist/facets/real-vite-fs-shim.js +1279 -0
  77. package/dist/facets/real-vite-hmr.d.ts +142 -0
  78. package/dist/facets/real-vite-hmr.d.ts.map +1 -0
  79. package/dist/facets/real-vite-hmr.js +476 -0
  80. package/dist/facets/vite-dev-server.d.ts +293 -0
  81. package/dist/facets/vite-dev-server.d.ts.map +1 -0
  82. package/dist/facets/vite-dev-server.js +2580 -0
  83. package/dist/facets/wasm-swap-registry.d.ts +261 -0
  84. package/dist/facets/wasm-swap-registry.d.ts.map +1 -0
  85. package/dist/facets/wasm-swap-registry.js +551 -0
  86. package/dist/facets/ws-terminal.d.ts +107 -0
  87. package/dist/facets/ws-terminal.d.ts.map +1 -0
  88. package/dist/facets/ws-terminal.js +207 -0
  89. package/dist/git/commands.d.ts +13 -0
  90. package/dist/git/commands.d.ts.map +1 -0
  91. package/dist/git/commands.js +633 -0
  92. package/dist/git/network-facet.d.ts +61 -0
  93. package/dist/git/network-facet.d.ts.map +1 -0
  94. package/dist/git/network-facet.js +700 -0
  95. package/dist/git-bundle.generated.d.ts +16 -0
  96. package/dist/git-bundle.generated.d.ts.map +1 -0
  97. package/dist/git-bundle.generated.js +15 -0
  98. package/dist/index.d.ts +41 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +73 -0
  101. package/dist/loaders/child-process/spawn-facet.d.ts +90 -0
  102. package/dist/loaders/child-process/spawn-facet.d.ts.map +1 -0
  103. package/dist/loaders/child-process/spawn-facet.js +103 -0
  104. package/dist/loaders/child-process/spawn-pool.d.ts +110 -0
  105. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -0
  106. package/dist/loaders/child-process/spawn-pool.js +161 -0
  107. package/dist/loaders/fanout-pool.d.ts +195 -0
  108. package/dist/loaders/fanout-pool.d.ts.map +1 -0
  109. package/dist/loaders/fanout-pool.js +326 -0
  110. package/dist/loaders/generated-workers.d.ts +26 -0
  111. package/dist/loaders/generated-workers.d.ts.map +1 -0
  112. package/dist/loaders/generated-workers.js +25 -0
  113. package/dist/loaders/index.d.ts +15 -0
  114. package/dist/loaders/index.d.ts.map +1 -0
  115. package/dist/loaders/index.js +13 -0
  116. package/dist/loaders/loader-pool.d.ts +273 -0
  117. package/dist/loaders/loader-pool.d.ts.map +1 -0
  118. package/dist/loaders/loader-pool.js +624 -0
  119. package/dist/loaders/npm-resolve-preamble.d.ts +34 -0
  120. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -0
  121. package/dist/loaders/npm-resolve-preamble.js +237 -0
  122. package/dist/loaders/pre-bundle-preamble.d.ts +41 -0
  123. package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -0
  124. package/dist/loaders/pre-bundle-preamble.js +114 -0
  125. package/dist/loaders/vendor/codegen.d.ts +24 -0
  126. package/dist/loaders/vendor/codegen.d.ts.map +1 -0
  127. package/dist/loaders/vendor/codegen.js +44 -0
  128. package/dist/loaders/vendor/errors.d.ts +24 -0
  129. package/dist/loaders/vendor/errors.d.ts.map +1 -0
  130. package/dist/loaders/vendor/errors.js +46 -0
  131. package/dist/loaders/vendor/index.d.ts +16 -0
  132. package/dist/loaders/vendor/index.d.ts.map +1 -0
  133. package/dist/loaders/vendor/index.js +11 -0
  134. package/dist/loaders/vendor/pool.d.ts +88 -0
  135. package/dist/loaders/vendor/pool.d.ts.map +1 -0
  136. package/dist/loaders/vendor/pool.js +353 -0
  137. package/dist/loaders/vendor/primitives.d.ts +11 -0
  138. package/dist/loaders/vendor/primitives.d.ts.map +1 -0
  139. package/dist/loaders/vendor/primitives.js +33 -0
  140. package/dist/loaders/vendor/serialize.d.ts +3 -0
  141. package/dist/loaders/vendor/serialize.d.ts.map +1 -0
  142. package/dist/loaders/vendor/serialize.js +25 -0
  143. package/dist/loaders/vendor/types.d.ts +60 -0
  144. package/dist/loaders/vendor/types.d.ts.map +1 -0
  145. package/dist/loaders/vendor/types.js +4 -0
  146. package/dist/npm/cache.d.ts +136 -0
  147. package/dist/npm/cache.d.ts.map +1 -0
  148. package/dist/npm/cache.js +429 -0
  149. package/dist/npm/install-batch-facet.d.ts +129 -0
  150. package/dist/npm/install-batch-facet.d.ts.map +1 -0
  151. package/dist/npm/install-batch-facet.js +598 -0
  152. package/dist/npm/install-facet.d.ts +29 -0
  153. package/dist/npm/install-facet.d.ts.map +1 -0
  154. package/dist/npm/install-facet.js +9 -0
  155. package/dist/npm/installer.d.ts +239 -0
  156. package/dist/npm/installer.d.ts.map +1 -0
  157. package/dist/npm/installer.js +2022 -0
  158. package/dist/npm/npx-install.d.ts +74 -0
  159. package/dist/npm/npx-install.d.ts.map +1 -0
  160. package/dist/npm/npx-install.js +302 -0
  161. package/dist/npm/pre-bundle-facet.d.ts +168 -0
  162. package/dist/npm/pre-bundle-facet.d.ts.map +1 -0
  163. package/dist/npm/pre-bundle-facet.js +653 -0
  164. package/dist/npm/r2-cache.d.ts +286 -0
  165. package/dist/npm/r2-cache.d.ts.map +1 -0
  166. package/dist/npm/r2-cache.js +506 -0
  167. package/dist/npm/resolve-facet.d.ts +202 -0
  168. package/dist/npm/resolve-facet.d.ts.map +1 -0
  169. package/dist/npm/resolve-facet.js +833 -0
  170. package/dist/npm/resolve-one-facet.d.ts +204 -0
  171. package/dist/npm/resolve-one-facet.d.ts.map +1 -0
  172. package/dist/npm/resolve-one-facet.js +437 -0
  173. package/dist/npm/resolver.d.ts +109 -0
  174. package/dist/npm/resolver.d.ts.map +1 -0
  175. package/dist/npm/resolver.js +899 -0
  176. package/dist/npm/tarball-stream.d.ts +85 -0
  177. package/dist/npm/tarball-stream.d.ts.map +1 -0
  178. package/dist/npm/tarball-stream.js +205 -0
  179. package/dist/npm/tarball.d.ts +51 -0
  180. package/dist/npm/tarball.d.ts.map +1 -0
  181. package/dist/npm/tarball.js +185 -0
  182. package/dist/observability/diag-counters.d.ts +292 -0
  183. package/dist/observability/diag-counters.d.ts.map +1 -0
  184. package/dist/observability/diag-counters.js +302 -0
  185. package/dist/observability/heap-estimate.d.ts +166 -0
  186. package/dist/observability/heap-estimate.d.ts.map +1 -0
  187. package/dist/observability/heap-estimate.js +192 -0
  188. package/dist/observability/heavy-alloc-coord.d.ts +95 -0
  189. package/dist/observability/heavy-alloc-coord.d.ts.map +1 -0
  190. package/dist/observability/heavy-alloc-coord.js +149 -0
  191. package/dist/observability/oom-classify.d.ts +56 -0
  192. package/dist/observability/oom-classify.d.ts.map +1 -0
  193. package/dist/observability/oom-classify.js +118 -0
  194. package/dist/observability/oom-discriminator.d.ts +192 -0
  195. package/dist/observability/oom-discriminator.d.ts.map +1 -0
  196. package/dist/observability/oom-discriminator.js +233 -0
  197. package/dist/real-vite-bundle.generated.d.ts +46 -0
  198. package/dist/real-vite-bundle.generated.d.ts.map +1 -0
  199. package/dist/real-vite-bundle.generated.js +53 -0
  200. package/dist/replica/routing.d.ts +171 -0
  201. package/dist/replica/routing.d.ts.map +1 -0
  202. package/dist/replica/routing.js +288 -0
  203. package/dist/replica/suspension.d.ts +36 -0
  204. package/dist/replica/suspension.d.ts.map +1 -0
  205. package/dist/replica/suspension.js +49 -0
  206. package/dist/router/index.d.ts +156 -0
  207. package/dist/router/index.d.ts.map +1 -0
  208. package/dist/router/index.js +220 -0
  209. package/dist/runtime/assets-loader.d.ts +71 -0
  210. package/dist/runtime/assets-loader.d.ts.map +1 -0
  211. package/dist/runtime/assets-loader.js +128 -0
  212. package/dist/runtime/barrel-detect.d.ts +46 -0
  213. package/dist/runtime/barrel-detect.d.ts.map +1 -0
  214. package/dist/runtime/barrel-detect.js +73 -0
  215. package/dist/runtime/barrel-synthesizer.d.ts +181 -0
  216. package/dist/runtime/barrel-synthesizer.d.ts.map +1 -0
  217. package/dist/runtime/barrel-synthesizer.js +541 -0
  218. package/dist/runtime/bun-repl.d.ts +46 -0
  219. package/dist/runtime/bun-repl.d.ts.map +1 -0
  220. package/dist/runtime/bun-repl.js +326 -0
  221. package/dist/runtime/bun-runner.d.ts +66 -0
  222. package/dist/runtime/bun-runner.d.ts.map +1 -0
  223. package/dist/runtime/bun-runner.js +219 -0
  224. package/dist/runtime/clang-runner.d.ts +38 -0
  225. package/dist/runtime/clang-runner.d.ts.map +1 -0
  226. package/dist/runtime/clang-runner.js +1033 -0
  227. package/dist/runtime/comment-strip.d.ts +37 -0
  228. package/dist/runtime/comment-strip.d.ts.map +1 -0
  229. package/dist/runtime/comment-strip.js +74 -0
  230. package/dist/runtime/esbuild-service.d.ts +201 -0
  231. package/dist/runtime/esbuild-service.d.ts.map +1 -0
  232. package/dist/runtime/esbuild-service.js +1444 -0
  233. package/dist/runtime/esbuild-wasm-bytes.d.ts +87 -0
  234. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -0
  235. package/dist/runtime/esbuild-wasm-bytes.js +131 -0
  236. package/dist/runtime/framework-detect.d.ts +71 -0
  237. package/dist/runtime/framework-detect.d.ts.map +1 -0
  238. package/dist/runtime/framework-detect.js +178 -0
  239. package/dist/runtime/long-running-handle.d.ts +133 -0
  240. package/dist/runtime/long-running-handle.d.ts.map +1 -0
  241. package/dist/runtime/long-running-handle.js +192 -0
  242. package/dist/runtime/node-repl.d.ts +36 -0
  243. package/dist/runtime/node-repl.d.ts.map +1 -0
  244. package/dist/runtime/node-repl.js +293 -0
  245. package/dist/runtime/node-runner.d.ts +111 -0
  246. package/dist/runtime/node-runner.d.ts.map +1 -0
  247. package/dist/runtime/node-runner.js +194 -0
  248. package/dist/runtime/node-shims.d.ts +24 -0
  249. package/dist/runtime/node-shims.d.ts.map +1 -0
  250. package/dist/runtime/node-shims.js +3019 -0
  251. package/dist/runtime/package-manager.d.ts +118 -0
  252. package/dist/runtime/package-manager.d.ts.map +1 -0
  253. package/dist/runtime/package-manager.js +420 -0
  254. package/dist/runtime/port-registry.d.ts +80 -0
  255. package/dist/runtime/port-registry.d.ts.map +1 -0
  256. package/dist/runtime/port-registry.js +166 -0
  257. package/dist/runtime/process-logs-api.d.ts +102 -0
  258. package/dist/runtime/process-logs-api.d.ts.map +1 -0
  259. package/dist/runtime/process-logs-api.js +253 -0
  260. package/dist/runtime/process-logs.d.ts +311 -0
  261. package/dist/runtime/process-logs.d.ts.map +1 -0
  262. package/dist/runtime/process-logs.js +656 -0
  263. package/dist/runtime/process-table.d.ts +65 -0
  264. package/dist/runtime/process-table.d.ts.map +1 -0
  265. package/dist/runtime/process-table.js +107 -0
  266. package/dist/runtime/project-detect.d.ts +21 -0
  267. package/dist/runtime/project-detect.d.ts.map +1 -0
  268. package/dist/runtime/project-detect.js +46 -0
  269. package/dist/runtime/python-repl.d.ts +66 -0
  270. package/dist/runtime/python-repl.d.ts.map +1 -0
  271. package/dist/runtime/python-repl.js +563 -0
  272. package/dist/runtime/python-runner.d.ts +50 -0
  273. package/dist/runtime/python-runner.d.ts.map +1 -0
  274. package/dist/runtime/python-runner.js +870 -0
  275. package/dist/runtime/repl-session.d.ts +160 -0
  276. package/dist/runtime/repl-session.d.ts.map +1 -0
  277. package/dist/runtime/repl-session.js +393 -0
  278. package/dist/runtime/require-resolver.d.ts +52 -0
  279. package/dist/runtime/require-resolver.d.ts.map +1 -0
  280. package/dist/runtime/require-resolver.js +690 -0
  281. package/dist/runtime/router-basename.d.ts +36 -0
  282. package/dist/runtime/router-basename.d.ts.map +1 -0
  283. package/dist/runtime/router-basename.js +310 -0
  284. package/dist/runtime/ruby-repl.d.ts +43 -0
  285. package/dist/runtime/ruby-repl.d.ts.map +1 -0
  286. package/dist/runtime/ruby-repl.js +229 -0
  287. package/dist/runtime/ruby-runner.d.ts +62 -0
  288. package/dist/runtime/ruby-runner.d.ts.map +1 -0
  289. package/dist/runtime/ruby-runner.js +737 -0
  290. package/dist/runtime/runtime-catalog.d.ts +99 -0
  291. package/dist/runtime/runtime-catalog.d.ts.map +1 -0
  292. package/dist/runtime/runtime-catalog.js +183 -0
  293. package/dist/runtime/runtime-registry.d.ts +136 -0
  294. package/dist/runtime/runtime-registry.d.ts.map +1 -0
  295. package/dist/runtime/runtime-registry.js +318 -0
  296. package/dist/runtime/streams.d.ts +13 -0
  297. package/dist/runtime/streams.d.ts.map +1 -0
  298. package/dist/runtime/streams.js +402 -0
  299. package/dist/runtime/wasi-instance.d.ts +190 -0
  300. package/dist/runtime/wasi-instance.d.ts.map +1 -0
  301. package/dist/runtime/wasi-instance.js +2044 -0
  302. package/dist/runtime/wasm-runner.d.ts +117 -0
  303. package/dist/runtime/wasm-runner.d.ts.map +1 -0
  304. package/dist/runtime/wasm-runner.js +705 -0
  305. package/dist/session/bindings.d.ts +168 -0
  306. package/dist/session/bindings.d.ts.map +1 -0
  307. package/dist/session/bindings.js +524 -0
  308. package/dist/session/ctx-exports.d.ts +16 -0
  309. package/dist/session/ctx-exports.d.ts.map +1 -0
  310. package/dist/session/ctx-exports.js +22 -0
  311. package/dist/session/diag.d.ts +62 -0
  312. package/dist/session/diag.d.ts.map +1 -0
  313. package/dist/session/diag.js +118 -0
  314. package/dist/session/fs-watch.d.ts +132 -0
  315. package/dist/session/fs-watch.d.ts.map +1 -0
  316. package/dist/session/fs-watch.js +255 -0
  317. package/dist/session/helpers.d.ts +150 -0
  318. package/dist/session/helpers.d.ts.map +1 -0
  319. package/dist/session/helpers.js +472 -0
  320. package/dist/session/hibernation.d.ts +145 -0
  321. package/dist/session/hibernation.d.ts.map +1 -0
  322. package/dist/session/hibernation.js +385 -0
  323. package/dist/session/init-phases.d.ts +87 -0
  324. package/dist/session/init-phases.d.ts.map +1 -0
  325. package/dist/session/init-phases.js +137 -0
  326. package/dist/session/init.d.ts +46 -0
  327. package/dist/session/init.d.ts.map +1 -0
  328. package/dist/session/init.js +3579 -0
  329. package/dist/session/keys.d.ts +56 -0
  330. package/dist/session/keys.d.ts.map +1 -0
  331. package/dist/session/keys.js +55 -0
  332. package/dist/session/nimbus-session.d.ts +434 -0
  333. package/dist/session/nimbus-session.d.ts.map +1 -0
  334. package/dist/session/nimbus-session.js +1084 -0
  335. package/dist/session/programmatic.d.ts +105 -0
  336. package/dist/session/programmatic.d.ts.map +1 -0
  337. package/dist/session/programmatic.js +326 -0
  338. package/dist/session/replica-routes.d.ts +68 -0
  339. package/dist/session/replica-routes.d.ts.map +1 -0
  340. package/dist/session/replica-routes.js +77 -0
  341. package/dist/session/routes.d.ts +29 -0
  342. package/dist/session/routes.d.ts.map +1 -0
  343. package/dist/session/routes.js +1331 -0
  344. package/dist/session/rpc.d.ts +323 -0
  345. package/dist/session/rpc.d.ts.map +1 -0
  346. package/dist/session/rpc.js +808 -0
  347. package/dist/session/state-store.d.ts +210 -0
  348. package/dist/session/state-store.d.ts.map +1 -0
  349. package/dist/session/state-store.js +459 -0
  350. package/dist/session/supervisor-rpc.d.ts +269 -0
  351. package/dist/session/supervisor-rpc.d.ts.map +1 -0
  352. package/dist/session/supervisor-rpc.js +437 -0
  353. package/dist/session/ws-hibernation-config.d.ts +64 -0
  354. package/dist/session/ws-hibernation-config.d.ts.map +1 -0
  355. package/dist/session/ws-hibernation-config.js +89 -0
  356. package/dist/session/ws.d.ts +95 -0
  357. package/dist/session/ws.d.ts.map +1 -0
  358. package/dist/session/ws.js +395 -0
  359. package/dist/shell/features.d.ts +410 -0
  360. package/dist/shell/features.d.ts.map +1 -0
  361. package/dist/shell/features.js +1885 -0
  362. package/dist/shell/unix-commands.d.ts +15 -0
  363. package/dist/shell/unix-commands.d.ts.map +1 -0
  364. package/dist/shell/unix-commands.js +3703 -0
  365. package/dist/stubs/lifo-ui.d.ts +10 -0
  366. package/dist/stubs/lifo-ui.d.ts.map +1 -0
  367. package/dist/stubs/lifo-ui.js +1 -0
  368. package/dist/tailwind-play.generated.d.ts +19 -0
  369. package/dist/tailwind-play.generated.d.ts.map +1 -0
  370. package/dist/tailwind-play.generated.js +18 -0
  371. package/dist/vfs/dev-provider.d.ts +64 -0
  372. package/dist/vfs/dev-provider.d.ts.map +1 -0
  373. package/dist/vfs/dev-provider.js +191 -0
  374. package/dist/vfs/events.d.ts +51 -0
  375. package/dist/vfs/events.d.ts.map +1 -0
  376. package/dist/vfs/events.js +127 -0
  377. package/dist/vfs/path.d.ts +30 -0
  378. package/dist/vfs/path.d.ts.map +1 -0
  379. package/dist/vfs/path.js +41 -0
  380. package/dist/vfs/seed-project.d.ts +62 -0
  381. package/dist/vfs/seed-project.d.ts.map +1 -0
  382. package/dist/vfs/seed-project.js +1045 -0
  383. package/dist/vfs/sqlite-vfs.d.ts +435 -0
  384. package/dist/vfs/sqlite-vfs.d.ts.map +1 -0
  385. package/dist/vfs/sqlite-vfs.js +1670 -0
  386. package/dist/vfs/symlink-registry.d.ts +64 -0
  387. package/dist/vfs/symlink-registry.d.ts.map +1 -0
  388. package/dist/vfs/symlink-registry.js +137 -0
  389. package/dist/wrangler/nimbus-wrangler.d.ts +157 -0
  390. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -0
  391. package/dist/wrangler/nimbus-wrangler.js +931 -0
  392. package/package.json +77 -0
  393. package/public/_assets/cirrus-plugin-react.bundle.js +70740 -0
  394. package/public/_assets/esbuild-0.24.2.wasm +0 -0
  395. package/public/_assets/real-vite-bundle.js +101478 -0
  396. package/public/_assets/rollup.wasm +0 -0
  397. package/public/favicon.svg +8 -0
  398. package/public/index.html +565 -0
  399. package/public/s/index.html +2379 -0
  400. package/scripts/bundle-esbuild-wasm.mjs +244 -0
  401. package/scripts/bundle-facet-workers.mjs +137 -0
  402. package/scripts/bundle-git.mjs +118 -0
  403. package/scripts/bundle-npm-cjs.mjs +363 -0
  404. package/scripts/bundle-plugin-react.mjs +480 -0
  405. package/scripts/bundle-real-vite.mjs +959 -0
  406. package/scripts/bundle-runtime.mjs +600 -0
  407. package/scripts/bundle-tailwind-play.mjs +130 -0
  408. package/scripts/patch-lifo-ui.mjs +131 -0
@@ -0,0 +1,1331 @@
1
+ /**
2
+ * session/routes.ts — HTTP/WS fetch routing for the supervisor DO.
3
+ *
4
+ * One handleRequest function for everything the DO speaks:
5
+ * /ws upgrade → cold-init / warm-rejoin (B'.5) / 409
6
+ * /preview/* → cirrus-real or vite-dev-server forward
7
+ * /port/:n/* → user http-server proxy via port-registry
8
+ * /worker/* → nimbus-wrangler dev forward
9
+ * /api/_diag/* → forensic surfaces (memory, session, cirrus)
10
+ * /api/_test/* → NIMBUS_DEBUG-gated probe endpoints
11
+ * /api/* (other) → small JSON endpoints (write-file, mkdir, ...)
12
+ *
13
+ * The dispatcher is one big if/else by design — pattern-matching
14
+ * URL paths cleanly is easier to read than a Map-based router for
15
+ * this many one-off shapes, and grep-ability matters when debugging.
16
+ *
17
+ * Surfaces:
18
+ * - handleFetch(self, request) — top-level dispatcher; was _handleFetch.
19
+ *
20
+ * The class retains `fetch` (DO contract) + `_handleFetch` as delegators
21
+ * per plan §IX.4 R1.
22
+ *
23
+ * Per DEFECT-D1: route handlers read self.ctx + self.env extensively
24
+ * (~30 sites). RoutesHost = any pragmatic deviation, like InitHost in S6.
25
+ */
26
+ import { handleReplicaPreflight as _w12HandleReplicaPreflight } from '../replica/routing.js';
27
+ import { replicasSuspended as _w12ReplicasSuspended } from '../replica/suspension.js';
28
+ import { matchLogsPath, handleLogsWebSocketRequest, handleProcessesListRequest, } from '../runtime/process-logs-api.js';
29
+ import { readDiagCounters } from '../observability/diag-counters.js';
30
+ import { getFailures, getLastRpcFrame, getLastFacetId, getRecoveryEvents, recordRecoveryEvent, resetRecoveryEvents, } from '../observability/oom-discriminator.js';
31
+ import { LRU_MAX_ENTRIES } from '../constants.js';
32
+ import { estimateSupervisorHeap, WORKERD_EVICTION_LABELS } from '../observability/heap-estimate.js';
33
+ import { loadShellState, loadKernelMounts, getScrollbackStats, clearSessionState, appendScrollback, loadScrollback } from './state-store.js';
34
+ import { isWarmRejoin, joinExistingSession } from './init-phases.js';
35
+ import { EsbuildService } from '../runtime/esbuild-service.js';
36
+ import { ViteDevServer } from '../facets/vite-dev-server.js';
37
+ import { ProcessLogStore } from '../runtime/process-logs.js';
38
+ import { notifyTerminalEvent } from '../runtime/process-logs-api.js';
39
+ import { makeLongRunningPortStub } from '../runtime/long-running-handle.js';
40
+ import { getLoadedCodesStats } from './bindings.js';
41
+ import { renderNoDevServerHtml } from './helpers.js';
42
+ // CLN-1 (2026-05-11): also import R2_CACHE_PREFIX + L2_KEY_HOST so the
43
+ // cache-purge helpers below don't hardcode the synthetic key shape.
44
+ // Bumping R2_CACHE_PREFIX 'v1' → 'v2' now invalidates the L2 cache key
45
+ // shape used here automatically.
46
+ import { R2CacheClient, R2_CACHE_PREFIX, L2_KEY_HOST } from '../npm/r2-cache.js';
47
+ import { fetchEsbuildWasmBytes, ESBUILD_WASM_L2_KEY } from '../runtime/esbuild-wasm-bytes.js';
48
+ import { NimbusFanoutPool, IN_DO_THRESHOLD, MAX_PEER_FANOUT, hashKeyToShard } from '../loaders/fanout-pool.js';
49
+ export async function handleFetch(self, request) {
50
+ const url = new URL(request.url);
51
+ // Capture session basePath from the routing header (if forwarded by the
52
+ // Worker's session-router). Threaded through to ViteDevServer so the
53
+ // served app's module URLs, HMR paths, <base href>, and router basename
54
+ // all resolve under `/s/<id>/preview/...`.
55
+ await self.hydrateSessionBasePath(request);
56
+ // W9: bump the isolate generation counter on the FIRST request of a
57
+ // new isolate (cold start or post-hibernation wake). Cheap — one
58
+ // storage.get + one storage.put per isolate. Subsequent calls in the
59
+ // same isolate are a fast no-op (gated by _w9IsolateGenPersisted).
60
+ await self._w9MaybeBumpIsolateGen();
61
+ // ── W12 — DO read replica preflight ─────────────────────────────────
62
+ //
63
+ // If THIS isolate is a replica AND the route policy says delegate, we
64
+ // forward the Request to the primary via `ctx.storage.primary.fetch()`
65
+ // and return the primary's Response. Single intra-region RPC hop:
66
+ // the replica was placed near the primary, so this is fast; the user
67
+ // experiences edge-RTT-to-replica + RPC + primary-handle, which is
68
+ // strictly less than user-RTT-to-far-region for cross-region tenants.
69
+ //
70
+ // On the primary OR for replica-eligible routes (with cold/warm
71
+ // distinction handled), `delegated === false` and we fall through to
72
+ // the existing route handlers unchanged.
73
+ //
74
+ // Graceful-degrade: if `inspectReplicaState` reports isReplica but the
75
+ // primary stub is unusable, `handleReplicaPreflight` returns
76
+ // `delegated: false` and we handle locally — correctness > latency.
77
+ //
78
+ // Performance note: the preflight is <1ms (pure pathname classification
79
+ // + a `typeof` check on `ctx.storage.primary`). Hot path.
80
+ try {
81
+ const w12Pre = await _w12HandleReplicaPreflight(self.ctx, request, {
82
+ isWarm: !!(self.viteDevServer?.isRunning || self.cirrusReal?.isRunning),
83
+ suspended: _w12ReplicasSuspended(),
84
+ });
85
+ if (w12Pre.delegated && w12Pre.response) {
86
+ return w12Pre.response;
87
+ }
88
+ }
89
+ catch (e) {
90
+ // Preflight should never throw, but never let a routing helper kill
91
+ // request handling. Log + continue with local handling.
92
+ console.warn('[nimbus/W12] preflight threw:', e?.message);
93
+ }
94
+ if (url.pathname === '/ws') {
95
+ if (request.headers.get('Upgrade') !== 'websocket') {
96
+ return new Response('Expected WebSocket', { status: 426 });
97
+ }
98
+ // [B'.5] Three-way decision on a /ws upgrade:
99
+ // 1. Warm rejoin: a wsClose/wsError fired earlier and left
100
+ // kernel/shell/terminal alive in-memory. Re-attach the
101
+ // new ws to the existing Shell — Phase B skipped.
102
+ // 2. Cold init: no shell yet (first connect, or post-DO-
103
+ // eviction). Run the full R/B/W/O sequence.
104
+ // 3. Active conflict: Shell is non-null AND phase != drained,
105
+ // meaning some other /ws is already attached. 409 to
106
+ // prevent two-tab cross-wiring (multi-tab share is a
107
+ // separate feature; B'.5 doesn't enable it).
108
+ const pair = new WebSocketPair();
109
+ const [client, server] = Object.values(pair);
110
+ self.ctx.acceptWebSocket(server);
111
+ try {
112
+ server.serializeAttachment?.({ kind: 'shell' });
113
+ }
114
+ catch { }
115
+ if (isWarmRejoin(self)) {
116
+ // Warm rejoin path. The existing Shell is alive; we just
117
+ // swap the WebSocketTerminal's ws ref + replay scrollback.
118
+ try {
119
+ joinExistingSession(self, server, appendScrollback, loadScrollback);
120
+ }
121
+ catch (err) {
122
+ console.error('warm-rejoin error:', err?.message, err?.stack);
123
+ return new Response('Rejoin failed: ' + err?.message, { status: 500 });
124
+ }
125
+ return new Response(null, { status: 101, webSocket: client });
126
+ }
127
+ if (self.shell != null) {
128
+ // Active conflict — Shell exists and isn't drained. Some
129
+ // other /ws is attached; reject this one.
130
+ return new Response(JSON.stringify({
131
+ error: 'session already has active terminal',
132
+ hint: 'open a new /new session',
133
+ }), {
134
+ status: 409,
135
+ headers: { 'Content-Type': 'application/json' },
136
+ });
137
+ }
138
+ // Cold init path — first ever /ws (or post-DO-eviction).
139
+ try {
140
+ self.initSession(server);
141
+ }
142
+ catch (err) {
143
+ console.error('initSession error:', err?.message, err?.stack);
144
+ return new Response('Init failed: ' + err?.message, { status: 500 });
145
+ }
146
+ return new Response(null, { status: 101, webSocket: client });
147
+ }
148
+ // ── Process log streaming / listing — see src/process-logs-api.ts ──
149
+ const logsPid = matchLogsPath(url.pathname);
150
+ if (logsPid !== null) {
151
+ return handleLogsWebSocketRequest(request, logsPid, {
152
+ processLogs: self.processLogs,
153
+ processTable: self.processTable,
154
+ // W9: pass ctx so the upgrade uses ctx.acceptWebSocket (hibernatable).
155
+ ctx: self.ctx,
156
+ });
157
+ }
158
+ if (url.pathname === '/api/processes') {
159
+ return handleProcessesListRequest(self.processTable, self.processLogs);
160
+ }
161
+ // Primitives wave (P11) — kill / restart by PID for the Process tab UI.
162
+ //
163
+ // Both endpoints accept POST only. The body is empty; the PID is in the
164
+ // URL. Same authorization model as the rest of /api/* (session-scoped
165
+ // via the /s/<id>/ prefix).
166
+ //
167
+ // Behaviour:
168
+ // POST /api/kill/<pid> — equivalent to typing `kill <pid>` in
169
+ // the shell. Tears down a vite-shim PID
170
+ // cleanly (stops viteDevServer, deletes
171
+ // vite-config) OR delegates to
172
+ // facetManager.kill for real facets.
173
+ // POST /api/restart/<pid> — kill + (if it was the vite shim)
174
+ // re-run `vite` with the same argv to
175
+ // boot a fresh server. For real facets,
176
+ // currently only kills (caller must
177
+ // re-issue spawn). Same response shape.
178
+ //
179
+ // Response: 200 {ok, pid, action} on success; 404 {error, pid} when
180
+ // PID isn't tracked; 502 on internal failure.
181
+ {
182
+ const killMatch = url.pathname.match(/^\/api\/(kill|restart)\/(\d+)$/);
183
+ if (killMatch && request.method === 'POST') {
184
+ const action = killMatch[1];
185
+ const pid = parseInt(killMatch[2], 10);
186
+ const json = (status, body) => new Response(JSON.stringify(body), {
187
+ status, headers: { 'Content-Type': 'application/json' },
188
+ });
189
+ if (!Number.isFinite(pid) || pid <= 0) {
190
+ return json(400, { error: 'invalid pid', pid });
191
+ }
192
+ const entry = self.processTable.get(pid);
193
+ if (!entry) {
194
+ return json(404, { error: 'no such process', pid });
195
+ }
196
+ const isViteShim = self._viteShimPid === pid;
197
+ try {
198
+ if (isViteShim) {
199
+ // Same teardown the `kill` shell handler does. Centralised
200
+ // here so the UI doesn't need to reimplement it.
201
+ try {
202
+ if (self.cirrusReal?.isRunning) {
203
+ self.cirrusReal.stop(self.ctx);
204
+ self.cirrusReal = null;
205
+ }
206
+ if (self.viteDevServer?.isRunning) {
207
+ self.viteDevServer.stop();
208
+ self.viteDevServer = null;
209
+ try {
210
+ await self.ctx.storage.delete('vite-config');
211
+ }
212
+ catch { }
213
+ }
214
+ }
215
+ catch { /* keep going to teardown processTable / portRegistry */ }
216
+ try {
217
+ self.portRegistry.unregisterByPid(pid);
218
+ }
219
+ catch { }
220
+ try {
221
+ self.processTable.kill(pid);
222
+ }
223
+ catch { }
224
+ self._viteShimPid = null;
225
+ self._viteShimPort = null;
226
+ }
227
+ else if (self.facetManager) {
228
+ const ok = self.facetManager.kill(pid);
229
+ if (!ok)
230
+ return json(404, { error: 'facetManager.kill returned false', pid });
231
+ }
232
+ else {
233
+ // No facetManager and not a vite shim — best-effort
234
+ // process-table tombstone so the UI can re-render the badge.
235
+ try {
236
+ self.portRegistry.unregisterByPid(pid);
237
+ }
238
+ catch { }
239
+ try {
240
+ self.processTable.kill(pid);
241
+ }
242
+ catch { }
243
+ }
244
+ }
245
+ catch (e) {
246
+ return json(502, { error: String(e?.message || e), pid });
247
+ }
248
+ // For 'restart', re-issue the equivalent of `vite` in the shell
249
+ // when the killed PID was the vite shim. We do NOT generically
250
+ // restart arbitrary processes — the supervisor doesn't keep
251
+ // enough argv/env state to do that safely for real facets.
252
+ if (action === 'restart' && isViteShim) {
253
+ // Send a synthetic 'vite' command line to the terminal so the
254
+ // existing registry handler runs. This is the simplest way to
255
+ // re-trigger the SAME code path the user originally invoked,
256
+ // without duplicating its 100-line setup here.
257
+ if (self.terminal?.ws) {
258
+ try {
259
+ self.terminal.ws.send(JSON.stringify({ type: 'output', data: '\r\n' }));
260
+ }
261
+ catch { }
262
+ }
263
+ // Drop a marker so a future probe can assert on the action.
264
+ notifyTerminalEvent(self.terminal, {
265
+ type: 'restart-requested', pid, command: entry.command || 'vite',
266
+ });
267
+ }
268
+ return json(200, { ok: true, pid, action });
269
+ }
270
+ }
271
+ if (url.pathname === '/api/memory') {
272
+ // Minimal memory probe for stability investigations (WORKERD-CRASH
273
+ // hypotheses). Reports whatever we can measure inside workerd:
274
+ // - vfs.{totalFiles, totalBytes} from the SQLite VFS
275
+ // - process.memoryUsage() if nodejs_compat exposes it (else zeros)
276
+ // - performance.memory when present (Chromium-style heap info)
277
+ self.ensureSqliteFs();
278
+ let nodeMem = null;
279
+ try {
280
+ const g = globalThis;
281
+ if (g.process && typeof g.process.memoryUsage === 'function') {
282
+ const mu = g.process.memoryUsage();
283
+ nodeMem = {
284
+ rss: mu.rss | 0,
285
+ heapTotal: mu.heapTotal | 0,
286
+ heapUsed: mu.heapUsed | 0,
287
+ external: mu.external | 0,
288
+ arrayBuffers: mu.arrayBuffers | 0,
289
+ };
290
+ }
291
+ }
292
+ catch { /* ignore */ }
293
+ let perfMem = null;
294
+ try {
295
+ const g = globalThis;
296
+ if (g.performance && g.performance.memory) {
297
+ perfMem = {
298
+ jsHeapSizeLimit: g.performance.memory.jsHeapSizeLimit | 0,
299
+ totalJSHeapSize: g.performance.memory.totalJSHeapSize | 0,
300
+ usedJSHeapSize: g.performance.memory.usedJSHeapSize | 0,
301
+ };
302
+ }
303
+ }
304
+ catch { /* ignore */ }
305
+ const vfs = self.sqliteFs.getStats();
306
+ return Response.json({
307
+ vfs: { files: vfs.files, usedBytes: vfs.usedBytes },
308
+ nodeMem,
309
+ perfMem,
310
+ ts: Date.now(),
311
+ });
312
+ }
313
+ // ── Diagnostic memory probe ──────────────────────────────────────────
314
+ // /api/_diag/memory — supervisor heap estimate, eviction taxonomy,
315
+ // and recovery-event ring.
316
+ //
317
+ // Why this endpoint exists
318
+ // ────────────────────────
319
+ // workerd's process.memoryUsage() returns zero for every field inside
320
+ // a DO class context (verified at audit/sections/PROD-RESET-RESEARCH-
321
+ // R1.md §R1.4). The previous endpoint reported nodeMem/perfMem from
322
+ // process.memoryUsage() and they were always zero — useless.
323
+ //
324
+ // C'.1 replaces the zero-everywhere readout with a deterministic
325
+ // estimator (src/observability/heap-estimate.ts) that sums known
326
+ // supervisor allocations from runtime counters. Every byte has a
327
+ // named contributor.
328
+ //
329
+ // C'.2 adds a recovery_event ring (src/oom-discriminator.ts) so
330
+ // probes can assert that lifecycle transitions preserve session
331
+ // state without data loss.
332
+ //
333
+ // Schema (v3, additive over v2):
334
+ // - heap: deterministic estimate + per-source breakdown + ceiling.
335
+ // - evictionLabels: workerd taxonomy (5 reasons).
336
+ // - recoveryEvents: ring of session lifecycle transitions.
337
+ // - All v1/v2 fields preserved for back-compat with existing tools.
338
+ if (url.pathname === '/api/_diag/memory') {
339
+ self.ensureSqliteFs();
340
+ self._diagSampleMemory();
341
+ const nodeMem = self._diagReadNodeMem();
342
+ const perfMem = self._diagReadPerfMem();
343
+ const vfs = self.sqliteFs.getStats();
344
+ const DO_HEAP_LIMIT_BYTES = 128 * 1024 * 1024;
345
+ const heapUsed = nodeMem?.heapUsed ?? 0;
346
+ const counters = readDiagCounters();
347
+ const cacheStats = vfs.cache ?? {};
348
+ const lastFailures = getFailures();
349
+ // ── C'.1 deterministic heap estimate ─────────────────────────────
350
+ // Sources every contributing byte from a runtime counter — never
351
+ // calls process.memoryUsage(). Ceiling is the architectural soft
352
+ // budget (SUPERVISOR_HEAP_CEILING_BYTES = 64 MiB), half the
353
+ // workerd hard cap of 128 MiB.
354
+ // N3 (heap-correctness wave). Pre-fix, this was hardcoded 0 with
355
+ // the comment "matches reality (writes are flushed in
356
+ // microseconds)" — which was wrong: pendingWrites can hold up to
357
+ // 500 chunks × 64 KiB = 32 MiB at peak, AND writeStream's spool
358
+ // can buffer the full incoming batch. Both were invisible to the
359
+ // estimator because there was no counter to read.
360
+ //
361
+ // Post-fix, SqliteVFS maintains TWO running byte sums:
362
+ // - _pendingWriteBytes : the post-deferWrite queue
363
+ // - _writeStreamSpoolBytes : N2 spool inside writeStream
364
+ // Both contribute to "in-flight write bytes the supervisor is
365
+ // currently holding"; the estimator sees their sum.
366
+ // (vfs.sql is the sub-object in getStats() that surfaces these.)
367
+ const sqlStats = vfs.sql ?? {};
368
+ const inFlightWriteBytes = (sqlStats.pendingWriteBytes ?? 0) +
369
+ (sqlStats.writeStreamSpoolBytes ?? 0);
370
+ const heap = estimateSupervisorHeap(counters, {
371
+ cacheHotBytes: cacheStats.hotBytes ?? 0,
372
+ inFlightWriteBytes,
373
+ });
374
+ return Response.json({
375
+ // ── v1 fields (preserved) ─────────────────────────────────
376
+ vfs: { files: vfs.files, usedBytes: vfs.usedBytes },
377
+ nodeMem,
378
+ perfMem,
379
+ peak: {
380
+ rssBytes: self._diagPeakRss,
381
+ heapUsedBytes: self._diagPeakHeapUsed,
382
+ atMs: self._diagPeakAt,
383
+ samples: self._diagSampleCount,
384
+ },
385
+ counters,
386
+ limitBytes: DO_HEAP_LIMIT_BYTES,
387
+ usagePctOfLimit: heapUsed > 0
388
+ ? Math.round((heapUsed / DO_HEAP_LIMIT_BYTES) * 1000) / 10
389
+ : 0,
390
+ ts: Date.now(),
391
+ // ── v2 / W5 additions (preserved) ─────────────────────────
392
+ lastFailures,
393
+ vfsDetail: {
394
+ lruBytes: cacheStats.hotBytes ?? 0,
395
+ lruMaxEntries: cacheStats.maxEntries ?? LRU_MAX_ENTRIES,
396
+ lruMaxBytes: cacheStats.maxBytes ?? (LRU_MAX_ENTRIES * 65536),
397
+ lruShrunk: cacheStats.lruShrunk ?? false,
398
+ evictions: cacheStats.evictions ?? 0,
399
+ hitRate: cacheStats.hitRate ?? 0,
400
+ // N3 (heap-correctness wave): pending-writes observability.
401
+ // `pendingWrites` is the entry count (unchanged); the new
402
+ // `pendingWriteBytes` is the live byte total maintained by
403
+ // SqliteVFS._pendingWriteBytes. Probes use the pair to
404
+ // distinguish "many small chunks" from "few large chunks".
405
+ pendingWrites: sqlStats.pendingWrites ?? 0,
406
+ pendingWriteBytes: sqlStats.pendingWriteBytes ?? 0,
407
+ // N2: live byte count inside the writeStream() drain spool.
408
+ // Visible during a real npm install; ~0 at rest.
409
+ writeStreamSpoolBytes: sqlStats.writeStreamSpoolBytes ?? 0,
410
+ },
411
+ // H7 (heap-correctness wave): _NIMBUS_LOADED_CODES Map state.
412
+ // Pre-fix this Map grew unbounded — wrangler dev's rebuild
413
+ // loop accumulated one entry per save until the supervisor
414
+ // hit the workerd 128 MiB hard cap. Post-fix: hard-cap LRU
415
+ // (32 entries) with FIFO eviction. The counters here let
416
+ // ops dashboards visualise the bound + the eviction rate.
417
+ loadedCodes: getLoadedCodesStats(),
418
+ rpc: {
419
+ lastFrame: getLastRpcFrame(),
420
+ },
421
+ facet: {
422
+ lastDispatch: getLastFacetId(),
423
+ },
424
+ // ── v3 / C' observability foundation ──────────────────────
425
+ heap,
426
+ evictionLabels: WORKERD_EVICTION_LABELS,
427
+ recoveryEvents: getRecoveryEvents(),
428
+ // ── W9: hibernation observability ───────────────────────────
429
+ // `hib.isolateGen` increments per fresh isolate (cold start or
430
+ // post-hibernation wake). Two probe calls a minute apart with
431
+ // different gens means a hibernation/wake cycle ran in between.
432
+ // `rehydrated*` counters are >0 only on the first hydrate after
433
+ // a wake. `flushed*` counters track the alarm-driven SQL writes.
434
+ // `autoResponseConfigured` reports the runtime's actual
435
+ // capability (older workerd builds report false).
436
+ hib: {
437
+ isolateGen: self._w9IsolateGen,
438
+ autoResponseConfigured: self._w9WsConfig?.autoResponseConfigured ?? false,
439
+ autoResponseError: self._w9WsConfig?.autoResponseError ?? null,
440
+ hibernationEventTimeoutMs: self._w9WsConfig?.timeoutSetMs ?? null,
441
+ timeoutError: self._w9WsConfig?.timeoutError ?? null,
442
+ ...self.processLogs.hibStats(),
443
+ },
444
+ // ── W12: replica observability ──────────────────────────────
445
+ // `replica.state` is one of 'enabled' / 'enabled-via-configure' /
446
+ // 'unsupported' / 'error' / 'unknown' (per tryEnableReplicas
447
+ // result; pre-GA runtimes get 'unsupported' graceful-degrade).
448
+ // `isReplica` is true when this isolate is a regional read
449
+ // replica (its `ctx.storage.primary` is an RpcStub). `bookmark`
450
+ // reflects ctx.storage.getCurrentBookmark() if the API surfaces
451
+ // it — used by future read-your-writes wait-for-bookmark wiring.
452
+ // `suspended` reflects the global write-burst guard (npm install
453
+ // / git clone in flight) per CF research §G.4 + ~lambros feedback.
454
+ replica: self.getReplicaState(),
455
+ });
456
+ }
457
+ // ── /api/_diag/session — Track B' state-store debug surface [B'.1] ──
458
+ //
459
+ // Exposes the persisted shell state so the probe at
460
+ // audit/probes/b-prime/b1-shell-state/ can verify the SQL row
461
+ // shape directly. Read-only; no side effects. Always returns
462
+ // 200 even when no row exists (the snapshot just shows
463
+ // hasPersistedState=false).
464
+ if (url.pathname === '/api/_diag/session') {
465
+ const snap = loadShellState(self.ctx);
466
+ const mounts = loadKernelMounts(self.ctx);
467
+ const sbStats = getScrollbackStats(self.ctx);
468
+ return Response.json({
469
+ cwd: snap.cwd,
470
+ env: snap.env,
471
+ hydratedAt: snap.hydratedAt,
472
+ hasPersistedState: snap.hasPersistedState,
473
+ // [B'.2] persisted kernel mount list — empty before first
474
+ // initSession, populated after.
475
+ mounts,
476
+ // [B'.3] scrollback stats — rows, total bytes, byte cap.
477
+ scrollbackRows: sbStats.rows,
478
+ scrollbackBytes: sbStats.bytes,
479
+ scrollbackMaxBytes: sbStats.maxBytes,
480
+ // [B'.4] live initSession phase. null pre-first-init;
481
+ // 'rehydrate'/'build'/'wire'/'online' during init progress;
482
+ // 'hydrated' after init completes; 'drained' after wsClose.
483
+ phase: self._b4Phase ?? null,
484
+ // [B'.5] count of /ws upgrades that took the warm-rejoin
485
+ // path (Phase B skipped). Probes assert ≥1 after a forced
486
+ // close + reconnect on the same isolate.
487
+ warmJoinCount: self._b4WarmJoinCount ?? 0,
488
+ // Live shell state — useful for confirming the in-memory
489
+ // shell agrees with SQL. Null when no shell is currently
490
+ // attached (between wsClose and next /ws upgrade).
491
+ liveCwd: (() => { try {
492
+ return self.shell?.getCwd() ?? null;
493
+ }
494
+ catch {
495
+ return null;
496
+ } })(),
497
+ liveEnvKeys: (() => {
498
+ try {
499
+ const e = self.shell?.getEnv();
500
+ return e ? Object.keys(e).sort() : null;
501
+ }
502
+ catch {
503
+ return null;
504
+ }
505
+ })(),
506
+ ts: Date.now(),
507
+ });
508
+ }
509
+ // ── [D'.1] /api/_diag/cirrus — cirrus-real DO Facet diagnostics ─────
510
+ //
511
+ // Returns null when cirrus-real is not running (no NIMBUS_REAL_VITE
512
+ // session yet). When running, returns the supervisor-side dispatch
513
+ // shape (kind = 'do-facet') + the in-facet identity cookie (proves
514
+ // own-SQLite is working and survives ctx.facets warm reuse).
515
+ //
516
+ // Probe at audit/probes/d-prime/d1-cirrus-real-facet/ asserts kind
517
+ // and cookie persistence across forced supervisor reconnect.
518
+ if (url.pathname === '/api/_diag/cirrus') {
519
+ if (!self.cirrusReal) {
520
+ return Response.json({ running: false, kind: null });
521
+ }
522
+ try {
523
+ const diag = await self.cirrusReal.getDiag();
524
+ return Response.json({ running: true, ...diag });
525
+ }
526
+ catch (e) {
527
+ return Response.json({ running: true, error: e?.message || String(e) }, { status: 500 });
528
+ }
529
+ }
530
+ // ── W9: hibernation simulation + diagnostic spawn (NIMBUS_DEBUG=1) ──
531
+ //
532
+ // These endpoints exist to let local probes exercise the cross-
533
+ // hibernation code path. Real DO hibernation only happens in prod;
534
+ // wrangler dev keeps state across requests. So we simulate the
535
+ // "fresh isolate per dispatch" rule by clearing the in-memory
536
+ // ProcessLogStore — the next read MUST hydrate from SQL.
537
+ //
538
+ // 404 when NIMBUS_DEBUG isn't set, so prod isn't a free vector.
539
+ if (url.pathname.startsWith('/api/_test/')) {
540
+ if (!self.nimbusDebug) {
541
+ return new Response('not found', { status: 404 });
542
+ }
543
+ if (url.pathname === '/api/_test/hib/simulate' && request.method === 'POST') {
544
+ // Drain any pending writes first so SQL is the source of truth,
545
+ // then nuke the in-memory ring. The next read on any pid will
546
+ // re-hydrate via the adapter.
547
+ try {
548
+ self.processLogs.flush();
549
+ }
550
+ catch { }
551
+ const fresh = new ProcessLogStore();
552
+ // Re-wire persist on the new store (mirrors constructor path).
553
+ self._w9PersistWired = false;
554
+ self.processLogs = fresh;
555
+ self._w9WireProcessLogPersist();
556
+ return Response.json({ cleared: true, ts: Date.now() });
557
+ }
558
+ if (url.pathname === '/api/_test/spawn-emitter' && request.method === 'POST') {
559
+ // Spawns a synthetic emitter directly into ProcessLogStore +
560
+ // ProcessTable without going through FacetManager. Lets the e2e
561
+ // probe drive the W9 code path without a real long-running
562
+ // facet (which the test environment may not support).
563
+ try {
564
+ const body = await request.json();
565
+ const lines = Math.max(1, Math.min(1000, Number(body.lines) || 50));
566
+ const text = String(body.lineText || 'line');
567
+ const entry = self.processTable.spawn(`_test:${text}`, ['_test'], '/');
568
+ const pid = entry.pid;
569
+ for (let i = 0; i < lines; i++) {
570
+ self.processLogs.append(pid, 'stdout', `${text} ${i}\n`);
571
+ }
572
+ // Force-flush so SQL reflects state before the next request.
573
+ try {
574
+ self.processLogs.flush();
575
+ }
576
+ catch { }
577
+ return Response.json({ pid, lines });
578
+ }
579
+ catch (e) {
580
+ return Response.json({ error: e?.message }, { status: 400 });
581
+ }
582
+ }
583
+ if (url.pathname === '/api/_test/log-tail' && request.method === 'GET') {
584
+ const pid = parseInt(url.searchParams.get('pid') || '', 10);
585
+ const linesQ = parseInt(url.searchParams.get('lines') || '0', 10) || undefined;
586
+ if (!Number.isFinite(pid) || pid <= 0) {
587
+ return Response.json({ error: 'bad pid' }, { status: 400 });
588
+ }
589
+ const chunks = self.processLogs.tail(pid, linesQ ? { lines: linesQ } : {});
590
+ const allText = chunks.map((c) => c.data).join('');
591
+ const lines = allText.split('\n').filter((l) => l !== '');
592
+ return Response.json({ pid, lines, chunkCount: chunks.length });
593
+ }
594
+ // ── C'.2 recovery-event test endpoints ────────────────────────────
595
+ // Used by audit/probes/c-prime/recovery-events/ to verify the ring
596
+ // schema works pre-Track-B'. Once Track B' transitions land, real
597
+ // events flow through the ring naturally — these endpoints stay
598
+ // for synthetic-trigger probes (audit/probes/interactive-liveness/
599
+ // error-recovery/).
600
+ if (url.pathname === '/api/_test/recovery-event/record' && request.method === 'POST') {
601
+ const body = await request.json();
602
+ recordRecoveryEvent({
603
+ at: Number(body.at) || Date.now(),
604
+ fromState: String(body.fromState ?? 'cold'),
605
+ toState: String(body.toState ?? 'hydrated'),
606
+ trigger: String(body.trigger ?? 'manual-test'),
607
+ isolateGen: Number(body.isolateGen) || self._w9IsolateGen,
608
+ dataLoss: !!body.dataLoss,
609
+ snapshotKeysRehydrated: Number(body.snapshotKeysRehydrated) || 0,
610
+ notes: body.notes ? String(body.notes) : undefined,
611
+ });
612
+ return Response.json({ recorded: true, ringSize: getRecoveryEvents().length });
613
+ }
614
+ if (url.pathname === '/api/_test/recovery-event/reset' && request.method === 'POST') {
615
+ resetRecoveryEvents();
616
+ return Response.json({ reset: true });
617
+ }
618
+ // ── B'.1 session-state reset ─────────────────────────────────────
619
+ // Drops every nimbus_session_kv / nimbus_kernel_mounts /
620
+ // nimbus_terminal_scrollback row so the next /ws upgrade
621
+ // takes the cold-start path (Phase O fires; banner reprints).
622
+ // Used by probes to start each scenario from a known-empty state.
623
+ if (url.pathname === '/api/_test/session/reset' && request.method === 'POST') {
624
+ clearSessionState(self.ctx);
625
+ return Response.json({ reset: true });
626
+ }
627
+ // ── cache-and-scrub L2 cache benchmark ───────────────────────────
628
+ // Used by audit/probes/cache-and-scrub/* to measure the latency
629
+ // contrast between a cold L3-only path and a warm L2-served
630
+ // path. The probe asserts the warm path is ≥5× faster than the
631
+ // cold path (the wave's hard ship-gate).
632
+ //
633
+ // Pattern (per cache-layer probe):
634
+ // 1. POST /api/_test/cache/<layer>/reset — purge L2 entry
635
+ // 2. POST /api/_test/cache/<layer>/seed — write L3 entry
636
+ // 3. GET /api/_test/cache/<layer>/bench?n=N — N timed reads
637
+ //
638
+ // The bench endpoint returns latencies[] in ms (high-res via
639
+ // `performance.now()`) so the probe can compute median, ratios,
640
+ // hit-flag from response headers, etc.
641
+ if (url.pathname.startsWith('/api/_test/cache/')) {
642
+ return await handleCacheTestEndpoint(self, url, request);
643
+ }
644
+ // ── two-tier-fanout primitive probe ──────────────────────────────
645
+ // Used by audit/probes/two-tier-fanout/* to measure the
646
+ // POC C in-DO and POC B peer-DO speedups via the
647
+ // NimbusFanoutPool primitive. Independent of any specific
648
+ // production site (install-batch, pre-bundle, etc.) so the
649
+ // primitive's behavior can be measured cleanly without
650
+ // confounders.
651
+ if (url.pathname.startsWith('/api/_test/fanout/')) {
652
+ return await handleFanoutTestEndpoint(self, url, request);
653
+ }
654
+ return new Response('unknown _test endpoint', { status: 404 });
655
+ }
656
+ if (url.pathname === '/api/stats') {
657
+ self.ensureSqliteFs();
658
+ const vfsStats = self.sqliteFs.getStats();
659
+ const processStats = self.processTable.stats;
660
+ const logStoreStats = self.processLogs.stats;
661
+ // Preview UI polls vite.running to decide between /preview/ and
662
+ // the "no dev server" placeholder. We report running:true if
663
+ // EITHER the Cirrus in-process ViteDevServer OR the opt-in
664
+ // real-vite facet (cirrusReal) is live. Without this merge, a
665
+ // session on NIMBUS_REAL_VITE=1 saw vite.running=false even
666
+ // while real-vite was happily serving on /preview/.
667
+ const legacyViteStats = self.viteDevServer?.stats || null;
668
+ const cirrusRealRunning = !!self.cirrusReal?.isRunning;
669
+ const viteStats = cirrusRealRunning
670
+ ? {
671
+ running: true,
672
+ root: legacyViteStats?.root ?? 'home/user/app',
673
+ backend: 'real',
674
+ }
675
+ : legacyViteStats;
676
+ const wranglerStats = self.nimbusWrangler?.stats || null;
677
+ const portStats = self.portRegistry.stats;
678
+ // Audit C3: same-origin only. The UI shell at /s/<id>/ polls this
679
+ // from its own origin; no cross-origin reader is intended. If
680
+ // future embeds need cross-origin reads, add an explicit origin
681
+ // allowlist — not a wildcard.
682
+ return Response.json({ ...vfsStats, processes: processStats, logStore: logStoreStats, ports: portStats, vite: viteStats, wrangler: wranglerStats });
683
+ }
684
+ // ── File write API: bypasses shell for fast bulk seeding ──
685
+ // Audit C3: mutation endpoints do NOT advertise any CORS policy.
686
+ // A cross-origin page that learns a session ID would otherwise be
687
+ // able to write arbitrary files through the user's logged-in tab.
688
+ // Same-origin POSTs from the session shell still work — SOP (not
689
+ // CORS) governs them, so no preflight is emitted; cross-origin
690
+ // requests are rejected by the browser before reaching the Worker.
691
+ if (url.pathname === '/api/write-file' && request.method === 'POST') {
692
+ self.ensureSqliteFs();
693
+ try {
694
+ const body = await request.json();
695
+ const path = String(body.path).replace(/^\/+/, '');
696
+ // Ensure parent dirs
697
+ const parts = path.split('/');
698
+ for (let i = 1; i < parts.length; i++) {
699
+ const dir = parts.slice(0, i).join('/');
700
+ if (dir && !self.sqliteFs.exists(dir))
701
+ self.sqliteFs.mkdir(dir, { recursive: true });
702
+ }
703
+ self.sqliteFs.writeFile(path, String(body.content));
704
+ return Response.json({ ok: true, path });
705
+ }
706
+ catch (e) {
707
+ return Response.json({ error: e?.message }, { status: 400 });
708
+ }
709
+ }
710
+ if (url.pathname === '/api/mkdir' && request.method === 'POST') {
711
+ self.ensureSqliteFs();
712
+ try {
713
+ const body = await request.json();
714
+ const path = String(body.path).replace(/^\/+/, '');
715
+ self.sqliteFs.mkdir(path, { recursive: true });
716
+ return Response.json({ ok: true, path });
717
+ }
718
+ catch (e) {
719
+ return Response.json({ error: e?.message }, { status: 400 });
720
+ }
721
+ }
722
+ // ── Start vite via HTTP API (survives WS disconnects) ──
723
+ if (url.pathname === '/api/start-vite' && request.method === 'POST') {
724
+ self.ensureSqliteFs();
725
+ try {
726
+ const body = await request.json();
727
+ const root = String(body.root || 'home/user').replace(/^\/+/, '');
728
+ // Stop existing server
729
+ if (self.viteDevServer?.isRunning)
730
+ self.viteDevServer.stop();
731
+ // Start in-process ViteDevServer
732
+ if (!self.esbuildService)
733
+ self.esbuildService = new EsbuildService(self.sqliteFs);
734
+ const basePath = self.viteBasePath;
735
+ // Primitives wave (P5/P8): allocate a PID + port even on the
736
+ // /api/start-vite path so probes that drive vite via the test
737
+ // surface still see a real process in `ps` and stream
738
+ // diagnostics into the Process tab.
739
+ const apiVitePort = (typeof body.port === 'number' && body.port > 0) ? body.port : 5173;
740
+ const apiViteEntry = self.processTable.spawn('vite (api/start-vite, ' + root + ')', [], root);
741
+ self.processTable.setLongRunning(apiViteEntry.pid);
742
+ self.viteDevServer = new ViteDevServer({
743
+ vfs: self.sqliteFs, esbuild: self.esbuildService, root,
744
+ aliases: body.aliases, define: body.define,
745
+ onHmrMessage: () => { },
746
+ sql: self.ctx.storage.sql,
747
+ injectBasename: body.injectBasename,
748
+ basePath,
749
+ // env+ctx enable the on-demand facet bundle path. Without
750
+ // these, ViteDevServer falls back to in-supervisor esbuild
751
+ // for /preview/@modules/<spec> cold-path bundles — which OOMs
752
+ // on large packages (lucide-react). See vite-dev-server.ts:
753
+ // ensureOnDemandPool / serveModule.
754
+ env: self.env,
755
+ ctx: self.ctx,
756
+ port: apiVitePort,
757
+ pid: apiViteEntry.pid,
758
+ processLogs: self.processLogs,
759
+ });
760
+ self.viteDevServer.start();
761
+ try {
762
+ const apiViteStub = makeLongRunningPortStub(self.viteDevServer);
763
+ self.portRegistry.register(apiVitePort, apiViteEntry.pid, apiViteStub);
764
+ self._viteShimPid = apiViteEntry.pid;
765
+ self._viteShimPort = apiVitePort;
766
+ }
767
+ catch { }
768
+ // Persist so vite survives DO hibernation. basePath included so the
769
+ // rehydrated server after DO sleep emits URLs under the same prefix
770
+ // even before the next forwarded request updates sessionBasePath.
771
+ await self.ctx.storage.put('vite-config', { root, aliases: body.aliases, define: body.define, injectBasename: body.injectBasename, basePath });
772
+ return Response.json({ ok: true, root, running: true });
773
+ }
774
+ catch (e) {
775
+ return Response.json({ error: e?.message }, { status: 400 });
776
+ }
777
+ }
778
+ // [cleanup] /api/supervisor-rpc removed. The fetch-based fallback
779
+ // pre-dated the ctx.exports-driven SupervisorRPC class; once D'.1
780
+ // landed (cirrus-real on DO Facet) every facet ↔ supervisor RPC
781
+ // flows through service bindings. The HTTP fallback was quarantined
782
+ // under ARC-A-P3 with on-entry warnings; no caller ever hit it
783
+ // post-rebuild. Removed in this cleanup along with the
784
+ // handleSupervisorRpc body in src/session/supervisor-rpc.ts.
785
+ // CORS preflight for API endpoints (audit C3).
786
+ // Respond 204 with NO Access-Control-Allow-Origin. The browser
787
+ // treats a missing ACAO as "cross-origin denied," which is what
788
+ // we want for every endpoint in this DO: same-origin requests
789
+ // from the session shell skip preflight entirely (SOP governs
790
+ // them, not CORS); cross-origin callers are rejected.
791
+ //
792
+ // This handler matches only /api/* — the /preview, /worker, and
793
+ // /port proxies retain their own header handling (some of those
794
+ // still set wildcard ACAO in sibling modules like vite-dev-server
795
+ // and nimbus-wrangler; tightening those is tracked as follow-up
796
+ // since they serve user-controlled content and require separate
797
+ // review of each consumer).
798
+ if (request.method === 'OPTIONS' && url.pathname.startsWith('/api/')) {
799
+ return new Response(null, { status: 204 });
800
+ }
801
+ // ── Preview route: serves the Vite dev server output ──
802
+ // Uses in-process ViteDevServer (synchronous VFS access + esbuild transforms).
803
+ // This is reliable and avoids facet lifecycle issues.
804
+ if (url.pathname.startsWith('/preview/') || url.pathname === '/preview') {
805
+ // Ensure the starter project exists even if the user hits /preview/
806
+ // before opening a terminal session. Idempotent — no-op if already seeded.
807
+ try {
808
+ self.ensureSqliteFs();
809
+ self.seedFilesystem();
810
+ }
811
+ catch { /* non-fatal */ }
812
+ // Primitive #3 multi-target routing — `/preview/?port=N` lets a
813
+ // session with multiple long-running things (vite + Express, two
814
+ // vites on different ports, …) reach each one without changing
815
+ // the user-facing URL shape.
816
+ //
817
+ // Behaviour:
818
+ // /preview/ → first port in PortRegistry by registration
819
+ // time, OR if a non-port-registered cirrus
820
+ // shim / cirrusReal is live (legacy path),
821
+ // use it directly. The legacy path is the
822
+ // fast path because it avoids stub-rebuild.
823
+ // /preview/?port=N → routeRequest(N, …) regardless of legacy.
824
+ const queryPort = (() => {
825
+ const raw = url.searchParams.get('port');
826
+ if (!raw)
827
+ return null;
828
+ const n = parseInt(raw, 10);
829
+ return Number.isFinite(n) && n > 0 && n < 65536 ? n : null;
830
+ })();
831
+ if (queryPort != null) {
832
+ const previewInner = (url.pathname.replace(/^\/preview/, '') || '/') + (() => {
833
+ // Strip our `?port=N` so the inner handler doesn't re-see it.
834
+ const sp = new URLSearchParams(url.search);
835
+ sp.delete('port');
836
+ const q = sp.toString();
837
+ return q ? '?' + q : '';
838
+ })();
839
+ const proxied = await self.portRegistry.routeRequest(queryPort, request, previewInner);
840
+ if (proxied)
841
+ return proxied;
842
+ return new Response(`No process listening on port ${queryPort}`, { status: 502 });
843
+ }
844
+ // ── Real-vite takes precedence if running ───────────────────────
845
+ // Cirrus shim and real-vite are mutually exclusive per session.
846
+ // cirrusReal is checked first since users explicitly opted in via
847
+ // NIMBUS_REAL_VITE=1 or `nimbusDevServer: 'real'`.
848
+ if (self.cirrusReal?.isRunning) {
849
+ const previewPath = (url.pathname.replace(/^\/preview/, '') || '/') + url.search;
850
+ // Phase 2: HMR WebSocket upgrade. Vite's @vite/client opens a
851
+ // WS against `<base>/__nimbus_hmr` (our custom HMR path). We
852
+ // accept it here and plug it into cirrusReal.hmr so the facet's
853
+ // ws-shim sees a 'connection' event on its next long-poll.
854
+ // Non-hibernatable (server.accept, not ctx.acceptWebSocket) —
855
+ // clients auto-reconnect on DO wake.
856
+ if (previewPath.startsWith('/__nimbus_hmr') || previewPath === '/__nimbus_hmr') {
857
+ if (request.headers.get('Upgrade') !== 'websocket') {
858
+ return new Response('Expected WebSocket', { status: 426 });
859
+ }
860
+ const pair = new WebSocketPair();
861
+ const [client, server] = Object.values(pair);
862
+ // Use ctx.acceptWebSocket (hibernatable) — required because
863
+ // HMR sends messages from a DIFFERENT request context (the
864
+ // facet's long-poll RPC). workerd forbids cross-request I/O
865
+ // on server.accept()'d sockets.
866
+ self.ctx.acceptWebSocket(server, ['cirrus-hmr']);
867
+ const clientId = self.cirrusReal.attachHmrClient(server);
868
+ server.serializeAttachment?.({ kind: 'cirrus-hmr', clientId });
869
+ const hmrClients = (self._cirrusHmrWsClients ||= new Map());
870
+ hmrClients.set(server, clientId);
871
+ // Echo the vite-hmr subprotocol.
872
+ const wantedProto = request.headers.get('Sec-WebSocket-Protocol') || '';
873
+ const useProto = wantedProto.split(',').map(s => s.trim()).find(p => p === 'vite-hmr' || p === 'vite-ping');
874
+ const respHeaders = {};
875
+ if (useProto)
876
+ respHeaders['Sec-WebSocket-Protocol'] = useProto;
877
+ return new Response(null, { status: 101, webSocket: client, headers: respHeaders });
878
+ }
879
+ return self.cirrusReal.handleRequest(request, previewPath);
880
+ }
881
+ // Lazy-init: if DO hibernated and ViteDevServer was GC'd, reconstruct from saved config
882
+ if (!self.viteDevServer || !self.viteDevServer.isRunning) {
883
+ try {
884
+ const config = await self.ctx.storage.get('vite-config');
885
+ if (config?.root) {
886
+ self.ensureSqliteFs();
887
+ if (!self.esbuildService)
888
+ self.esbuildService = new EsbuildService(self.sqliteFs);
889
+ // Prefer the current request's basePath (just captured from the
890
+ // X-Nimbus-Base header) over the stored one — the latter is only
891
+ // a fallback for cold rehydrates that precede any header hit.
892
+ const basePath = self.viteBasePath || config.basePath;
893
+ // Primitives wave (P8): on hibernation rehydrate, re-allocate
894
+ // a PID so log streaming has somewhere to land. Without
895
+ // this, the rehydrated server would be silent again. The
896
+ // PID is registered against the previously-known port (or
897
+ // the default 5173 when the saved config predates P5).
898
+ const rehydratedPort = (config.port && Number.isFinite(config.port)) ? config.port : 5173;
899
+ const rehydratedEntry = self.processTable.spawn('vite (rehydrated, ' + config.root + ')', [], config.root);
900
+ self.processTable.setLongRunning(rehydratedEntry.pid);
901
+ self.viteDevServer = new ViteDevServer({
902
+ vfs: self.sqliteFs, esbuild: self.esbuildService, root: config.root,
903
+ aliases: config.aliases, define: config.define,
904
+ onHmrMessage: () => { },
905
+ sql: self.ctx.storage.sql,
906
+ injectBasename: config.injectBasename,
907
+ basePath,
908
+ env: self.env,
909
+ ctx: self.ctx,
910
+ port: rehydratedPort,
911
+ pid: rehydratedEntry.pid,
912
+ processLogs: self.processLogs,
913
+ });
914
+ self.viteDevServer.start();
915
+ // Re-register the port so /preview/?port=N keeps working
916
+ // across hibernation cycles.
917
+ try {
918
+ const rehydratedStub = makeLongRunningPortStub(self.viteDevServer);
919
+ self.portRegistry.register(rehydratedPort, rehydratedEntry.pid, rehydratedStub);
920
+ self._viteShimPid = rehydratedEntry.pid;
921
+ self._viteShimPort = rehydratedPort;
922
+ }
923
+ catch { /* registry full / unavailable — fall through */ }
924
+ }
925
+ }
926
+ catch { /* lazy-init failed, fall through to "no server" response */ }
927
+ }
928
+ if (self.viteDevServer?.isRunning) {
929
+ const previewPath = (url.pathname.replace(/^\/preview/, '') || '/') + url.search;
930
+ return self.viteDevServer.handleRequest(request, previewPath);
931
+ }
932
+ // Polished placeholder — auto-reloads when vite starts.
933
+ // Checks the VFS for the starter app so we can offer a context-aware hint.
934
+ const hasSeed = (() => {
935
+ try {
936
+ return self.sqliteFs.exists('home/user/app') &&
937
+ self.sqliteFs.exists('home/user/app/package.json');
938
+ }
939
+ catch {
940
+ return false;
941
+ }
942
+ })();
943
+ const hint = hasSeed
944
+ ? 'cd app &amp;&amp; npm install &amp;&amp; npm run dev'
945
+ : 'vite';
946
+ // The placeholder JS polls the session's /api/stats. If this DO was
947
+ // reached directly (no session prefix), fall back to a relative path.
948
+ const statsUrl = (self.sessionBasePath || '') + '/api/stats';
949
+ return new Response(renderNoDevServerHtml({ hint, polled: statsUrl, liveKey: 'vite' }),
950
+ // Audit C3: HTML served same-origin to the session shell.
951
+ // No wildcard ACAO — the page's own fetch to /api/stats is
952
+ // same-origin and needs no CORS header.
953
+ { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' } });
954
+ }
955
+ // ── Worker route: serves the nimbus-wrangler dev worker output ──
956
+ //
957
+ // Primitives wave (P5): the canonical path is `/__nimbus/worker/*`
958
+ // so projects with their own `worker/` directory at root (Markflow,
959
+ // CF Pages projects, …) can serve it via `/preview/worker/*` without
960
+ // collision. The bare `/worker/*` path remains accepted for
961
+ // back-compat — same handler — but the response carries a
962
+ // `Deprecation` and `Sunset` header so callers can migrate. New
963
+ // sessions are encouraged to use the namespaced form.
964
+ const workerPathMatch = url.pathname.startsWith('/__nimbus/worker/') || url.pathname === '/__nimbus/worker' ||
965
+ url.pathname.startsWith('/worker/') || url.pathname === '/worker';
966
+ const isLegacyWorkerPath = url.pathname.startsWith('/worker/') || url.pathname === '/worker';
967
+ if (workerPathMatch) {
968
+ if (!self.nimbusWrangler?.isRunning) {
969
+ // Mirror the polished /preview/ placeholder — auto-reloads when
970
+ // nimbus-wrangler starts. The placeholder references BOTH command
971
+ // names so users coming from either `wrangler dev` or
972
+ // `nimbus-wrangler dev` see a familiar hint.
973
+ const hasWranglerConfig = (() => {
974
+ try {
975
+ self.ensureSqliteFs();
976
+ return self.sqliteFs.exists('home/user/wrangler.jsonc') ||
977
+ self.sqliteFs.exists('home/user/wrangler.json') ||
978
+ self.sqliteFs.exists('home/user/wrangler.toml');
979
+ }
980
+ catch {
981
+ return false;
982
+ }
983
+ })();
984
+ const hint = hasWranglerConfig
985
+ ? 'npm run dev'
986
+ : 'wrangler dev';
987
+ return new Response(renderNoDevServerHtml({ hint, polled: (self.sessionBasePath || '') + '/api/stats', liveKey: 'wrangler' }),
988
+ // Audit C3: same-origin HTML, no ACAO needed (see /preview/).
989
+ { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' } });
990
+ }
991
+ // Strip the matched prefix to compute the inner worker path.
992
+ // `/__nimbus/worker/*` and `/worker/*` collapse to the same
993
+ // inner path so the dispatcher logic doesn't need to branch.
994
+ const innerPrefix = isLegacyWorkerPath ? '/worker' : '/__nimbus/worker';
995
+ const workerPath = url.pathname.replace(new RegExp('^' + innerPrefix), '') || '/';
996
+ // Full outer-facing prefix for the proxy (e.g.
997
+ // "/s/nimble-otter-4271/__nimbus/worker"). Used to rewrite
998
+ // Location headers emitted by the inner Worker so cross-redirects
999
+ // (POST /new → /s/<inner>/) land back on the correctly-prefixed
1000
+ // outer URL rather than a bare /s/<inner>/ path that would spawn
1001
+ // a different outer session.
1002
+ const outerWorkerBase = (self.sessionBasePath || '') + innerPrefix;
1003
+ const resp = await self.nimbusWrangler.handleRequest(request, workerPath, outerWorkerBase);
1004
+ if (isLegacyWorkerPath) {
1005
+ // Surface the deprecation in headers without rewriting body —
1006
+ // unobtrusive for browsers, visible to tooling.
1007
+ const newHeaders = new Headers(resp.headers);
1008
+ newHeaders.set('Deprecation', 'true');
1009
+ newHeaders.set('Sunset', 'Wed, 01 Jan 2027 00:00:00 GMT');
1010
+ newHeaders.set('Link', '<' + (self.sessionBasePath || '') + '/__nimbus/worker' + workerPath +
1011
+ '>; rel="successor-version"');
1012
+ return new Response(resp.body, {
1013
+ status: resp.status, statusText: resp.statusText, headers: newHeaders,
1014
+ });
1015
+ }
1016
+ return resp;
1017
+ }
1018
+ // ── Port route: routes to facet HTTP servers ──
1019
+ // Audit F3 (STABILITY-AUDIT.md C-S3): routeRequest now always
1020
+ // returns a Response — 501 when no facet has a stub registered
1021
+ // (the normal case today, since the facet-side producer was
1022
+ // never wired), or a real proxied response once wiring lands.
1023
+ // The post-routeRequest null-branch + 502 fallback below is kept
1024
+ // defensively for any future refactor that returns null for a
1025
+ // different error condition.
1026
+ const portMatch = url.pathname.match(/^\/port\/(\d+)(\/.*)?$/);
1027
+ if (portMatch) {
1028
+ const port = parseInt(portMatch[1]);
1029
+ const path = portMatch[2] || '/';
1030
+ const result = await self.portRegistry.routeRequest(port, request, path);
1031
+ if (result)
1032
+ return result;
1033
+ return new Response(`No process listening on port ${port}`, {
1034
+ status: 502,
1035
+ });
1036
+ }
1037
+ // ── /api/_diag/cache — per-tier cache observability ───────────────
1038
+ //
1039
+ // Cache-observability wave: surfaces the L1/L2/L3/L4 × tarball/
1040
+ // packument/asset hit/miss/bytes grid maintained by
1041
+ // src/_shared/cache-stats.ts. Pre-wave the only observability was
1042
+ // /api/_diag/memory.r2.* (RPC-call level), which conflated L2 and
1043
+ // L3 and missed L1 entirely.
1044
+ //
1045
+ // Added at the END of handleFetch so future wave additions to the
1046
+ // diag cluster (typically inserted near other /api/_diag/* routes)
1047
+ // don't cause merge conflicts in the cache-observability commit.
1048
+ // POST /reset zeros the counters in-place for fresh measurement
1049
+ // windows; the dual-endpoint pattern mirrors /api/_test/* reset
1050
+ // helpers used by the recovery-event probe.
1051
+ if (url.pathname === '/api/_diag/cache' && request.method === 'GET') {
1052
+ const { snapshot } = await import('../_shared/cache-stats.js');
1053
+ const snap = snapshot();
1054
+ return Response.json(snap);
1055
+ }
1056
+ if (url.pathname === '/api/_diag/cache/reset' && request.method === 'POST') {
1057
+ const { reset } = await import('../_shared/cache-stats.js');
1058
+ reset();
1059
+ return new Response(null, { status: 204 });
1060
+ }
1061
+ return new Response('Not found', { status: 404 });
1062
+ }
1063
+ // ── cache-and-scrub L2 benchmark endpoint ───────────────────────────────
1064
+ //
1065
+ // Routes under /api/_test/cache/* exercise the L2 (`caches.default`)
1066
+ // layer that wraps R2 packument/tarball reads + the env.ASSETS
1067
+ // esbuild-wasm fetch. Probes assert the L2 hit path is ≥5× faster
1068
+ // than the cold path (the wave's hard ship-gate).
1069
+ //
1070
+ // Endpoint surface (all NIMBUS_DEBUG-gated by the parent router):
1071
+ //
1072
+ // POST /api/_test/cache/packument/seed {name, payload}
1073
+ // → write the packument to R2 (so cold reads have something to
1074
+ // serve) AND purge any stale L2 entry (so the first bench
1075
+ // read is guaranteed L3-only).
1076
+ // GET /api/_test/cache/packument/bench?name=X&n=N
1077
+ // → run N sequential getPackument(X) calls, return latencies[].
1078
+ // POST /api/_test/cache/tarball/seed {name, version, sizeKb}
1079
+ // → similar; payload is a synthetic Uint8Array of sizeKb*1024.
1080
+ // GET /api/_test/cache/tarball/bench?name=X&version=Y&n=N
1081
+ // → similar.
1082
+ // GET /api/_test/cache/wasm/bench?n=N
1083
+ // → run N sequential fetchEsbuildWasmBytes() calls. The first
1084
+ // is asset-fetch + L2 write; subsequent should hit L2.
1085
+ async function handleCacheTestEndpoint(self, url, request) {
1086
+ const env = self.env;
1087
+ const path = url.pathname;
1088
+ // Build a fresh R2CacheClient bound to the request's env (mirrors
1089
+ // SupervisorRPC._r2 in semantics — graceful-degrade on missing
1090
+ // bindings).
1091
+ const r2 = new R2CacheClient(env?.NPM_TARBALL_CACHE ?? null, env?.NPM_PACKUMENT_CACHE ?? null);
1092
+ const caches = globalThis.caches;
1093
+ const purgeL2 = async (synthUrl) => {
1094
+ try {
1095
+ await caches?.default?.delete(new Request(synthUrl));
1096
+ }
1097
+ catch { }
1098
+ };
1099
+ if (path === '/api/_test/cache/packument/seed' && request.method === 'POST') {
1100
+ const body = await request.json();
1101
+ const name = String(body.name || '');
1102
+ const payload = String(body.payload || JSON.stringify({ name, versions: {} }));
1103
+ if (!name)
1104
+ return Response.json({ error: 'missing name' }, { status: 400 });
1105
+ // Purge L2 first so the next bench read starts from L3 cold.
1106
+ await purgeL2(`${L2_KEY_HOST}/${R2_CACHE_PREFIX}/p/${encodeURIComponent(name)}.json`);
1107
+ const ok = await r2.putPackument(name, payload);
1108
+ return Response.json({ seeded: ok, name, payloadBytes: payload.length });
1109
+ }
1110
+ if (path === '/api/_test/cache/packument/bench' && request.method === 'GET') {
1111
+ const name = url.searchParams.get('name') ?? '';
1112
+ const n = Math.max(1, Math.min(20, parseInt(url.searchParams.get('n') || '5', 10)));
1113
+ if (!name)
1114
+ return Response.json({ error: 'missing name' }, { status: 400 });
1115
+ const latencies = [];
1116
+ let lastBytes = 0;
1117
+ let nullCount = 0;
1118
+ for (let i = 0; i < n; i++) {
1119
+ const t0 = performance.now();
1120
+ const got = await r2.getPackument(name);
1121
+ const t1 = performance.now();
1122
+ latencies.push(t1 - t0);
1123
+ if (!got)
1124
+ nullCount++;
1125
+ else
1126
+ lastBytes = got.json.length;
1127
+ }
1128
+ // R2CacheClient instance was constructed at the top of this
1129
+ // handler; counters reflect the N calls just made.
1130
+ const stats = r2.stats();
1131
+ return Response.json({ name, n, latencies, lastBytes, nullCount, stats });
1132
+ }
1133
+ if (path === '/api/_test/cache/tarball/seed' && request.method === 'POST') {
1134
+ const body = await request.json();
1135
+ const name = String(body.name || '');
1136
+ const version = String(body.version || '');
1137
+ const sizeKb = Math.max(1, Math.min(15360, Number(body.sizeKb) || 16)); // up to 15 MiB (under MAX_R2_TARBALL_BYTES = 30 MiB)
1138
+ if (!name || !version)
1139
+ return Response.json({ error: 'missing name/version' }, { status: 400 });
1140
+ await purgeL2(`${L2_KEY_HOST}/${R2_CACHE_PREFIX}/t/${encodeURIComponent(name)}/${encodeURIComponent(version)}.tgz`);
1141
+ // Synthetic payload — bytes are arbitrary; the cache layer doesn't
1142
+ // care about content. Probe just measures fetch latency.
1143
+ const bytes = new Uint8Array(sizeKb * 1024);
1144
+ for (let i = 0; i < bytes.length; i++)
1145
+ bytes[i] = i & 0xff;
1146
+ const ok = await r2.putTarball(name, version, bytes);
1147
+ return Response.json({ seeded: ok, name, version, sizeBytes: bytes.length });
1148
+ }
1149
+ if (path === '/api/_test/cache/tarball/bench' && request.method === 'GET') {
1150
+ const name = url.searchParams.get('name') ?? '';
1151
+ const version = url.searchParams.get('version') ?? '';
1152
+ const n = Math.max(1, Math.min(20, parseInt(url.searchParams.get('n') || '5', 10)));
1153
+ if (!name || !version)
1154
+ return Response.json({ error: 'missing name/version' }, { status: 400 });
1155
+ const latencies = [];
1156
+ let lastBytes = 0;
1157
+ let nullCount = 0;
1158
+ for (let i = 0; i < n; i++) {
1159
+ const t0 = performance.now();
1160
+ const got = await r2.getTarball(name, version);
1161
+ const t1 = performance.now();
1162
+ latencies.push(t1 - t0);
1163
+ if (!got)
1164
+ nullCount++;
1165
+ else
1166
+ lastBytes = got.length;
1167
+ }
1168
+ const stats = r2.stats();
1169
+ return Response.json({ name, version, n, latencies, lastBytes, nullCount, stats });
1170
+ }
1171
+ if (path === '/api/_test/cache/wasm/reset' && request.method === 'POST') {
1172
+ // Purge the L2 entry so the next bench call goes cold (re-runs
1173
+ // env.ASSETS.fetch + L2 write-back). The L2 key is exported
1174
+ // from esbuild-wasm-bytes.ts so the test endpoint stays in
1175
+ // lockstep with the runtime module's key shape across any
1176
+ // future ESBUILD_VERSION bump.
1177
+ await purgeL2(ESBUILD_WASM_L2_KEY);
1178
+ return Response.json({ purged: true });
1179
+ }
1180
+ if (path === '/api/_test/cache/wasm/bench' && request.method === 'GET') {
1181
+ const n = Math.max(1, Math.min(10, parseInt(url.searchParams.get('n') || '3', 10)));
1182
+ const latencies = [];
1183
+ let lastBytes = 0;
1184
+ for (let i = 0; i < n; i++) {
1185
+ const t0 = performance.now();
1186
+ const ab = await fetchEsbuildWasmBytes(env);
1187
+ const t1 = performance.now();
1188
+ latencies.push(t1 - t0);
1189
+ lastBytes = ab.byteLength;
1190
+ }
1191
+ return Response.json({ n, latencies, lastBytes });
1192
+ }
1193
+ return new Response('unknown cache _test endpoint', { status: 404 });
1194
+ }
1195
+ // ── two-tier-fanout primitive benchmark endpoint ────────────────────────
1196
+ //
1197
+ // Routes under /api/_test/fanout/* exercise NimbusFanoutPool's two
1198
+ // topologies (POC C in-DO + POC B peer-DO) via a synthetic workload
1199
+ // that's independent of the production install-batch / pre-bundle
1200
+ // sites. The probe measures speedup, peer-DO routing determinism,
1201
+ // and backpressure behavior in isolation.
1202
+ //
1203
+ // Endpoint surface (all NIMBUS_DEBUG-gated by the parent router):
1204
+ //
1205
+ // GET /api/_test/fanout/topology?n=N
1206
+ // → returns which topology N would route to (no dispatch).
1207
+ // GET /api/_test/fanout/route?n=N&keys=k1,k2,...
1208
+ // → returns the deterministic peer-DO sibling-id per key.
1209
+ // POST /api/_test/fanout/bench {n, sleepMs}
1210
+ // → runs N synthetic tasks, each sleeping `sleepMs` inside
1211
+ // its loader isolate. Returns total wall time + per-peer
1212
+ // ledger of how many tasks each peer DO handled.
1213
+ // POST /api/_test/fanout/serial-bench {n, sleepMs}
1214
+ // → runs N synthetic tasks SERIALLY (concurrency=1) inside
1215
+ // ONE loader isolate. Used to compute T_serial for the
1216
+ // 5× speedup assertion.
1217
+ //
1218
+ // The synthetic worker function intentionally doesn't import npm
1219
+ // packages or do real I/O — it just sleeps, so the parallelism
1220
+ // floor is the loader/RPC overhead, not network jitter.
1221
+ async function handleFanoutTestEndpoint(self, url, request) {
1222
+ const env = self.env;
1223
+ const path = url.pathname;
1224
+ if (path === '/api/_test/fanout/topology' && request.method === 'GET') {
1225
+ const n = Math.max(0, parseInt(url.searchParams.get('n') || '0', 10));
1226
+ return Response.json({
1227
+ n,
1228
+ topology: n === 0 ? 'empty' : (n < IN_DO_THRESHOLD ? 'in-do' : 'peer-do'),
1229
+ inDoThreshold: IN_DO_THRESHOLD,
1230
+ maxPeerFanout: MAX_PEER_FANOUT,
1231
+ });
1232
+ }
1233
+ if (path === '/api/_test/fanout/route' && request.method === 'GET') {
1234
+ const keysRaw = url.searchParams.get('keys') || '';
1235
+ const keys = keysRaw.split(',').map((k) => k.trim()).filter(Boolean);
1236
+ const peerCount = Math.max(1, Math.min(parseInt(url.searchParams.get('n') || String(keys.length), 10), MAX_PEER_FANOUT));
1237
+ const placement = keys.map((k) => ({
1238
+ key: k,
1239
+ shard: hashKeyToShard(k, peerCount),
1240
+ }));
1241
+ return Response.json({ peerCount, placement });
1242
+ }
1243
+ if (path === '/api/_test/fanout/bench' && request.method === 'POST') {
1244
+ const body = await request.json();
1245
+ const n = Math.max(1, Math.min(64, Number(body.n) || 8));
1246
+ const sleepMs = Math.max(0, Math.min(2000, Number(body.sleepMs) || 100));
1247
+ const pool = new NimbusFanoutPool(env, self.ctx, {
1248
+ tag: 'fanout-bench',
1249
+ timeoutMs: 60_000,
1250
+ });
1251
+ const tasks = Array.from({ length: n }, (_, i) => ({
1252
+ key: `task-${i}`,
1253
+ args: { id: i, sleepMs },
1254
+ }));
1255
+ const t0 = performance.now();
1256
+ // The function runs INSIDE each loader isolate; we use Date.now()
1257
+ // (millisecond resolution is fine; we're sleeping for ms-scale)
1258
+ // to record per-task start/end so the supervisor can compute the
1259
+ // distribution after the fact.
1260
+ const results = await pool.submitMany(tasks, async (item, env) => {
1261
+ const startMs = Date.now();
1262
+ // Identify which env we're running in. SUPERVISOR is the
1263
+ // RPC stub auto-injected by NimbusLoaderPool; its presence
1264
+ // tells us we're inside a loader isolate (not the supervisor).
1265
+ const loaderEnvKeys = Object.keys(env || {}).sort();
1266
+ // Sleep entirely inside the isolate — no external network.
1267
+ await new Promise((r) => setTimeout(r, item.sleepMs));
1268
+ const endMs = Date.now();
1269
+ return { id: item.id, startMs, endMs, loaderEnvKeys };
1270
+ });
1271
+ const t1 = performance.now();
1272
+ // Aggregate per-peer ledger from the response shape. Each task's
1273
+ // result includes its loaderEnvKeys; the SUPERVISOR binding's
1274
+ // doId is observable to confirm peer routing (if peer-DO topology
1275
+ // is in use, each task's SUPERVISOR.doId differs from the
1276
+ // coordinator's). We don't expose doId here directly — we rely
1277
+ // on hashKeyToShard predicting placement and inspecting the
1278
+ // overlap in start/end timestamps to infer parallelism.
1279
+ const startTimes = results.map((r) => r.startMs);
1280
+ const endTimes = results.map((r) => r.endMs);
1281
+ const minStart = Math.min(...startTimes);
1282
+ const maxEnd = Math.max(...endTimes);
1283
+ const totalDurations = results.map((r) => r.endMs - r.startMs);
1284
+ return Response.json({
1285
+ n,
1286
+ sleepMs,
1287
+ wallTimeMs: t1 - t0,
1288
+ results,
1289
+ analysis: {
1290
+ minStart,
1291
+ maxEnd,
1292
+ spanMs: maxEnd - minStart,
1293
+ sumDurations: totalDurations.reduce((a, b) => a + b, 0),
1294
+ topology: n < IN_DO_THRESHOLD ? 'in-do' : 'peer-do',
1295
+ },
1296
+ });
1297
+ }
1298
+ if (path === '/api/_test/fanout/serial-bench' && request.method === 'POST') {
1299
+ const body = await request.json();
1300
+ const n = Math.max(1, Math.min(64, Number(body.n) || 8));
1301
+ const sleepMs = Math.max(0, Math.min(2000, Number(body.sleepMs) || 100));
1302
+ // Same workload, but FORCE serial dispatch by using a single
1303
+ // NimbusLoaderPool with concurrency=1 and submitting one task
1304
+ // at a time. This is the T_serial reference for the 5× speedup
1305
+ // assertion.
1306
+ const { NimbusLoaderPool } = await import('../loaders/loader-pool.js');
1307
+ const pool = new NimbusLoaderPool(env, self.ctx, {
1308
+ concurrency: 1,
1309
+ timeoutMs: 60_000,
1310
+ tag: 'fanout-serial',
1311
+ });
1312
+ const t0 = performance.now();
1313
+ try {
1314
+ for (let i = 0; i < n; i++) {
1315
+ await pool.submit(async (item) => {
1316
+ await new Promise((r) => setTimeout(r, item.sleepMs));
1317
+ return item.id;
1318
+ }, { id: i, sleepMs });
1319
+ }
1320
+ }
1321
+ finally {
1322
+ try {
1323
+ pool.dispose();
1324
+ }
1325
+ catch { }
1326
+ }
1327
+ const t1 = performance.now();
1328
+ return Response.json({ n, sleepMs, wallTimeMs: t1 - t0 });
1329
+ }
1330
+ return new Response('unknown fanout _test endpoint', { status: 404 });
1331
+ }