@nimbus-sh/worker 0.2.2 → 0.3.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 (131) hide show
  1. package/README.md +5 -2
  2. package/dist/_shared/session-router.d.ts +8 -0
  3. package/dist/_shared/session-router.d.ts.map +1 -1
  4. package/dist/_shared/session-router.js +8 -0
  5. package/dist/facets/cirrus-real.js +1 -1
  6. package/dist/facets/manager.d.ts +42 -104
  7. package/dist/facets/manager.d.ts.map +1 -1
  8. package/dist/facets/manager.js +126 -283
  9. package/dist/facets/opencode-staging.d.ts +1 -1
  10. package/dist/facets/opencode-staging.d.ts.map +1 -1
  11. package/dist/facets/opencode-staging.js +4 -0
  12. package/dist/facets/vite-dev-server.d.ts +2 -2
  13. package/dist/facets/vite-dev-server.js +4 -4
  14. package/dist/git/commands.d.ts +8 -0
  15. package/dist/git/commands.d.ts.map +1 -1
  16. package/dist/git/commands.js +41 -9
  17. package/dist/git/network-facet.d.ts +1 -1
  18. package/dist/git/network-facet.d.ts.map +1 -1
  19. package/dist/git/network-facet.js +35 -12
  20. package/dist/git-bundle.generated.d.ts +2 -2
  21. package/dist/git-bundle.generated.d.ts.map +1 -1
  22. package/dist/git-bundle.generated.js +3 -3
  23. package/dist/index.js +1 -1
  24. package/dist/loaders/child-process/spawn-facet.js +1 -1
  25. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
  26. package/dist/loaders/child-process/spawn-pool.js +2 -2
  27. package/dist/loaders/generated-workers.d.ts +1 -1
  28. package/dist/loaders/generated-workers.d.ts.map +1 -1
  29. package/dist/loaders/generated-workers.js +2 -2
  30. package/dist/loaders/npm-resolve-preamble.d.ts +3 -3
  31. package/dist/loaders/npm-resolve-preamble.js +3 -3
  32. package/dist/loaders/pre-bundle-preamble.d.ts +5 -5
  33. package/dist/loaders/pre-bundle-preamble.js +6 -6
  34. package/dist/loaders/process-host.d.ts +6 -107
  35. package/dist/loaders/process-host.d.ts.map +1 -1
  36. package/dist/loaders/process-host.js +6 -405
  37. package/dist/npm/install-batch-facet.js +1 -1
  38. package/dist/npm/installer.d.ts +4 -4
  39. package/dist/npm/installer.js +18 -18
  40. package/dist/npm/pre-bundle-facet.d.ts +2 -2
  41. package/dist/npm/pre-bundle-facet.js +5 -5
  42. package/dist/npm/resolve-facet.d.ts +1 -1
  43. package/dist/npm/resolve-facet.js +1 -1
  44. package/dist/npm/resolve-one-facet.d.ts +5 -5
  45. package/dist/npm/resolve-one-facet.js +5 -5
  46. package/dist/router/index.js +1 -1
  47. package/dist/router/remote-api.js +8 -0
  48. package/dist/runtime/bun-repl.d.ts +1 -1
  49. package/dist/runtime/bun-repl.js +3 -3
  50. package/dist/runtime/facet-loader-host.d.ts +5 -8
  51. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  52. package/dist/runtime/facet-loader-host.js +7 -11
  53. package/dist/runtime/node-repl.js +2 -2
  54. package/dist/runtime/python-repl.js +3 -3
  55. package/dist/runtime/ruby-repl.js +2 -2
  56. package/dist/runtime/ruby-resident.js +1 -1
  57. package/dist/session/hibernation.d.ts +18 -51
  58. package/dist/session/hibernation.d.ts.map +1 -1
  59. package/dist/session/hibernation.js +49 -186
  60. package/dist/session/init-phases.d.ts +2 -2
  61. package/dist/session/init-phases.d.ts.map +1 -1
  62. package/dist/session/init-phases.js +1 -1
  63. package/dist/session/init.d.ts.map +1 -1
  64. package/dist/session/init.js +16 -2
  65. package/dist/session/keys.d.ts +14 -35
  66. package/dist/session/keys.d.ts.map +1 -1
  67. package/dist/session/keys.js +17 -35
  68. package/dist/session/nimbus-session.d.ts +10 -4
  69. package/dist/session/nimbus-session.d.ts.map +1 -1
  70. package/dist/session/nimbus-session.js +20 -11
  71. package/dist/session/port-capability.d.ts +43 -0
  72. package/dist/session/port-capability.d.ts.map +1 -0
  73. package/dist/session/port-capability.js +49 -0
  74. package/dist/session/programmatic.d.ts +41 -5
  75. package/dist/session/programmatic.d.ts.map +1 -1
  76. package/dist/session/programmatic.js +147 -15
  77. package/dist/session/routes.d.ts +2 -0
  78. package/dist/session/routes.d.ts.map +1 -1
  79. package/dist/session/routes.js +91 -16
  80. package/dist/session/rpc.d.ts +35 -8
  81. package/dist/session/rpc.d.ts.map +1 -1
  82. package/dist/session/rpc.js +73 -14
  83. package/dist/session/start-real-vite.d.ts.map +1 -1
  84. package/dist/session/start-real-vite.js +2 -0
  85. package/dist/session/state-store.d.ts +3 -2
  86. package/dist/session/state-store.d.ts.map +1 -1
  87. package/dist/session/state-store.js +3 -2
  88. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  89. package/dist/session/supervisor-rpc.js +5 -0
  90. package/dist/session/ws.d.ts +2 -2
  91. package/dist/session/ws.d.ts.map +1 -1
  92. package/dist/session/ws.js +2 -2
  93. package/dist/wrangler/nimbus-wrangler.js +1 -1
  94. package/package.json +3 -2
  95. package/scripts/bundle-runtime.mjs +5 -2
  96. package/dist/facets/inner-do-registry.d.ts +0 -41
  97. package/dist/facets/inner-do-registry.d.ts.map +0 -1
  98. package/dist/facets/inner-do-registry.js +0 -51
  99. package/dist/facets/launch-pacer.d.ts +0 -111
  100. package/dist/facets/launch-pacer.d.ts.map +0 -1
  101. package/dist/facets/launch-pacer.js +0 -130
  102. package/dist/loaders/fanout-pool.d.ts +0 -207
  103. package/dist/loaders/fanout-pool.d.ts.map +0 -1
  104. package/dist/loaders/fanout-pool.js +0 -363
  105. package/dist/loaders/loader-pool.d.ts +0 -294
  106. package/dist/loaders/loader-pool.d.ts.map +0 -1
  107. package/dist/loaders/loader-pool.js +0 -642
  108. package/dist/loaders/process-fabric.d.ts +0 -509
  109. package/dist/loaders/process-fabric.d.ts.map +0 -1
  110. package/dist/loaders/process-fabric.js +0 -360
  111. package/dist/loaders/vendor/errors.d.ts +0 -24
  112. package/dist/loaders/vendor/errors.d.ts.map +0 -1
  113. package/dist/loaders/vendor/errors.js +0 -46
  114. package/dist/loaders/vendor/serialize.d.ts +0 -3
  115. package/dist/loaders/vendor/serialize.d.ts.map +0 -1
  116. package/dist/loaders/vendor/serialize.js +0 -25
  117. package/dist/loaders/vendor/types.d.ts +0 -69
  118. package/dist/loaders/vendor/types.d.ts.map +0 -1
  119. package/dist/loaders/vendor/types.js +0 -4
  120. package/dist/loaders/workerd-facet-host.d.ts +0 -141
  121. package/dist/loaders/workerd-facet-host.d.ts.map +0 -1
  122. package/dist/loaders/workerd-facet-host.js +0 -284
  123. package/dist/session/bindings.d.ts +0 -209
  124. package/dist/session/bindings.d.ts.map +0 -1
  125. package/dist/session/bindings.js +0 -680
  126. package/dist/session/ctx-exports.d.ts +0 -16
  127. package/dist/session/ctx-exports.d.ts.map +0 -1
  128. package/dist/session/ctx-exports.js +0 -22
  129. package/dist/session/ws-hibernation-config.d.ts +0 -64
  130. package/dist/session/ws-hibernation-config.d.ts.map +0 -1
  131. package/dist/session/ws-hibernation-config.js +0 -89
@@ -22,23 +22,25 @@ import { FACET_RESIDENT_STORE_SOURCE } from '../vfs/facet-resident-store.js';
22
22
  import { VFS_CURSOR_SEED_SOURCE, serializeFacetVfsCursor, } from '@nimbus-sh/core/_shared/facet-vfs-cursor.js';
23
23
  import { VFS_WRITE_LEDGER_SOURCE } from '@nimbus-sh/core/_shared/vfs-write-ledger.js';
24
24
  import { vfsPathExtension } from '@nimbus-sh/core/vfs/path.js';
25
+ import { clearPortCapability } from '../session/port-capability.js';
25
26
  import { prefetchForRequire } from '@nimbus-sh/core/runtime/require-resolver.js';
26
27
  import { hasTopLevelModuleSyntax } from '@nimbus-sh/core/runtime/javascript-ast.js';
27
28
  import { bindImportMetaResolve, importMetaDefines } from '@nimbus-sh/core/runtime/import-meta-transform.js';
28
29
  import { recordFailure, getLastRpcFrame, getLastFacetId } from '@nimbus-sh/core/observability/oom-discriminator.js';
29
30
  import { classifyError } from '@nimbus-sh/core/observability/oom-classify.js';
30
- import { LaunchPacer, launchChunkMaxBytes } from './launch-pacer.js';
31
+ import { LaunchPacer, LaunchTurnPump, launchChunkMaxBytes } from '@nimbus-sh/fabric/launch-pacer.js';
32
+ import { ResidentLaunchJournal, } from '@nimbus-sh/fabric/launch-journal.js';
31
33
  import { EsbuildService } from '@nimbus-sh/core/runtime/esbuild-service.js';
32
34
  import { isExecDiagEnabled, recordExecTelemetry } from './exec-telemetry.js';
33
35
  import { disposeRpcResource, disposeRpcResources } from '@nimbus-sh/core/_shared/rpc-dispose.js';
34
36
  import { sqliteWasmModuleEntry } from './opencode-staging.js';
35
- import { ProcessFabric, FACET_IMAGE_DIR, facetImageDigest, facetImagePath, } from '../loaders/process-fabric.js';
36
- import { createLoadedWorkerEntrypoint, getNimbusCtxExports, } from '../loaders/workerd-facet-host.js';
37
+ import { FacetImageStore, } from '@nimbus-sh/fabric/facet-image-store.js';
38
+ import { ProcessFabric, } from '@nimbus-sh/fabric/process-fabric.js';
39
+ import { createLoadedWorkerEntrypoint, getNimbusCtxExports, } from '@nimbus-sh/fabric/workerd-facet-host.js';
37
40
  import { SQLITE_WASM_MODULE_NAME, } from '../runtime/opencode-facet-runner.js';
38
41
  import { parsePortFromArgv, resolveLongRunningPort } from '@nimbus-sh/core/runtime/long-running-handle.js';
39
42
  import { DEFAULT_FACET_BUNDLE_PROFILE, } from '@nimbus-sh/core/runtime/bundle-profile.js';
40
- import { BUNDLE_BUILD_DEADLINE_MS, CF_COMPAT_DATE, CHUNK_SIZE, FACET_TIMEOUT_MS, VFS_BUNDLE_MAX_FILES, VFS_BUNDLE_MAX_BYTES, CWD_SNAPSHOT_MAX_FILE_BYTES, BUNDLE_MAX_ENCODED_BYTES, MAX_RPC_SAFE_PAYLOAD_BYTES, MAX_TX_BLOB_BYTES, PREFETCH_CACHE_MAX_BYTES, } from '@nimbus-sh/core/constants.js';
41
- import { RESIDENT_LAUNCH_KEY_PREFIX } from '../session/keys.js';
43
+ import { BUNDLE_BUILD_DEADLINE_MS, CF_COMPAT_DATE, FACET_TIMEOUT_MS, VFS_BUNDLE_MAX_FILES, VFS_BUNDLE_MAX_BYTES, CWD_SNAPSHOT_MAX_FILE_BYTES, BUNDLE_MAX_ENCODED_BYTES, MAX_RPC_SAFE_PAYLOAD_BYTES, PREFETCH_CACHE_MAX_BYTES, } from '@nimbus-sh/core/constants.js';
42
44
  import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
43
45
  import { acquireSupervisorAllocation } from '@nimbus-sh/core/observability/heavy-alloc-coord.js';
44
46
  import { prefetchBundleStart, prefetchBundleEnd, setPrefetchCacheBytes, } from '@nimbus-sh/core/observability/diag-counters.js';
@@ -3147,19 +3149,11 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
3147
3149
  };
3148
3150
  }
3149
3151
  const ROUTEABLE_PORT_ATTACH_TIMEOUT_MS = 1_000;
3150
- /**
3151
- * Bytes of an image written in one storage transaction.
3152
- *
3153
- * The bound is the VFS's own, not a knob: a `writeRange` whose chunks fit
3154
- * inside one transaction is committed in place, and one that does not falls
3155
- * back to copy-on-write — which rewrites every chunk of the file, per slice,
3156
- * making a sliced write quadratic in its size. A whole number of chunks is
3157
- * the other half of that: a slice that ends mid-chunk makes the next one read
3158
- * the partial chunk back to complete it.
3159
- */
3160
- const FACET_IMAGE_WRITE_SLICE_BYTES = Math.floor(MAX_TX_BLOB_BYTES / CHUNK_SIZE) * CHUNK_SIZE;
3161
- /** A launch is re-driven once. A reset that recurs is not the transient one. */
3162
- const RESIDENT_LAUNCH_MAX_ATTEMPT = 1;
3152
+ /** 'running' is the measured common case: the platform's reset strikes
3153
+ * seconds after a launch settles, while the resident runs. */
3154
+ function residentLaunchDoing(record) {
3155
+ return record.phase === 'running' ? 'running' : 'starting';
3156
+ }
3163
3157
  export class FacetManager {
3164
3158
  ctx;
3165
3159
  env;
@@ -3183,21 +3177,25 @@ export class FacetManager {
3183
3177
  /** NIMBUS_DEBUG=1: placement diagnostics into the process log store. */
3184
3178
  debugEnabled = false;
3185
3179
  processRpcResources = new Map();
3186
- /** pid → the boot images its facet loads from; the image sweep's root set. */
3187
- residentImages = new Map();
3188
3180
  /**
3189
- * Launches suspended between chunks, waiting for a turn of their own.
3190
- *
3191
- * In-memory on purpose: a launch is only meaningful while the process table
3192
- * entry it is building for exists, and both are lost together if the isolate
3193
- * resets. What survives a reset is the journal, which names the launch's
3194
- * INPUTS rather than its position — a resumed queue would be resurrecting
3195
- * half-built work for pids that no longer exist, where re-driving a launch
3196
- * from its inputs is the same idempotent work again.
3181
+ * The content-addressed boot-image store (fabric's facet-image-store.ts),
3182
+ * writing through this session's kernel-credentialed VFS and rooted off the
3183
+ * live process table.
3184
+ */
3185
+ imageStore = new FacetImageStore(() => this._imageBlobs(), (pid) => this.processes.get(pid)?.state === 'running');
3186
+ /**
3187
+ * The resident-launch journal (fabric's launch-journal.ts): the durable
3188
+ * record of every resident this session owes the user, and its recovery
3189
+ * after an instance reset. This manager supplies what a launch IS — the
3190
+ * inputs `_spawnResident` re-drives from — and how its loss is reported.
3191
+ */
3192
+ launchJournal;
3193
+ /**
3194
+ * The granting side of the launch pacer (fabric's launch-pacer.ts). The
3195
+ * session's alarm re-enters the object through `pumpResidentLaunches`;
3196
+ * journal recovery rides the first pump.
3197
3197
  */
3198
- launchWaiters = [];
3199
- /** Whether this instance has already read the journal a reset leaves behind. */
3200
- launchesRecovered = false;
3198
+ launchPump;
3201
3199
  timedOutProcessIds = new Set();
3202
3200
  // attach-pid → serve-pid: the resident serve facet a bare-`opencode` dual
3203
3201
  // spawn created as an OS-child of the attach TUI. When the attach process
@@ -3268,34 +3266,62 @@ export class FacetManager {
3268
3266
  ? Reflect.get(env, 'NIMBUS_DEBUG')
3269
3267
  : undefined;
3270
3268
  this.debugEnabled = debugVar === '1' || debugVar === 'true';
3269
+ this.launchJournal = new ResidentLaunchJournal(ctx.storage, {
3270
+ generationBase: () => this.processes.pidBase,
3271
+ waitUntil: (promise) => this.ctx.waitUntil(promise),
3272
+ redrive: (record, attempt) => this._spawnResident(record.code, record.opts, attempt),
3273
+ onRedrive: (record) => this.hooks.notify?.('\x1b[2m[nimbus: the session restarted while '
3274
+ + `"${record.command}" was ${residentLaunchDoing(record)} — restarting it]\x1b[0m\r\n`),
3275
+ onAbandoned: (record) => this.hooks.notify?.('\x1b[2m[nimbus: the session restarted again while '
3276
+ + `"${record.command}" was ${residentLaunchDoing(record)} — leaving it stopped]\x1b[0m\r\n`),
3277
+ onRedriveFailed: (record, e) => this.hooks.notify?.(`\x1b[2m[nimbus: "${record.command}" could not be restarted: `
3278
+ + `${errorMessage(e)}]\x1b[0m\r\n`),
3279
+ });
3280
+ this.launchPump = new LaunchTurnPump({
3281
+ requestTurn: hooks.requestLaunchTurn?.bind(hooks),
3282
+ recover: () => this.launchJournal.recoverInterrupted(),
3283
+ });
3284
+ // The journal row of a resident lives for the PROCESS's lifetime, so its
3285
+ // release belongs on the one seam every end-of-life passes through —
3286
+ // exit, kill, self-reported exit and timeout abort all mark the table.
3287
+ // Rooted on waitUntil: the hook fires synchronously inside whatever turn
3288
+ // ended the process, and the delete must not be a floating promise there.
3289
+ this.processes.setOnTerminal((pid) => {
3290
+ this.ctx.waitUntil(this.launchJournal.release(pid));
3291
+ });
3271
3292
  }
3272
3293
  setVfs(vfs) { this.vfs = vfs; }
3273
3294
  /**
3274
- * The image store's directory, created before the first filesystem view is
3275
- * built rather than on the first image write.
3276
- *
3277
- * Lazily created, it made the store perturb the very view every manifest is
3278
- * built from: the root listing gained an entry the moment an image landed,
3279
- * so the next spawn of an identical program generated different text and
3280
- * addressed a different image. Existing before the first walk makes it
3281
- * stable.
3282
- *
3283
- * Sited on the exec path and not in setVfs, because setVfs runs while the
3284
- * Durable Object is coming up — including on every wake — and a synchronous
3285
- * filesystem write there costs the session its startup. Measured: a
3286
- * throwaway built that way stopped accepting terminal connections at all,
3287
- * while the same build without it served them.
3295
+ * The env/ctx pair every loader-backed runtime builds its facet pools
3296
+ * from. A pool is constructed from exactly these two, so the manager
3297
+ * exposes them as one narrow accessor rather than every runtime reaching
3298
+ * into its private fields.
3288
3299
  */
3289
- _ensureImageStoreDir(vfs) {
3290
- if (this.imageStoreDirReady)
3291
- return;
3292
- this.imageStoreDirReady = true;
3293
- try {
3294
- vfs.as(CRED_KERNEL).mkdir(FACET_IMAGE_DIR, { recursive: true, mode: 0o755 });
3300
+ loaderHost() {
3301
+ return { env: this.env, ctx: this.ctx };
3302
+ }
3303
+ /**
3304
+ * The image store's disk: this session's VFS, as the kernel — the store is
3305
+ * written by the kernel and read by processes through supervisor bindings
3306
+ * that enforce their own credential. Mode 0644 at creation, as POSIX has
3307
+ * it, is what makes the read succeed for any process by construction; the
3308
+ * store itself decides nothing about modes.
3309
+ */
3310
+ _imageBlobs() {
3311
+ const vfs = this.vfs;
3312
+ if (!vfs) {
3313
+ throw new Error('Nimbus: a resident process needs a session filesystem to materialize its boot image');
3295
3314
  }
3296
- catch { /* a session whose disk is not writable has no images to store */ }
3315
+ const fs = vfs.as(CRED_KERNEL);
3316
+ return {
3317
+ mkdirp: (dir) => fs.mkdir(dir, { recursive: true, mode: 0o755 }),
3318
+ sizeOf: (path) => (fs.exists(path) ? fs.lstat(path).size : null),
3319
+ writeFile: (path, bytes) => fs.writeFile(path, bytes, { mode: 0o644 }),
3320
+ writeRange: (path, offset, bytes) => fs.writeRange(path, offset, bytes),
3321
+ list: (dir) => fs.readdir(dir).map((entry) => entry.name),
3322
+ unlink: (path) => fs.unlink(path),
3323
+ };
3297
3324
  }
3298
- imageStoreDirReady = false;
3299
3325
  /**
3300
3326
  * W3.5 Fix B: hand the FacetManager a pre-warmed EsbuildService for
3301
3327
  * the ESM→CJS bundle pre-pass. NimbusSession already lazy-creates one
@@ -3603,7 +3629,7 @@ export class FacetManager {
3603
3629
  const diagOn = isExecDiagEnabled();
3604
3630
  const __bundleStart = diagOn ? Date.now() : 0;
3605
3631
  if (this.vfs)
3606
- this._ensureImageStoreDir(this.vfs);
3632
+ this.imageStore.ensureDir();
3607
3633
  const processVfs = this.vfs?.as(entry.cred);
3608
3634
  const credKey = `${entry.cred.uid}:${entry.cred.gid}:${entry.cred.groups.join(',')}`;
3609
3635
  const vfsState = processVfs
@@ -3913,7 +3939,7 @@ export class FacetManager {
3913
3939
  // home dirs (~/.local/share/opencode, …) via fs.promises.mkdir; those and
3914
3940
  // other writes flush live through the SUPERVISOR RPC bridge.
3915
3941
  if (this.vfs)
3916
- this._ensureImageStoreDir(this.vfs);
3942
+ this.imageStore.ensureDir();
3917
3943
  const processVfs = this.vfs?.as(entry.cred);
3918
3944
  const vfsState = processVfs
3919
3945
  ? await buildPrefetchBundle(processVfs, undefined, opts.cwd, '', this.esbuild || undefined)
@@ -4122,6 +4148,7 @@ export class FacetManager {
4122
4148
  .finally(() => {
4123
4149
  this.releaseProcessRpcResources(pid);
4124
4150
  }));
4151
+ await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, port);
4125
4152
  this.portRegistry.register(port, pid);
4126
4153
  return { pid, exitCode: 0, stdout: '', stderr: '', vfsWrites: {} };
4127
4154
  }
@@ -4158,7 +4185,7 @@ export class FacetManager {
4158
4185
  * The reader the fabric completes a boot spec's by-path members with.
4159
4186
  *
4160
4187
  * Reads as CRED_KERNEL because that is who WROTE them: the generated images
4161
- * are kernel-owned (`_materializeFacetImages`) and the runtime wasm images
4188
+ * are kernel-owned (`_imageBlobs`) and the runtime wasm images
4162
4189
  * are installed by the kernel. Uncached because these are the session's
4163
4190
  * largest files — a ruby interpreter image is 34.3 MiB — and pinning one in
4164
4191
  * the VFS content LRU for the life of the session is what once crashed the
@@ -4172,82 +4199,11 @@ export class FacetManager {
4172
4199
  const fs = vfs.as(CRED_KERNEL);
4173
4200
  return { readFile: (path) => fs.readFileUncached(path) };
4174
4201
  }
4175
- /**
4176
- * Materialize generated module sources in the content-addressed image store
4177
- * and return the `vfsTextModules` map naming them.
4178
- *
4179
- * A resident process's module map is sized by the user's disk, so it does
4180
- * not ride inside the boot spec — see ResidentCodeSpec.vfsTextModules.
4181
- * Writing it here, once, is what lets the session stop holding it: after
4182
- * this returns, the only thing it keeps is a path.
4183
- *
4184
- * The store is written by the kernel and read by the process, so nothing
4185
- * here depends on which credential spawned what. Digest collisions are the
4186
- * hash's problem; everything else is idempotent — an image already present
4187
- * at its own digest is already the bytes we were about to write.
4188
- */
4189
- async _materializeFacetImages(pid, modules, pacer) {
4190
- const vfs = this.vfs;
4191
- if (!vfs) {
4192
- throw new Error('Nimbus: a resident process needs a session filesystem to materialize its boot image');
4193
- }
4194
- const fs = vfs.as(CRED_KERNEL);
4195
- const images = {};
4196
- const sources = new Map();
4197
- for (const [moduleName, source] of Object.entries(modules)) {
4198
- const path = facetImagePath(await facetImageDigest(source));
4199
- images[moduleName] = path;
4200
- sources.set(path, source);
4201
- await pacer.spend(source.length);
4202
- }
4203
- // Register the WHOLE root set here, in one synchronous step, before any
4204
- // byte of it exists on disk. That ordering is the entire protocol between
4205
- // a launch and the sweep: an image is rooted from before it is written, so
4206
- // a sweep can never observe a file this launch has written but not yet
4207
- // claimed. The old loop achieved it by not awaiting at all, which read as
4208
- // "the writes must not be interrupted" — they may be. What must not be
4209
- // interrupted is the gap between writing and rooting, and rooting first
4210
- // closes it for every write that follows, however many turns they span.
4211
- this.residentImages.set(pid, [...sources.keys()]);
4212
- fs.mkdir(FACET_IMAGE_DIR, { recursive: true, mode: 0o755 });
4213
- for (const [path, source] of sources) {
4214
- const stored = path.replace(/^\/+/, '');
4215
- const bytes = new TextEncoder().encode(source);
4216
- // An image at its full size is a COMPLETE one: a write only ever grows
4217
- // the file from offset zero, so a write cut short by a reset leaves a
4218
- // strictly shorter file and fails this test. Size is enough of a check
4219
- // because the reader verifies the digest before the loader sees it.
4220
- if (fs.exists(stored) && fs.lstat(stored).size === bytes.byteLength) {
4221
- await pacer.spend(bytes.byteLength);
4222
- continue;
4223
- }
4224
- // Sliced because the platform resets the object over what ONE TURN has
4225
- // outstanding, not over what it eventually writes — pi's 22.9 MB map
4226
- // went in as a single write and took the session down with it ~25% of
4227
- // the time. Spending between slices is what puts the rest of the image
4228
- // on later turns; the slice bound is what keeps any one of them small.
4229
- let offset = 0;
4230
- do {
4231
- const slice = bytes.subarray(offset, offset + FACET_IMAGE_WRITE_SLICE_BYTES);
4232
- // The first slice REPLACES the file, so an interrupted write's remains
4233
- // are truncated to a known length rather than left as a tail past this
4234
- // content — and the mode is chosen here, at creation, as POSIX has it.
4235
- if (offset === 0)
4236
- fs.writeFile(stored, slice, { mode: 0o644 });
4237
- else
4238
- fs.writeRange(stored, offset, slice);
4239
- offset += slice.byteLength;
4240
- await pacer.spend(slice.byteLength);
4241
- } while (offset < bytes.byteLength);
4242
- }
4243
- this._sweepFacetImages(fs);
4244
- return images;
4245
- }
4246
4202
  /**
4247
4203
  * Fail a paced launch whose process went away while it was suspended.
4248
4204
  *
4249
- * Between turns anything may happen to the process — a kill, a reap, a
4250
- * `_sweepFacetImages` that has already unrooted its images. Continuing would
4205
+ * Between turns anything may happen to the process — a kill, a reap, an
4206
+ * image sweep that has already unrooted its images. Continuing would
4251
4207
  * spend further turns building a facet for a pid nothing will ever attach
4252
4208
  * to, and would write image files the next sweep immediately collects.
4253
4209
  */
@@ -4256,44 +4212,6 @@ export class FacetManager {
4256
4212
  return;
4257
4213
  throw new Error(`Nimbus: resident launch for pid ${pid} was cancelled while it was suspended`);
4258
4214
  }
4259
- /**
4260
- * Drop every image no running process boots from.
4261
- *
4262
- * Content addressing means a changed program writes a NEW image rather than
4263
- * replacing one, so a watch loop — or simply a session that runs a few
4264
- * different programs — would otherwise leave one bundle-sized file behind
4265
- * per distinct version. The root set is the process table, which is exact:
4266
- * an image is live for precisely as long as the process that boots from it.
4267
- * Nothing is left for a TTL or an eviction heuristic to guess at, and after
4268
- * a DO reset the table is empty so every orphan goes.
4269
- */
4270
- _sweepFacetImages(fs) {
4271
- const live = new Set();
4272
- for (const [pid, paths] of this.residentImages) {
4273
- if (this.processes.get(pid)?.state === 'running') {
4274
- for (const path of paths)
4275
- live.add(path);
4276
- }
4277
- else {
4278
- this.residentImages.delete(pid);
4279
- }
4280
- }
4281
- let entries;
4282
- try {
4283
- entries = fs.readdir(FACET_IMAGE_DIR);
4284
- }
4285
- catch {
4286
- return;
4287
- }
4288
- for (const entry of entries) {
4289
- if (live.has(`/${FACET_IMAGE_DIR}/${entry.name}`))
4290
- continue;
4291
- try {
4292
- fs.unlink(`${FACET_IMAGE_DIR}/${entry.name}`);
4293
- }
4294
- catch { /* already gone */ }
4295
- }
4296
- }
4297
4215
  /**
4298
4216
  * The one way this manager boots a resident process. Every resident process
4299
4217
  * is a facet of this session; there is nothing to place and nothing here
@@ -4324,110 +4242,16 @@ export class FacetManager {
4324
4242
  this.vfs?.activateAppendWriter(pid, writerId);
4325
4243
  }
4326
4244
  /**
4327
- * How a paced launch asks for a fresh turn.
4328
- *
4329
- * The host grants one by calling `pumpResidentLaunches` from a context that
4330
- * is genuinely a new invocation — the session's alarm. Without such a host
4331
- * there is no fresh turn to be had, and the launch continues on this one
4332
- * rather than hanging: that is exactly the single-turn launch this path has
4333
- * always performed, so a harness or a runtime without alarms loses the
4334
- * responsiveness but keeps the behaviour.
4335
- */
4336
- launchScheduler = {
4337
- nextTurn: (chunkEnded) => new Promise((resume) => {
4338
- this.launchWaiters.push({ resume, chunkEnded });
4339
- if (this.hooks.requestLaunchTurn) {
4340
- this.hooks.requestLaunchTurn();
4341
- return;
4342
- }
4343
- setTimeout(() => { void this.pumpResidentLaunches(); }, 0);
4344
- }),
4345
- };
4346
- /**
4347
- * Run one chunk of every launch waiting for a turn.
4348
- *
4349
- * Awaits the chunk each resumed launch then performs, so the invocation that
4350
- * granted the turn is the invocation that pays for the work — rather than
4351
- * releasing it into a handler's microtask drain, where nothing owns it and
4352
- * the runtime may tear the context down mid-chunk.
4245
+ * Grant every suspended launch a chunk of this turn — the session's alarm
4246
+ * calls this, and journal recovery rides the first pump. See fabric's
4247
+ * `LaunchTurnPump.pump` for the ownership argument.
4353
4248
  */
4354
- async pumpResidentLaunches() {
4355
- await this._recoverInterruptedLaunches();
4356
- const waiting = this.launchWaiters;
4357
- if (waiting.length === 0)
4358
- return;
4359
- this.launchWaiters = [];
4360
- for (const waiter of waiting)
4361
- waiter.resume();
4362
- await Promise.all(waiting.map((waiter) => waiter.chunkEnded));
4363
- }
4364
- /**
4365
- * Re-drive the launches a previous instance was building when it was reset.
4366
- *
4367
- * The platform resets a session Durable Object over what one turn has
4368
- * outstanding in storage ("Internal error in Durable Object storage caused
4369
- * object to be reset"), and a launch is the largest writer this session has.
4370
- * Everything the launch held was in memory, so the process it was building
4371
- * and the terminal watching it both went with the instance, and until now
4372
- * the user was told nothing at all.
4373
- *
4374
- * Sited on the pump because the pump is what an alarm calls, and a launch
4375
- * that was suspended has an alarm armed for it — a reset during a chunk
4376
- * fails that alarm, and the platform re-delivers it to the instance that
4377
- * replaces this one. So the first turn after a reset is already this one.
4378
- *
4379
- * Runs once per instance: the journal only changes when a launch of THIS
4380
- * instance starts or settles, and those are rows this instance wrote.
4381
- */
4382
- async _recoverInterruptedLaunches() {
4383
- if (this.launchesRecovered)
4384
- return;
4385
- this.launchesRecovered = true;
4386
- const journal = await this.ctx.storage.list({
4387
- prefix: RESIDENT_LAUNCH_KEY_PREFIX,
4388
- });
4389
- for (const [key, record] of journal) {
4390
- // A pid at or below this instance's base was allocated by a PREVIOUS one
4391
- // (process-table.ts, PID_GEN_STRIDE), so its launch never finished; above
4392
- // the base is this instance's own, still running. Same predicate as
4393
- // `session/rpc.ts` uses to attribute a prior generation's pid.
4394
- if (!(record.pid > 0 && record.pid <= this.processes.pidBase))
4395
- continue;
4396
- await this.ctx.storage.delete(key);
4397
- if (record.attempt >= RESIDENT_LAUNCH_MAX_ATTEMPT) {
4398
- this.hooks.notify?.('\x1b[2m[nimbus: the session restarted again while '
4399
- + `"${record.command}" was starting — leaving it stopped]\x1b[0m\r\n`);
4400
- continue;
4401
- }
4402
- this.hooks.notify?.('\x1b[2m[nimbus: the session restarted while '
4403
- + `"${record.command}" was starting — restarting it]\x1b[0m\r\n`);
4404
- // Not awaited: this call is running inside the alarm that granted the
4405
- // turn, and the launch it starts asks for turns of its own through that
4406
- // same alarm — awaiting it here would be waiting on an alarm that cannot
4407
- // be scheduled until this one returns.
4408
- this.ctx.waitUntil(this._spawnResident(record.code, record.opts, record.attempt + 1)
4409
- .catch((e) => {
4410
- this.hooks.notify?.(`\x1b[2m[nimbus: "${record.command}" could not be restarted: `
4411
- + `${errorMessage(e)}]\x1b[0m\r\n`);
4412
- }));
4413
- }
4414
- }
4415
- /**
4416
- * Record a launch as in flight, so an instance that replaces this one knows
4417
- * it never finished. Best-effort: a launch that cannot be journalled still
4418
- * runs, and a reset then costs exactly what it cost before the journal.
4419
- */
4420
- async _journalLaunch(record) {
4421
- try {
4422
- await this.ctx.storage.put(`${RESIDENT_LAUNCH_KEY_PREFIX}${record.pid}`, record);
4423
- }
4424
- catch (e) {
4425
- console.warn('[nimbus] resident launch journal write failed:', errorMessage(e));
4426
- }
4249
+ pumpResidentLaunches() {
4250
+ return this.launchPump.pump();
4427
4251
  }
4428
4252
  /** Whether any launch is suspended waiting for a turn. */
4429
4253
  get hasPendingLaunchTurns() {
4430
- return this.launchWaiters.length > 0;
4254
+ return this.launchPump.hasPending;
4431
4255
  }
4432
4256
  /** Allocate a free loopback port for a resident server facet (from 4096 up). */
4433
4257
  _allocateLoopbackPort() {
@@ -4664,29 +4488,41 @@ export class FacetManager {
4664
4488
  */
4665
4489
  async _runResidentLaunch(entry, code, command, opts, attempt) {
4666
4490
  const cwd = opts.cwd || '/home/user';
4667
- const pacer = new LaunchPacer(this.launchScheduler, launchChunkMaxBytes(this.env), () => this._assertLaunchStillOwned(entry.pid));
4668
- // Journalled before the first byte of work and removed when the launch
4669
- // settles either way, so a row that outlives its instance means one thing:
4670
- // the launch was still running when the object was reset. A re-drive owns
4671
- // its own process, so the caller's pid goes with the instance that had it.
4672
- await this._journalLaunch({
4491
+ const pacer = new LaunchPacer(this.launchPump, launchChunkMaxBytes(this.env), () => this._assertLaunchStillOwned(entry.pid));
4492
+ // Journalled before the first byte of work. A launch that FAILS deletes
4493
+ // its row on the way out — its process has already been exited and the
4494
+ // user notified, so there is nothing left to owe. A launch that SETTLES
4495
+ // rewrites the row as a running resident instead: measured live, the
4496
+ // resets this journal exists for strike seconds AFTER the launch settles,
4497
+ // and a row deleted at settle was exactly why recovery sat inert while
4498
+ // sessions kept dying. The row is finally released by the supervisor's
4499
+ // terminal hook when the process ends. A re-drive owns its own process,
4500
+ // so the caller's pid goes with the instance that had it.
4501
+ const record = {
4673
4502
  pid: entry.pid,
4674
4503
  command,
4675
4504
  code,
4676
4505
  opts: { ...opts, skipSpawn: undefined, callerPid: undefined },
4677
4506
  attempt,
4678
- });
4507
+ phase: 'starting',
4508
+ };
4509
+ await this.launchJournal.journal(record);
4679
4510
  try {
4680
4511
  await this._residentLaunchBody(entry, code, command, cwd, opts, pacer);
4681
4512
  }
4513
+ catch (e) {
4514
+ await this.launchJournal.release(entry.pid);
4515
+ throw e;
4516
+ }
4682
4517
  finally {
4683
4518
  pacer.settle();
4684
- try {
4685
- await this.ctx.storage.delete(`${RESIDENT_LAUNCH_KEY_PREFIX}${entry.pid}`);
4686
- }
4687
- catch (e) {
4688
- console.warn('[nimbus] resident launch journal delete failed:', errorMessage(e));
4689
- }
4519
+ }
4520
+ // Booted and running: the launch proved itself, so the resident starts
4521
+ // its running life with a fresh re-drive budget. If the process already
4522
+ // ended inside the launch body's own settlement, the terminal hook has
4523
+ // released the row — do not write it back.
4524
+ if (this.launchJournal.has(entry.pid)) {
4525
+ await this.launchJournal.journal({ ...record, attempt: 0, phase: 'running' });
4690
4526
  }
4691
4527
  }
4692
4528
  async _residentLaunchBody(entry, code, command, cwd, opts, pacer) {
@@ -4699,7 +4535,7 @@ export class FacetManager {
4699
4535
  }
4700
4536
  }
4701
4537
  if (this.vfs)
4702
- this._ensureImageStoreDir(this.vfs);
4538
+ this.imageStore.ensureDir();
4703
4539
  const diagOn = isExecDiagEnabled();
4704
4540
  const __bundleStart = diagOn ? Date.now() : 0;
4705
4541
  const processVfs = this.vfs?.as(entry.cred);
@@ -4744,7 +4580,7 @@ export class FacetManager {
4744
4580
  let handle;
4745
4581
  let resourcesTracked = false;
4746
4582
  try {
4747
- const vfsTextModules = await this._materializeFacetImages(entry.pid, {
4583
+ const vfsTextModules = await this.imageStore.materialize(entry.pid, {
4748
4584
  'worker.js': generatedWorker.code,
4749
4585
  ...generatedWorker.modules,
4750
4586
  }, pacer);
@@ -4834,6 +4670,9 @@ export class FacetManager {
4834
4670
  await handle.booted();
4835
4671
  }
4836
4672
  if (opts.port && opts.port > 0 && opts.port < 65536) {
4673
+ // A new process on a port retires the previous occupant's preview
4674
+ // capability, so a URL handed out for that one cannot reach this one.
4675
+ await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, opts.port);
4837
4676
  this.portRegistry.register(opts.port, entry.pid);
4838
4677
  }
4839
4678
  }
@@ -4901,6 +4740,9 @@ export class FacetManager {
4901
4740
  resourcesTracked = true;
4902
4741
  this.portRegistry.bindFacetStub(entry.pid, handle.routeTarget);
4903
4742
  if (opts.port && opts.port > 0 && opts.port < 65536) {
4743
+ // A new process on a port retires the previous occupant's preview
4744
+ // capability, so a URL handed out for that one cannot reach this one.
4745
+ await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, opts.port);
4904
4746
  this.portRegistry.register(opts.port, entry.pid);
4905
4747
  }
4906
4748
  return { pid: entry.pid, boot: await handle.booted() };
@@ -4921,8 +4763,9 @@ export class FacetManager {
4921
4763
  throw e;
4922
4764
  }
4923
4765
  }
4924
- registerPort(pid, port) {
4766
+ async registerPort(pid, port) {
4925
4767
  if (port > 0 && port < 65536) {
4768
+ await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, port);
4926
4769
  this.portRegistry.register(port, pid);
4927
4770
  }
4928
4771
  }
@@ -23,7 +23,7 @@
23
23
  * place; the L2 asset cache makes refetches cheap).
24
24
  */
25
25
  import { z } from 'zod/v4';
26
- import type { WorkerCode } from '../loaders/vendor/types.js';
26
+ import type { WorkerCode } from '@nimbus-sh/fabric/vendor/types.js';
27
27
  export interface OpencodeAssetsEnv {
28
28
  ASSETS: {
29
29
  fetch(req: Request): Promise<Response>;
@@ -1 +1 @@
1
- {"version":3,"file":"opencode-staging.d.ts","sourceRoot":"","sources":["../../src/facets/opencode-staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAwB3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAG7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;CACpD;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;iBAwBlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAoCxE,iFAAiF;AACjF,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAC/B,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,CAMhD;AAkFD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAC/B,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,UAAU,CAAC,CAyDrB"}
1
+ {"version":3,"file":"opencode-staging.d.ts","sourceRoot":"","sources":["../../src/facets/opencode-staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAwB3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAIpE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;CACpD;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;iBAwBlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAoCxE,iFAAiF;AACjF,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAC/B,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,CAMhD;AAuFD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAC/B,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,UAAU,CAAC,CAyDrB"}
@@ -30,6 +30,7 @@ import { fetchOpenTUIWasmBytes } from '../runtime/opentui-wasm-bytes.js';
30
30
  import { OPENTUI_WASM_MODULE_NAME } from '../runtime/opentui-facet-backend.js';
31
31
  import { OPENCODE_CHUNKS_PACK, OPENCODE_TREE_SITTER_WASMS, OPENCODE_TUI_WORKERS, OPENCODE_YOGA_WASM, } from '../opencode-artifact.generated.js';
32
32
  import { generateOpencodeRunnerCode, opencodeBuiltinBridgeModules, OPENCODE_BUNDLE_MODULE_NAME, SQLITE_WASM_MODULE_NAME, YOGA_WASM_MODULE_NAME, } from '../runtime/opencode-facet-runner.js';
33
+ import { setStagedBootAssembler } from '@nimbus-sh/fabric/process-fabric.js';
33
34
  import { CF_COMPAT_DATE } from '@nimbus-sh/core/constants.js';
34
35
  /**
35
36
  * Everything a facet spawn needs beyond the artifact sources themselves.
@@ -145,6 +146,9 @@ function chunkModuleEntries(env) {
145
146
  }
146
147
  return chunkEntriesInflight;
147
148
  }
149
+ // A 'staged' boot spec reaching the fabric assembles through this. Module
150
+ // scope so every isolate that can host a staged facet has it before first use.
151
+ setStagedBootAssembler((env, stage) => assembleOpencodeFacetConfig(env, stage));
148
152
  /**
149
153
  * Assemble the full Worker Loader config for an opencode facet from a stage
150
154
  * spec. Returns the config WITHOUT the SUPERVISOR env binding — the caller
@@ -50,7 +50,7 @@ export interface ViteDevServerOptions {
50
50
  /**
51
51
  * Worker bindings env. Required for the on-demand-bundle facet path
52
52
  * (LOADER + ctx-exports). When provided, /preview/@modules/<spec>
53
- * misses bundle in a NimbusLoaderPool isolate instead of the
53
+ * misses bundle in a LoaderPool isolate instead of the
54
54
  * supervisor's EsbuildService — same architecture as the
55
55
  * pre-bundle path. Without this option, the supervisor falls back
56
56
  * to in-process esbuild (legacy behaviour).
@@ -180,7 +180,7 @@ export declare class ViteDevServer {
180
180
  private logSink;
181
181
  constructor(opts: ViteDevServerOptions);
182
182
  /**
183
- * Lazily construct the NimbusLoaderPool used for on-demand bundling
183
+ * Lazily construct the LoaderPool used for on-demand bundling
184
184
  * of /preview/@modules/<spec> requests that miss both the in-memory
185
185
  * and pkg_esm_bundles caches. Mirrors the pre-bundle pool's
186
186
  * configuration: 1 worker, internal pLimit not needed (one bundle
@@ -1262,7 +1262,7 @@ export class ViteDevServer {
1262
1262
  this.detectTailwind();
1263
1263
  }
1264
1264
  /**
1265
- * Lazily construct the NimbusLoaderPool used for on-demand bundling
1265
+ * Lazily construct the LoaderPool used for on-demand bundling
1266
1266
  * of /preview/@modules/<spec> requests that miss both the in-memory
1267
1267
  * and pkg_esm_bundles caches. Mirrors the pre-bundle pool's
1268
1268
  * configuration: 1 worker, internal pLimit not needed (one bundle
@@ -1278,11 +1278,11 @@ export class ViteDevServer {
1278
1278
  if (!this.env || !this.ctx)
1279
1279
  return null;
1280
1280
  this.onDemandPoolPromise = (async () => {
1281
- const { NimbusLoaderPool } = await import('../loaders/loader-pool.js');
1281
+ const { LoaderPool } = await import('@nimbus-sh/fabric/loader-pool.js');
1282
1282
  const { PRE_BUNDLE_PREAMBLE } = await import('../loaders/pre-bundle-preamble.js');
1283
1283
  const { fetchEsbuildWasmBytes } = await import('../runtime/esbuild-wasm-bytes.js');
1284
1284
  const wasmBytes = await fetchEsbuildWasmBytes(this.env);
1285
- const pool = new NimbusLoaderPool(this.env, this.ctx, {
1285
+ const pool = new LoaderPool(this.env, this.ctx, {
1286
1286
  concurrency: 1,
1287
1287
  timeoutMs: 60_000,
1288
1288
  retries: 0,
@@ -1797,7 +1797,7 @@ export class ViteDevServer {
1797
1797
  // supervisor isolate. For large modules (lucide-react, ~18 MiB
1798
1798
  // unpacked) that OOM'd the supervisor and surfaced as CF error
1799
1799
  // 1101 on /preview/@modules/lucide-react, taking down the entire
1800
- // preview. We now dispatch the bundle work to a NimbusLoaderPool
1800
+ // preview. We now dispatch the bundle work to a LoaderPool
1801
1801
  // isolate via its own 128 MiB heap — same pattern as install-time
1802
1802
  // pre-bundling. Supervisor never bundles esbuild for any path.
1803
1803
  //