@nimbus-sh/worker 0.2.3 → 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 (129) hide show
  1. package/dist/_shared/session-router.d.ts +8 -0
  2. package/dist/_shared/session-router.d.ts.map +1 -1
  3. package/dist/_shared/session-router.js +8 -0
  4. package/dist/facets/cirrus-real.js +1 -1
  5. package/dist/facets/manager.d.ts +40 -126
  6. package/dist/facets/manager.d.ts.map +1 -1
  7. package/dist/facets/manager.js +97 -313
  8. package/dist/facets/opencode-staging.d.ts +1 -1
  9. package/dist/facets/opencode-staging.d.ts.map +1 -1
  10. package/dist/facets/opencode-staging.js +4 -0
  11. package/dist/facets/vite-dev-server.d.ts +2 -2
  12. package/dist/facets/vite-dev-server.js +4 -4
  13. package/dist/git/commands.d.ts +8 -0
  14. package/dist/git/commands.d.ts.map +1 -1
  15. package/dist/git/commands.js +41 -9
  16. package/dist/git/network-facet.d.ts +1 -1
  17. package/dist/git/network-facet.d.ts.map +1 -1
  18. package/dist/git/network-facet.js +35 -12
  19. package/dist/git-bundle.generated.d.ts +2 -2
  20. package/dist/git-bundle.generated.d.ts.map +1 -1
  21. package/dist/git-bundle.generated.js +3 -3
  22. package/dist/index.js +1 -1
  23. package/dist/loaders/child-process/spawn-facet.js +1 -1
  24. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
  25. package/dist/loaders/child-process/spawn-pool.js +2 -2
  26. package/dist/loaders/generated-workers.d.ts +1 -1
  27. package/dist/loaders/generated-workers.d.ts.map +1 -1
  28. package/dist/loaders/generated-workers.js +2 -2
  29. package/dist/loaders/npm-resolve-preamble.d.ts +3 -3
  30. package/dist/loaders/npm-resolve-preamble.js +3 -3
  31. package/dist/loaders/pre-bundle-preamble.d.ts +5 -5
  32. package/dist/loaders/pre-bundle-preamble.js +6 -6
  33. package/dist/loaders/process-host.d.ts +6 -107
  34. package/dist/loaders/process-host.d.ts.map +1 -1
  35. package/dist/loaders/process-host.js +6 -405
  36. package/dist/npm/install-batch-facet.js +1 -1
  37. package/dist/npm/installer.d.ts +4 -4
  38. package/dist/npm/installer.js +18 -18
  39. package/dist/npm/pre-bundle-facet.d.ts +2 -2
  40. package/dist/npm/pre-bundle-facet.js +5 -5
  41. package/dist/npm/resolve-facet.d.ts +1 -1
  42. package/dist/npm/resolve-facet.js +1 -1
  43. package/dist/npm/resolve-one-facet.d.ts +5 -5
  44. package/dist/npm/resolve-one-facet.js +5 -5
  45. package/dist/router/index.js +1 -1
  46. package/dist/router/remote-api.js +8 -0
  47. package/dist/runtime/bun-repl.d.ts +1 -1
  48. package/dist/runtime/bun-repl.js +3 -3
  49. package/dist/runtime/facet-loader-host.d.ts +5 -8
  50. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  51. package/dist/runtime/facet-loader-host.js +7 -11
  52. package/dist/runtime/node-repl.js +2 -2
  53. package/dist/runtime/python-repl.js +3 -3
  54. package/dist/runtime/ruby-repl.js +2 -2
  55. package/dist/runtime/ruby-resident.js +1 -1
  56. package/dist/session/hibernation.d.ts +18 -51
  57. package/dist/session/hibernation.d.ts.map +1 -1
  58. package/dist/session/hibernation.js +49 -186
  59. package/dist/session/init-phases.d.ts +2 -2
  60. package/dist/session/init-phases.d.ts.map +1 -1
  61. package/dist/session/init-phases.js +1 -1
  62. package/dist/session/init.d.ts.map +1 -1
  63. package/dist/session/init.js +6 -3
  64. package/dist/session/keys.d.ts +14 -37
  65. package/dist/session/keys.d.ts.map +1 -1
  66. package/dist/session/keys.js +17 -37
  67. package/dist/session/nimbus-session.d.ts +10 -4
  68. package/dist/session/nimbus-session.d.ts.map +1 -1
  69. package/dist/session/nimbus-session.js +20 -11
  70. package/dist/session/port-capability.d.ts +43 -0
  71. package/dist/session/port-capability.d.ts.map +1 -0
  72. package/dist/session/port-capability.js +49 -0
  73. package/dist/session/programmatic.d.ts +41 -5
  74. package/dist/session/programmatic.d.ts.map +1 -1
  75. package/dist/session/programmatic.js +147 -15
  76. package/dist/session/routes.d.ts +2 -0
  77. package/dist/session/routes.d.ts.map +1 -1
  78. package/dist/session/routes.js +91 -16
  79. package/dist/session/rpc.d.ts +35 -8
  80. package/dist/session/rpc.d.ts.map +1 -1
  81. package/dist/session/rpc.js +73 -14
  82. package/dist/session/start-real-vite.d.ts.map +1 -1
  83. package/dist/session/start-real-vite.js +2 -0
  84. package/dist/session/state-store.d.ts +3 -2
  85. package/dist/session/state-store.d.ts.map +1 -1
  86. package/dist/session/state-store.js +3 -2
  87. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  88. package/dist/session/supervisor-rpc.js +5 -0
  89. package/dist/session/ws.d.ts +2 -2
  90. package/dist/session/ws.d.ts.map +1 -1
  91. package/dist/session/ws.js +2 -2
  92. package/dist/wrangler/nimbus-wrangler.js +1 -1
  93. package/package.json +3 -2
  94. package/dist/facets/inner-do-registry.d.ts +0 -41
  95. package/dist/facets/inner-do-registry.d.ts.map +0 -1
  96. package/dist/facets/inner-do-registry.js +0 -51
  97. package/dist/facets/launch-pacer.d.ts +0 -111
  98. package/dist/facets/launch-pacer.d.ts.map +0 -1
  99. package/dist/facets/launch-pacer.js +0 -130
  100. package/dist/loaders/fanout-pool.d.ts +0 -207
  101. package/dist/loaders/fanout-pool.d.ts.map +0 -1
  102. package/dist/loaders/fanout-pool.js +0 -363
  103. package/dist/loaders/loader-pool.d.ts +0 -294
  104. package/dist/loaders/loader-pool.d.ts.map +0 -1
  105. package/dist/loaders/loader-pool.js +0 -642
  106. package/dist/loaders/process-fabric.d.ts +0 -509
  107. package/dist/loaders/process-fabric.d.ts.map +0 -1
  108. package/dist/loaders/process-fabric.js +0 -360
  109. package/dist/loaders/vendor/errors.d.ts +0 -24
  110. package/dist/loaders/vendor/errors.d.ts.map +0 -1
  111. package/dist/loaders/vendor/errors.js +0 -46
  112. package/dist/loaders/vendor/serialize.d.ts +0 -3
  113. package/dist/loaders/vendor/serialize.d.ts.map +0 -1
  114. package/dist/loaders/vendor/serialize.js +0 -25
  115. package/dist/loaders/vendor/types.d.ts +0 -69
  116. package/dist/loaders/vendor/types.d.ts.map +0 -1
  117. package/dist/loaders/vendor/types.js +0 -4
  118. package/dist/loaders/workerd-facet-host.d.ts +0 -141
  119. package/dist/loaders/workerd-facet-host.d.ts.map +0 -1
  120. package/dist/loaders/workerd-facet-host.js +0 -284
  121. package/dist/session/bindings.d.ts +0 -209
  122. package/dist/session/bindings.d.ts.map +0 -1
  123. package/dist/session/bindings.js +0 -680
  124. package/dist/session/ctx-exports.d.ts +0 -16
  125. package/dist/session/ctx-exports.d.ts.map +0 -1
  126. package/dist/session/ctx-exports.js +0 -22
  127. package/dist/session/ws-hibernation-config.d.ts +0 -64
  128. package/dist/session/ws-hibernation-config.d.ts.map +0 -1
  129. 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,29 +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.
3197
3191
  */
3198
- launchWaiters = [];
3199
- /** Whether this instance has already read the journal a reset leaves behind. */
3200
- launchesRecovered = false;
3192
+ launchJournal;
3201
3193
  /**
3202
- * Pids THIS instance holds journal rows for. What keeps the terminal hook —
3203
- * which fires for every process, shells and one-shots included — from
3204
- * paying a storage delete for pids that never had a row. In-memory is
3205
- * correct: rows from a previous instance are recovery's to consume, never
3206
- * this hook's.
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.
3207
3197
  */
3208
- journalledPids = new Set();
3198
+ launchPump;
3209
3199
  timedOutProcessIds = new Set();
3210
3200
  // attach-pid → serve-pid: the resident serve facet a bare-`opencode` dual
3211
3201
  // spawn created as an OS-child of the attach TUI. When the attach process
@@ -3276,42 +3266,62 @@ export class FacetManager {
3276
3266
  ? Reflect.get(env, 'NIMBUS_DEBUG')
3277
3267
  : undefined;
3278
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
+ });
3279
3284
  // The journal row of a resident lives for the PROCESS's lifetime, so its
3280
3285
  // release belongs on the one seam every end-of-life passes through —
3281
3286
  // exit, kill, self-reported exit and timeout abort all mark the table.
3282
3287
  // Rooted on waitUntil: the hook fires synchronously inside whatever turn
3283
3288
  // ended the process, and the delete must not be a floating promise there.
3284
3289
  this.processes.setOnTerminal((pid) => {
3285
- this.ctx.waitUntil(this._releaseResidentJournal(pid));
3290
+ this.ctx.waitUntil(this.launchJournal.release(pid));
3286
3291
  });
3287
3292
  }
3288
3293
  setVfs(vfs) { this.vfs = vfs; }
3289
3294
  /**
3290
- * The image store's directory, created before the first filesystem view is
3291
- * built rather than on the first image write.
3292
- *
3293
- * Lazily created, it made the store perturb the very view every manifest is
3294
- * built from: the root listing gained an entry the moment an image landed,
3295
- * so the next spawn of an identical program generated different text and
3296
- * addressed a different image. Existing before the first walk makes it
3297
- * stable.
3298
- *
3299
- * Sited on the exec path and not in setVfs, because setVfs runs while the
3300
- * Durable Object is coming up — including on every wake — and a synchronous
3301
- * filesystem write there costs the session its startup. Measured: a
3302
- * throwaway built that way stopped accepting terminal connections at all,
3303
- * 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.
3304
3299
  */
3305
- _ensureImageStoreDir(vfs) {
3306
- if (this.imageStoreDirReady)
3307
- return;
3308
- this.imageStoreDirReady = true;
3309
- try {
3310
- 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');
3311
3314
  }
3312
- 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
+ };
3313
3324
  }
3314
- imageStoreDirReady = false;
3315
3325
  /**
3316
3326
  * W3.5 Fix B: hand the FacetManager a pre-warmed EsbuildService for
3317
3327
  * the ESM→CJS bundle pre-pass. NimbusSession already lazy-creates one
@@ -3619,7 +3629,7 @@ export class FacetManager {
3619
3629
  const diagOn = isExecDiagEnabled();
3620
3630
  const __bundleStart = diagOn ? Date.now() : 0;
3621
3631
  if (this.vfs)
3622
- this._ensureImageStoreDir(this.vfs);
3632
+ this.imageStore.ensureDir();
3623
3633
  const processVfs = this.vfs?.as(entry.cred);
3624
3634
  const credKey = `${entry.cred.uid}:${entry.cred.gid}:${entry.cred.groups.join(',')}`;
3625
3635
  const vfsState = processVfs
@@ -3929,7 +3939,7 @@ export class FacetManager {
3929
3939
  // home dirs (~/.local/share/opencode, …) via fs.promises.mkdir; those and
3930
3940
  // other writes flush live through the SUPERVISOR RPC bridge.
3931
3941
  if (this.vfs)
3932
- this._ensureImageStoreDir(this.vfs);
3942
+ this.imageStore.ensureDir();
3933
3943
  const processVfs = this.vfs?.as(entry.cred);
3934
3944
  const vfsState = processVfs
3935
3945
  ? await buildPrefetchBundle(processVfs, undefined, opts.cwd, '', this.esbuild || undefined)
@@ -4138,6 +4148,7 @@ export class FacetManager {
4138
4148
  .finally(() => {
4139
4149
  this.releaseProcessRpcResources(pid);
4140
4150
  }));
4151
+ await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, port);
4141
4152
  this.portRegistry.register(port, pid);
4142
4153
  return { pid, exitCode: 0, stdout: '', stderr: '', vfsWrites: {} };
4143
4154
  }
@@ -4174,7 +4185,7 @@ export class FacetManager {
4174
4185
  * The reader the fabric completes a boot spec's by-path members with.
4175
4186
  *
4176
4187
  * Reads as CRED_KERNEL because that is who WROTE them: the generated images
4177
- * are kernel-owned (`_materializeFacetImages`) and the runtime wasm images
4188
+ * are kernel-owned (`_imageBlobs`) and the runtime wasm images
4178
4189
  * are installed by the kernel. Uncached because these are the session's
4179
4190
  * largest files — a ruby interpreter image is 34.3 MiB — and pinning one in
4180
4191
  * the VFS content LRU for the life of the session is what once crashed the
@@ -4188,82 +4199,11 @@ export class FacetManager {
4188
4199
  const fs = vfs.as(CRED_KERNEL);
4189
4200
  return { readFile: (path) => fs.readFileUncached(path) };
4190
4201
  }
4191
- /**
4192
- * Materialize generated module sources in the content-addressed image store
4193
- * and return the `vfsTextModules` map naming them.
4194
- *
4195
- * A resident process's module map is sized by the user's disk, so it does
4196
- * not ride inside the boot spec — see ResidentCodeSpec.vfsTextModules.
4197
- * Writing it here, once, is what lets the session stop holding it: after
4198
- * this returns, the only thing it keeps is a path.
4199
- *
4200
- * The store is written by the kernel and read by the process, so nothing
4201
- * here depends on which credential spawned what. Digest collisions are the
4202
- * hash's problem; everything else is idempotent — an image already present
4203
- * at its own digest is already the bytes we were about to write.
4204
- */
4205
- async _materializeFacetImages(pid, modules, pacer) {
4206
- const vfs = this.vfs;
4207
- if (!vfs) {
4208
- throw new Error('Nimbus: a resident process needs a session filesystem to materialize its boot image');
4209
- }
4210
- const fs = vfs.as(CRED_KERNEL);
4211
- const images = {};
4212
- const sources = new Map();
4213
- for (const [moduleName, source] of Object.entries(modules)) {
4214
- const path = facetImagePath(await facetImageDigest(source));
4215
- images[moduleName] = path;
4216
- sources.set(path, source);
4217
- await pacer.spend(source.length);
4218
- }
4219
- // Register the WHOLE root set here, in one synchronous step, before any
4220
- // byte of it exists on disk. That ordering is the entire protocol between
4221
- // a launch and the sweep: an image is rooted from before it is written, so
4222
- // a sweep can never observe a file this launch has written but not yet
4223
- // claimed. The old loop achieved it by not awaiting at all, which read as
4224
- // "the writes must not be interrupted" — they may be. What must not be
4225
- // interrupted is the gap between writing and rooting, and rooting first
4226
- // closes it for every write that follows, however many turns they span.
4227
- this.residentImages.set(pid, [...sources.keys()]);
4228
- fs.mkdir(FACET_IMAGE_DIR, { recursive: true, mode: 0o755 });
4229
- for (const [path, source] of sources) {
4230
- const stored = path.replace(/^\/+/, '');
4231
- const bytes = new TextEncoder().encode(source);
4232
- // An image at its full size is a COMPLETE one: a write only ever grows
4233
- // the file from offset zero, so a write cut short by a reset leaves a
4234
- // strictly shorter file and fails this test. Size is enough of a check
4235
- // because the reader verifies the digest before the loader sees it.
4236
- if (fs.exists(stored) && fs.lstat(stored).size === bytes.byteLength) {
4237
- await pacer.spend(bytes.byteLength);
4238
- continue;
4239
- }
4240
- // Sliced because the platform resets the object over what ONE TURN has
4241
- // outstanding, not over what it eventually writes — pi's 22.9 MB map
4242
- // went in as a single write and took the session down with it ~25% of
4243
- // the time. Spending between slices is what puts the rest of the image
4244
- // on later turns; the slice bound is what keeps any one of them small.
4245
- let offset = 0;
4246
- do {
4247
- const slice = bytes.subarray(offset, offset + FACET_IMAGE_WRITE_SLICE_BYTES);
4248
- // The first slice REPLACES the file, so an interrupted write's remains
4249
- // are truncated to a known length rather than left as a tail past this
4250
- // content — and the mode is chosen here, at creation, as POSIX has it.
4251
- if (offset === 0)
4252
- fs.writeFile(stored, slice, { mode: 0o644 });
4253
- else
4254
- fs.writeRange(stored, offset, slice);
4255
- offset += slice.byteLength;
4256
- await pacer.spend(slice.byteLength);
4257
- } while (offset < bytes.byteLength);
4258
- }
4259
- this._sweepFacetImages(fs);
4260
- return images;
4261
- }
4262
4202
  /**
4263
4203
  * Fail a paced launch whose process went away while it was suspended.
4264
4204
  *
4265
- * Between turns anything may happen to the process — a kill, a reap, a
4266
- * `_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
4267
4207
  * spend further turns building a facet for a pid nothing will ever attach
4268
4208
  * to, and would write image files the next sweep immediately collects.
4269
4209
  */
@@ -4272,44 +4212,6 @@ export class FacetManager {
4272
4212
  return;
4273
4213
  throw new Error(`Nimbus: resident launch for pid ${pid} was cancelled while it was suspended`);
4274
4214
  }
4275
- /**
4276
- * Drop every image no running process boots from.
4277
- *
4278
- * Content addressing means a changed program writes a NEW image rather than
4279
- * replacing one, so a watch loop — or simply a session that runs a few
4280
- * different programs — would otherwise leave one bundle-sized file behind
4281
- * per distinct version. The root set is the process table, which is exact:
4282
- * an image is live for precisely as long as the process that boots from it.
4283
- * Nothing is left for a TTL or an eviction heuristic to guess at, and after
4284
- * a DO reset the table is empty so every orphan goes.
4285
- */
4286
- _sweepFacetImages(fs) {
4287
- const live = new Set();
4288
- for (const [pid, paths] of this.residentImages) {
4289
- if (this.processes.get(pid)?.state === 'running') {
4290
- for (const path of paths)
4291
- live.add(path);
4292
- }
4293
- else {
4294
- this.residentImages.delete(pid);
4295
- }
4296
- }
4297
- let entries;
4298
- try {
4299
- entries = fs.readdir(FACET_IMAGE_DIR);
4300
- }
4301
- catch {
4302
- return;
4303
- }
4304
- for (const entry of entries) {
4305
- if (live.has(`/${FACET_IMAGE_DIR}/${entry.name}`))
4306
- continue;
4307
- try {
4308
- fs.unlink(`${FACET_IMAGE_DIR}/${entry.name}`);
4309
- }
4310
- catch { /* already gone */ }
4311
- }
4312
- }
4313
4215
  /**
4314
4216
  * The one way this manager boots a resident process. Every resident process
4315
4217
  * is a facet of this session; there is nothing to place and nothing here
@@ -4340,125 +4242,16 @@ export class FacetManager {
4340
4242
  this.vfs?.activateAppendWriter(pid, writerId);
4341
4243
  }
4342
4244
  /**
4343
- * How a paced launch asks for a fresh turn.
4344
- *
4345
- * The host grants one by calling `pumpResidentLaunches` from a context that
4346
- * is genuinely a new invocation — the session's alarm. Without such a host
4347
- * there is no fresh turn to be had, and the launch continues on this one
4348
- * rather than hanging: that is exactly the single-turn launch this path has
4349
- * always performed, so a harness or a runtime without alarms loses the
4350
- * responsiveness but keeps the behaviour.
4351
- */
4352
- launchScheduler = {
4353
- nextTurn: (chunkEnded) => new Promise((resume) => {
4354
- this.launchWaiters.push({ resume, chunkEnded });
4355
- if (this.hooks.requestLaunchTurn) {
4356
- this.hooks.requestLaunchTurn();
4357
- return;
4358
- }
4359
- setTimeout(() => { void this.pumpResidentLaunches(); }, 0);
4360
- }),
4361
- };
4362
- /**
4363
- * Run one chunk of every launch waiting for a turn.
4364
- *
4365
- * Awaits the chunk each resumed launch then performs, so the invocation that
4366
- * granted the turn is the invocation that pays for the work — rather than
4367
- * releasing it into a handler's microtask drain, where nothing owns it and
4368
- * the runtime may tear the context down mid-chunk.
4369
- */
4370
- async pumpResidentLaunches() {
4371
- await this._recoverInterruptedLaunches();
4372
- const waiting = this.launchWaiters;
4373
- if (waiting.length === 0)
4374
- return;
4375
- this.launchWaiters = [];
4376
- for (const waiter of waiting)
4377
- waiter.resume();
4378
- await Promise.all(waiting.map((waiter) => waiter.chunkEnded));
4379
- }
4380
- /**
4381
- * Re-drive the launches a previous instance was building when it was reset.
4382
- *
4383
- * The platform resets a session Durable Object over what one turn has
4384
- * outstanding in storage ("Internal error in Durable Object storage caused
4385
- * object to be reset"), and a launch is the largest writer this session has.
4386
- * Everything the launch held was in memory, so the process it was building
4387
- * and the terminal watching it both went with the instance, and until now
4388
- * the user was told nothing at all.
4389
- *
4390
- * Sited on the pump because the pump is what an alarm calls, and a launch
4391
- * that was suspended has an alarm armed for it — a reset during a chunk
4392
- * fails that alarm, and the platform re-delivers it to the instance that
4393
- * replaces this one. So the first turn after a reset is already this one.
4394
- *
4395
- * Runs once per instance: the journal only changes when a launch of THIS
4396
- * instance starts or settles, and those are rows this instance wrote.
4397
- */
4398
- async _recoverInterruptedLaunches() {
4399
- if (this.launchesRecovered)
4400
- return;
4401
- this.launchesRecovered = true;
4402
- const journal = await this.ctx.storage.list({
4403
- prefix: RESIDENT_LAUNCH_KEY_PREFIX,
4404
- });
4405
- for (const [key, record] of journal) {
4406
- // A pid at or below this instance's base was allocated by a PREVIOUS one
4407
- // (process-table.ts, PID_GEN_STRIDE), so its launch never finished; above
4408
- // the base is this instance's own, still running. Same predicate as
4409
- // `session/rpc.ts` uses to attribute a prior generation's pid.
4410
- if (!(record.pid > 0 && record.pid <= this.processes.pidBase))
4411
- continue;
4412
- await this.ctx.storage.delete(key);
4413
- // 'running' is the measured common case: the platform's reset strikes
4414
- // seconds after a launch settles, while the resident runs.
4415
- const doing = record.phase === 'running' ? 'running' : 'starting';
4416
- if (record.attempt >= RESIDENT_LAUNCH_MAX_ATTEMPT) {
4417
- this.hooks.notify?.('\x1b[2m[nimbus: the session restarted again while '
4418
- + `"${record.command}" was ${doing} — leaving it stopped]\x1b[0m\r\n`);
4419
- continue;
4420
- }
4421
- this.hooks.notify?.('\x1b[2m[nimbus: the session restarted while '
4422
- + `"${record.command}" was ${doing} — restarting it]\x1b[0m\r\n`);
4423
- // Not awaited: this call is running inside the alarm that granted the
4424
- // turn, and the launch it starts asks for turns of its own through that
4425
- // same alarm — awaiting it here would be waiting on an alarm that cannot
4426
- // be scheduled until this one returns.
4427
- this.ctx.waitUntil(this._spawnResident(record.code, record.opts, record.attempt + 1)
4428
- .catch((e) => {
4429
- this.hooks.notify?.(`\x1b[2m[nimbus: "${record.command}" could not be restarted: `
4430
- + `${errorMessage(e)}]\x1b[0m\r\n`);
4431
- }));
4432
- }
4433
- }
4434
- /**
4435
- * Record a launch as in flight, so an instance that replaces this one knows
4436
- * it never finished. Best-effort: a launch that cannot be journalled still
4437
- * runs, and a reset then costs exactly what it cost before the journal.
4438
- *
4439
- * Synced, not merely put: `await put()` resolves before durability, and the
4440
- * reset this journal exists for destroys every write its turn still had
4441
- * outstanding — measured live, a launch killed in its first chunks left NO
4442
- * row for the replacement instance to find, which is how the recovery this
4443
- * feeds sat inert while its own test stayed green. `sync()` is the storage
4444
- * layer's durability barrier: the row is on disk before the launch performs
4445
- * its first byte of real work. What remains is a reset between the put and
4446
- * the sync's completion — and a launch that dies there has not started, so
4447
- * losing its row costs a retype, not a recovery.
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.
4448
4248
  */
4449
- async _journalLaunch(record) {
4450
- try {
4451
- this.journalledPids.add(record.pid);
4452
- await this.ctx.storage.put(`${RESIDENT_LAUNCH_KEY_PREFIX}${record.pid}`, record);
4453
- await this.ctx.storage.sync();
4454
- }
4455
- catch (e) {
4456
- console.warn('[nimbus] resident launch journal write failed:', errorMessage(e));
4457
- }
4249
+ pumpResidentLaunches() {
4250
+ return this.launchPump.pump();
4458
4251
  }
4459
4252
  /** Whether any launch is suspended waiting for a turn. */
4460
4253
  get hasPendingLaunchTurns() {
4461
- return this.launchWaiters.length > 0;
4254
+ return this.launchPump.hasPending;
4462
4255
  }
4463
4256
  /** Allocate a free loopback port for a resident server facet (from 4096 up). */
4464
4257
  _allocateLoopbackPort() {
@@ -4695,7 +4488,7 @@ export class FacetManager {
4695
4488
  */
4696
4489
  async _runResidentLaunch(entry, code, command, opts, attempt) {
4697
4490
  const cwd = opts.cwd || '/home/user';
4698
- const pacer = new LaunchPacer(this.launchScheduler, launchChunkMaxBytes(this.env), () => this._assertLaunchStillOwned(entry.pid));
4491
+ const pacer = new LaunchPacer(this.launchPump, launchChunkMaxBytes(this.env), () => this._assertLaunchStillOwned(entry.pid));
4699
4492
  // Journalled before the first byte of work. A launch that FAILS deletes
4700
4493
  // its row on the way out — its process has already been exited and the
4701
4494
  // user notified, so there is nothing left to owe. A launch that SETTLES
@@ -4713,12 +4506,12 @@ export class FacetManager {
4713
4506
  attempt,
4714
4507
  phase: 'starting',
4715
4508
  };
4716
- await this._journalLaunch(record);
4509
+ await this.launchJournal.journal(record);
4717
4510
  try {
4718
4511
  await this._residentLaunchBody(entry, code, command, cwd, opts, pacer);
4719
4512
  }
4720
4513
  catch (e) {
4721
- await this._releaseResidentJournal(entry.pid);
4514
+ await this.launchJournal.release(entry.pid);
4722
4515
  throw e;
4723
4516
  }
4724
4517
  finally {
@@ -4728,24 +4521,8 @@ export class FacetManager {
4728
4521
  // its running life with a fresh re-drive budget. If the process already
4729
4522
  // ended inside the launch body's own settlement, the terminal hook has
4730
4523
  // released the row — do not write it back.
4731
- if (this.journalledPids.has(entry.pid)) {
4732
- await this._journalLaunch({ ...record, attempt: 0, phase: 'running' });
4733
- }
4734
- }
4735
- /**
4736
- * The journal row's one release: the process is over, nothing is owed.
4737
- * Synced so an instance reset moments later cannot roll the delete back and
4738
- * resurrect a process the user watched end.
4739
- */
4740
- async _releaseResidentJournal(pid) {
4741
- if (!this.journalledPids.delete(pid))
4742
- return;
4743
- try {
4744
- await this.ctx.storage.delete(`${RESIDENT_LAUNCH_KEY_PREFIX}${pid}`);
4745
- await this.ctx.storage.sync();
4746
- }
4747
- catch (e) {
4748
- console.warn('[nimbus] resident launch journal delete failed:', errorMessage(e));
4524
+ if (this.launchJournal.has(entry.pid)) {
4525
+ await this.launchJournal.journal({ ...record, attempt: 0, phase: 'running' });
4749
4526
  }
4750
4527
  }
4751
4528
  async _residentLaunchBody(entry, code, command, cwd, opts, pacer) {
@@ -4758,7 +4535,7 @@ export class FacetManager {
4758
4535
  }
4759
4536
  }
4760
4537
  if (this.vfs)
4761
- this._ensureImageStoreDir(this.vfs);
4538
+ this.imageStore.ensureDir();
4762
4539
  const diagOn = isExecDiagEnabled();
4763
4540
  const __bundleStart = diagOn ? Date.now() : 0;
4764
4541
  const processVfs = this.vfs?.as(entry.cred);
@@ -4803,7 +4580,7 @@ export class FacetManager {
4803
4580
  let handle;
4804
4581
  let resourcesTracked = false;
4805
4582
  try {
4806
- const vfsTextModules = await this._materializeFacetImages(entry.pid, {
4583
+ const vfsTextModules = await this.imageStore.materialize(entry.pid, {
4807
4584
  'worker.js': generatedWorker.code,
4808
4585
  ...generatedWorker.modules,
4809
4586
  }, pacer);
@@ -4893,6 +4670,9 @@ export class FacetManager {
4893
4670
  await handle.booted();
4894
4671
  }
4895
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);
4896
4676
  this.portRegistry.register(opts.port, entry.pid);
4897
4677
  }
4898
4678
  }
@@ -4960,6 +4740,9 @@ export class FacetManager {
4960
4740
  resourcesTracked = true;
4961
4741
  this.portRegistry.bindFacetStub(entry.pid, handle.routeTarget);
4962
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);
4963
4746
  this.portRegistry.register(opts.port, entry.pid);
4964
4747
  }
4965
4748
  return { pid: entry.pid, boot: await handle.booted() };
@@ -4980,8 +4763,9 @@ export class FacetManager {
4980
4763
  throw e;
4981
4764
  }
4982
4765
  }
4983
- registerPort(pid, port) {
4766
+ async registerPort(pid, port) {
4984
4767
  if (port > 0 && port < 65536) {
4768
+ await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, port);
4985
4769
  this.portRegistry.register(port, pid);
4986
4770
  }
4987
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