@nimbus-sh/worker 0.7.0 → 0.9.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 (102) hide show
  1. package/dist/facets/compose.d.ts +3 -0
  2. package/dist/facets/compose.d.ts.map +1 -1
  3. package/dist/facets/compose.js +1 -1
  4. package/dist/facets/manager.d.ts +26 -15
  5. package/dist/facets/manager.d.ts.map +1 -1
  6. package/dist/facets/manager.js +154 -112
  7. package/dist/facets/real-vite-hmr.d.ts +3 -1
  8. package/dist/facets/real-vite-hmr.d.ts.map +1 -1
  9. package/dist/facets/real-vite-hmr.js +18 -8
  10. package/dist/facets/ws-terminal.d.ts +9 -31
  11. package/dist/facets/ws-terminal.d.ts.map +1 -1
  12. package/dist/facets/ws-terminal.js +58 -51
  13. package/dist/hosted/commands.d.ts +12 -0
  14. package/dist/hosted/commands.d.ts.map +1 -0
  15. package/dist/hosted/commands.js +1647 -0
  16. package/dist/hosted/runtime.d.ts +135 -0
  17. package/dist/hosted/runtime.d.ts.map +1 -0
  18. package/dist/hosted/runtime.js +344 -0
  19. package/dist/hosted/services.d.ts +44 -0
  20. package/dist/hosted/services.d.ts.map +1 -0
  21. package/dist/hosted/services.js +522 -0
  22. package/dist/loaders/generated-workers.d.ts +1 -1
  23. package/dist/loaders/generated-workers.d.ts.map +1 -1
  24. package/dist/loaders/generated-workers.js +2 -2
  25. package/dist/node-shims-artifact.generated.js +3 -3
  26. package/dist/runtime/bash-repl.d.ts +6 -4
  27. package/dist/runtime/bash-repl.d.ts.map +1 -1
  28. package/dist/runtime/bash-repl.js +46 -7
  29. package/dist/runtime/bun-runner.js +1 -1
  30. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  31. package/dist/runtime/facet-loader-host.js +1 -3
  32. package/dist/runtime/node-runner.d.ts +1 -1
  33. package/dist/runtime/node-runner.d.ts.map +1 -1
  34. package/dist/runtime/node-runner.js +2 -2
  35. package/dist/runtime/node-shims.d.ts.map +1 -1
  36. package/dist/runtime/node-shims.js +14 -1
  37. package/dist/runtime/opencode-facet-runner.js +14 -14
  38. package/dist/runtime/package-manager.d.ts +35 -122
  39. package/dist/runtime/package-manager.d.ts.map +1 -1
  40. package/dist/runtime/package-manager.js +61 -596
  41. package/dist/runtime/process-logs-api.d.ts +1 -1
  42. package/dist/runtime/process-logs-api.d.ts.map +1 -1
  43. package/dist/runtime/process-logs-api.js +1 -1
  44. package/dist/runtime/python-repl.d.ts +6 -4
  45. package/dist/runtime/python-repl.d.ts.map +1 -1
  46. package/dist/runtime/python-repl.js +92 -67
  47. package/dist/runtime/repl-session.d.ts +9 -47
  48. package/dist/runtime/repl-session.d.ts.map +1 -1
  49. package/dist/runtime/repl-session.js +105 -105
  50. package/dist/runtime/ruby-repl.d.ts +3 -2
  51. package/dist/runtime/ruby-repl.d.ts.map +1 -1
  52. package/dist/runtime/ruby-repl.js +7 -6
  53. package/dist/runtime/runtime-catalog.d.ts +13 -0
  54. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  55. package/dist/runtime/runtime-catalog.js +88 -0
  56. package/dist/runtime/wasi/preamble.d.ts.map +1 -1
  57. package/dist/runtime/wasi/preamble.js +74 -56
  58. package/dist/runtime-catalog.generated.js +1 -1
  59. package/dist/session/agent.d.ts +1 -21
  60. package/dist/session/agent.d.ts.map +1 -1
  61. package/dist/session/ai.d.ts +5 -1
  62. package/dist/session/ai.d.ts.map +1 -1
  63. package/dist/session/ai.js +7 -0
  64. package/dist/session/hibernation.d.ts +2 -1
  65. package/dist/session/hibernation.d.ts.map +1 -1
  66. package/dist/session/hibernation.js +7 -4
  67. package/dist/session/init.d.ts +1 -36
  68. package/dist/session/init.d.ts.map +1 -1
  69. package/dist/session/init.js +18 -1773
  70. package/dist/session/loopback.d.ts +2 -29
  71. package/dist/session/loopback.d.ts.map +1 -1
  72. package/dist/session/loopback.js +3 -27
  73. package/dist/session/nimbus-session.d.ts +28 -34
  74. package/dist/session/nimbus-session.d.ts.map +1 -1
  75. package/dist/session/nimbus-session.js +78 -518
  76. package/dist/session/npm-install-port.d.ts +3 -1
  77. package/dist/session/npm-install-port.d.ts.map +1 -1
  78. package/dist/session/port-capability.d.ts.map +1 -1
  79. package/dist/session/port-capability.js +11 -1
  80. package/dist/session/programmatic.d.ts +5 -38
  81. package/dist/session/programmatic.d.ts.map +1 -1
  82. package/dist/session/programmatic.js +17 -49
  83. package/dist/session/rpc.d.ts +3 -43
  84. package/dist/session/rpc.d.ts.map +1 -1
  85. package/dist/session/rpc.js +8 -67
  86. package/dist/session/supervisor-op.d.ts +10 -5
  87. package/dist/session/supervisor-op.d.ts.map +1 -1
  88. package/dist/session/supervisor-op.js +21 -48
  89. package/dist/session/supervisor-rpc.d.ts +33 -16
  90. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  91. package/dist/session/supervisor-rpc.js +49 -4
  92. package/dist/vfs/facet-resident-store.d.ts.map +1 -1
  93. package/dist/vfs/facet-resident-store.js +4 -2
  94. package/dist/workspace-host.d.ts +7 -0
  95. package/dist/workspace-host.d.ts.map +1 -0
  96. package/dist/workspace-host.js +5 -0
  97. package/dist/workspace.d.ts +2 -0
  98. package/dist/workspace.d.ts.map +1 -0
  99. package/dist/workspace.js +1 -0
  100. package/package.json +17 -12
  101. package/public/_assets/runtime/{node-shims-8f8c64edd2a1f0f5.js → node-shims-acaa037e6d26ec0d.js} +14 -1
  102. package/scripts/bundle-runtime.mjs +35 -8
@@ -1,22 +1,11 @@
1
- /**
2
- * nimbus-session.ts — NimbusSession Durable Object (v2.0).
3
- *
4
- * The supervisor DO that owns the VFS, shell, and all commands.
5
- * `node` execution is delegated to dynamic workers via LOADER.load().
6
- * IPC between facets and the supervisor flows through SupervisorRPC.
7
- */
8
- import { staticStdinReader } from '@nimbus-sh/core/shell/stdin-adapter.js';
1
+ import * as runtimeServices from '../hosted/services.js';
9
2
  import { DurableObject as CloudflareDurableObject } from 'cloudflare:workers';
10
3
  import { SqliteVFS } from '@nimbus-sh/core/vfs/sqlite-vfs.js';
11
- import { composeFacetManager } from '../facets/compose.js';
12
- import { FacetProcessManager, textBytes } from '../facets/process.js';
13
- import { ChildProcessSpawnPool } from '../loaders/child-process/spawn-pool.js';
14
4
  import { SessionProcessSupervisor } from '@nimbus-sh/core/runtime/session-process-supervisor.js';
5
+ import { SqliteFilesystemAuthority } from '@nimbus-sh/core/runtime/filesystem-authority.js';
15
6
  import { PID_GEN_STRIDE } from '@nimbus-sh/core/runtime/process-table.js';
16
- import { CRED_KERNEL, CRED_SESSION_USER, } from '@nimbus-sh/core/runtime/os-contracts.js';
7
+ import { CRED_KERNEL, CRED_SESSION_USER } from '@nimbus-sh/core/runtime/os-contracts.js';
17
8
  import { PortRegistry } from '@nimbus-sh/core/runtime/port-registry.js';
18
- import { EsbuildBundlePool } from '../facets/esbuild-bundle-pool.js';
19
- import { EsbuildService } from '@nimbus-sh/core/runtime/esbuild-service.js';
20
9
  import { registerAllocObserver } from '@nimbus-sh/platform/heavy-alloc-coord.js';
21
10
  // S10: oom-discriminator helpers (recordFailure, getFailures,
22
11
  // getLastRpcFrame, getLastFacetId, snapshotForStorage, rehydrateFromStorage)
@@ -30,58 +19,63 @@ import { registerAllocObserver } from '@nimbus-sh/platform/heavy-alloc-coord.js'
30
19
  // longer references them directly. Phase 2 A'.5 renamed the function
31
20
  // (was getEsbuildWasmBytes; cached) to fetchEsbuildWasmBytes (no
32
21
  // supervisor cache; goes through env.ASSETS on demand).
22
+ // S10: oom-discriminator helpers (recordFailure, getFailures,
23
+ // getLastRpcFrame, getLastFacetId, snapshotForStorage, rehydrateFromStorage)
24
+ // moved to sibling modules (-rpc uses recordFailure for _reportExternalExit;
25
+ // -ws uses recordFailure for webSocketError; -diag uses getFailures +
26
+ // snapshotForStorage + rehydrateFromStorage; -routes uses getFailures +
27
+ // getLastRpcFrame + getLastFacetId for /api/_diag/memory). Class file
28
+ // no longer references any of them directly.
29
+ // S10: classifyError, LRU_MAX_ENTRIES, fetchEsbuildWasmBytes moved to
30
+ // sibling modules (-rpc, -routes, esbuild-wasm-bytes); class file no
31
+ // longer references them directly. Phase 2 A'.5 renamed the function
32
+ // (was getEsbuildWasmBytes; cached) to fetchEsbuildWasmBytes (no
33
+ // supervisor cache; goes through env.ASSETS on demand).
33
34
  import { adoptCtxExports } from '@nimbus-sh/fabric/composition.js';
34
- import { NIMBUS_VERSION, DEFAULT_MOUNT_POINTS, CF_COMPAT_DATE } from '@nimbus-sh/core/constants.js';
35
+ import { NIMBUS_VERSION, DEFAULT_MOUNT_POINTS } from '@nimbus-sh/core/constants.js';
35
36
  import { seedBaseFilesystem } from '@nimbus-sh/core/workspace';
37
+ import { routeSessionLoopback } from './ai.js';
36
38
  import { seedProject, SEED_PROJECT_NAME } from '@nimbus-sh/core/vfs/seed-project.js';
37
39
  import { BASE_PATH_HEADER } from '../_shared/session-router.js';
38
40
  import { ATTACH_BOOTSTRAP_JTI_KEY_PREFIX, SESSION_DESTROYED_KEY } from './keys.js';
39
41
  import { appendScrollback } from './state-store.js';
40
- import { notifyTerminalEvent, wireProcessLogSocketBroadcast } from '../runtime/process-logs-api.js';
42
+ import { wireProcessLogSocketBroadcast } from '../runtime/process-logs-api.js';
43
+ // S3: tryEnableReplicas + getReplicaState extracted to ./nimbus-session-replica.ts.
41
44
  // S3: tryEnableReplicas + getReplicaState extracted to ./nimbus-session-replica.ts.
42
- import { wireReplicasOnConstruct as _w12WireReplicasOnConstruct, getReplicaState as _w12GetReplicaState, } from './replica-routes.js';
45
+ import { wireReplicasOnConstruct as _w12WireReplicasOnConstruct, getReplicaState as _w12GetReplicaState } from './replica-routes.js';
46
+ // S5: storage-key constants moved to ./nimbus-session-keys.ts; consumed by
47
+ // sibling modules (-hib, -diag, -ws). The class file itself no longer
48
+ // references any storage key directly.
49
+ // S4: W9 hibernation surface extracted.
43
50
  // S5: storage-key constants moved to ./nimbus-session-keys.ts; consumed by
44
51
  // sibling modules (-hib, -diag, -ws). The class file itself no longer
45
52
  // references any storage key directly.
46
53
  // S4: W9 hibernation surface extracted.
47
- import { wireHibernationOnConstruct as _w9WireHibernationOnConstruct, wireProcessLogPersist as _w9DoWireProcessLogPersist, ensureHibSchema as _w9DoEnsureHibSchema, scheduleHibFlush as _w9DoScheduleHibFlush, clearDestroyedTombstone as _w1ClearDestroyedTombstone, dispatchAlarm as _w9DoDispatchAlarm, flushOnClose as _w9DoFlushOnClose, } from './hibernation.js';
54
+ import { wireHibernationOnConstruct as _w9WireHibernationOnConstruct, wireProcessLogPersist as _w9DoWireProcessLogPersist, ensureHibSchema as _w9DoEnsureHibSchema, scheduleHibFlush as _w9DoScheduleHibFlush, clearDestroyedTombstone as _w1ClearDestroyedTombstone, dispatchAlarm as _w9DoDispatchAlarm, flushOnClose as _w9DoFlushOnClose } from './hibernation.js';
48
55
  import { timers } from '@nimbus-sh/fabric/timers.js';
49
56
  import { adoptGeneration, generation } from '@nimbus-sh/fabric/generation.js';
50
57
  // S6: initSession (1875 LOC of cmd registrations + boot wiring) extracted.
58
+ // S6: initSession (1875 LOC of cmd registrations + boot wiring) extracted.
51
59
  import { initSession as _w11InitSession } from './init.js';
52
60
  // S7: webSocket lifecycle (message, close, error, F1 discriminator,
53
61
  // _w5SafePersistRing) extracted.
54
- import { wsMessage as _wsDoMessage, wsClose as _wsDoClose, wsError as _wsDoError, safePersistRing as _wsDoSafePersistRing, } from './ws.js';
62
+ // S7: webSocket lifecycle (message, close, error, F1 discriminator,
63
+ // _w5SafePersistRing) extracted.
64
+ import { wsMessage as _wsDoMessage, wsClose as _wsDoClose, wsError as _wsDoError, safePersistRing as _wsDoSafePersistRing } from './ws.js';
65
+ // S8: Supervisor RPC + W8 cp* + legacy VFS impls extracted.
55
66
  // S8: Supervisor RPC + W8 cp* + legacy VFS impls extracted.
56
67
  import * as _rpc from './rpc.js';
57
68
  import { buildSessionSupervisorOps } from './supervisor-op.js';
58
- // The supervisor terminates a facet's outbound sockets so inbound frames
59
- // arrive as supervisor replies (VFS coherence witness 3).
60
- import { WebSocketRelay } from './ws-relay.js';
69
+ // S9: HTTP fetch routing extracted (combined S9a + S9b).
61
70
  // S9: HTTP fetch routing extracted (combined S9a + S9b).
62
71
  import * as _routes from './routes.js';
63
72
  import * as _portCapability from './port-capability.js';
64
73
  // Programmatic SDK RPC surface.
74
+ // Programmatic SDK RPC surface.
65
75
  import * as _programmatic from './programmatic.js';
66
76
  // S10: heap probe + W5 OOM-ring persistence extracted.
77
+ // S10: heap probe + W5 OOM-ring persistence extracted.
67
78
  import * as _diag from './diag.js';
68
- // ── Pure helpers extracted to ./nimbus-session-helpers.ts (S1) ────────
69
- //
70
- // renderNoDevServerHtml, BUNDLER_BIN_PREFIXES, NIMBUS_UNSUPPORTED_BINS,
71
- // WRANGLER_IGNORED_FLAGS{,_WITH_VALUE}, WRANGLER_UNSUPPORTED_CONFIG_FIELDS,
72
- // filterWranglerFlags, detectUnsupportedWranglerConfig, _CP_FACET_DIRECT,
73
- // _CP_PURE_BUILTIN, _classifyCommand, detectBundlerBin, checkNodeModulesGuard
74
- // all live in the helpers module now.
75
- //
76
- // They are imported here (so call sites in this file work unchanged) and
77
- // re-exported (so external callers importing them from
78
- // `./nimbus-session.js` keep working — back-compat).
79
- //
80
- // (esbuild wasm bytes are fetched from env.ASSETS by
81
- // src/esbuild-wasm-bytes.ts at pool-construction time; A'.5 dropped
82
- // the supervisor-resident cache + the SUPERVISOR.getEsbuildWasm RPC.)
83
- // Helpers needed by this class file's own logic (not just re-export).
84
- import { _classifyCommand, } from './helpers.js';
85
79
  import { z } from 'zod/v4';
86
80
  const CpFacetDirectPayloadSchema = z.object({
87
81
  command: z.unknown().optional().transform((value) => value == null ? '' : String(value)),
@@ -241,7 +235,26 @@ The editor opens this file in Markdown preview mode by default. Use
241
235
  `;
242
236
  }
243
237
  export class NimbusSession extends CloudflareDurableObject {
238
+ #runtimeServices;
244
239
  // this.ctx and this.env are provided by the DurableObject base class
240
+ runtimeWorkspace = null;
241
+ runtimeReady = null;
242
+ get runtimeManager() {
243
+ if (!this.runtimeWorkspace)
244
+ throw new Error('Nimbus runtime is not initialized');
245
+ return this.runtimeWorkspace.runtimes;
246
+ }
247
+ async routeLoopback(port, request) {
248
+ return routeSessionLoopback({ ctx: this.ctx, env: this.env, portRegistry: this.portRegistry }, port, request);
249
+ }
250
+ async ensureRuntimeReady() {
251
+ if (this.shell)
252
+ return;
253
+ this.runtimeReady ??= this.initSession(null).then(() => {
254
+ this._b4Phase = 'drained';
255
+ }).finally(() => { this.runtimeReady = null; });
256
+ await this.runtimeReady;
257
+ }
245
258
  sqliteFs = null;
246
259
  kernel = null;
247
260
  shell = null;
@@ -414,7 +427,14 @@ export class NimbusSession extends CloudflareDurableObject {
414
427
  */
415
428
  wranglerAliasBannerShown = false;
416
429
  constructor(ctx, env) {
417
- super(ctx, env); // enables RPC on the DO
430
+ super(ctx, env);
431
+ this.#runtimeServices = runtimeServices.bindRuntimeServices(this, {
432
+ ctx,
433
+ env,
434
+ notify: (line) => this._notifySession(line),
435
+ requestLaunchTurn: async (at) => { await this._scheduleLaunchTurn(at); },
436
+ filesystem: () => this.getFilesystemAuthority(),
437
+ });
418
438
  // In `wrangler dev`, the outer Worker and this DO share a single
419
439
  // workerd process, so the `adoptCtxExports(ctx.exports)` call in the
420
440
  // outer fetch handler (src/index.ts) is visible here via the
@@ -647,6 +667,13 @@ export class NimbusSession extends CloudflareDurableObject {
647
667
  * Lazy: sqliteFs exists only after ensureSqliteFs().
648
668
  */
649
669
  _supervisorOps = null;
670
+ filesystemAuthority = null;
671
+ getFilesystemAuthority() {
672
+ this.ensureSqliteFs();
673
+ if (!this.sqliteFs)
674
+ throw new Error('Filesystem is not initialized');
675
+ return this.filesystemAuthority ??= new SqliteFilesystemAuthority(this.sqliteFs);
676
+ }
650
677
  supervisorOps() {
651
678
  if (!this._supervisorOps)
652
679
  this._supervisorOps = buildSessionSupervisorOps(this);
@@ -673,32 +700,16 @@ export class NimbusSession extends CloudflareDurableObject {
673
700
  }
674
701
  async _rpcStat(path, pid, cred) { return _rpc._rpcStat(this, path, pid, cred); }
675
702
  async _rpcLstat(path, pid, cred) { return _rpc._rpcLstat(this, path, pid, cred); }
676
- async _rpcHasLegacySymlinkUnder(path, pid) {
677
- return _rpc._rpcHasLegacySymlinkUnder(this, path, pid);
678
- }
679
- async _rpcUtimes(path, atimeMs, mtimeMs, pid) {
680
- return _rpc._rpcUtimes(this, path, atimeMs, mtimeMs, pid);
681
- }
682
703
  async _rpcChmod(path, mode, pid, cred) {
683
704
  return _rpc._rpcChmod(this, path, mode, pid, cred);
684
705
  }
685
- async _rpcAccess(path, mode, pid) {
686
- return _rpc._rpcAccess(this, path, mode, pid);
687
- }
688
- async _rpcChown(path, uid, gid, pid, options) {
689
- return _rpc._rpcChown(this, path, uid, gid, pid, options);
690
- }
691
706
  async _rpcSetUmask(mask, pid) {
692
707
  return _rpc._rpcSetUmask(this, mask, pid);
693
708
  }
694
709
  async _rpcReaddir(path, pid, cred) { return _rpc._rpcReaddir(this, path, pid, cred); }
695
710
  async _rpcExists(path, pid, cred) { return _rpc._rpcExists(this, path, pid, cred); }
696
711
  async _rpcMkdir(path, pid, cred) { return _rpc._rpcMkdir(this, path, pid, cred); }
697
- async _rpcRmdir(path, pid) { return _rpc._rpcRmdir(this, path, pid); }
698
712
  async _rpcRename(from, to, pid, cred) { return _rpc._rpcRename(this, from, to, pid, cred); }
699
- async _rpcReadlink(path, pid) { return _rpc._rpcReadlink(this, path, pid); }
700
- async _rpcSymlink(target, path, pid) { return _rpc._rpcSymlink(this, target, path, pid); }
701
- async _rpcFsRevision(path, pid) { return _rpc._rpcFsRevision(this, path, pid); }
702
713
  async _rpcFsAcquire(epoch, cursor, pid) {
703
714
  return _rpc._rpcFsAcquire(this, epoch, cursor, pid);
704
715
  }
@@ -717,20 +728,9 @@ export class NimbusSession extends CloudflareDurableObject {
717
728
  async _rpcWsClose(id, code, reason, pid) {
718
729
  return _rpc._rpcWsClose(this, id, code, reason, pid);
719
730
  }
720
- async _rpcFsOpen(path, flags, pid) { return _rpc._rpcFsOpen(this, path, flags, pid); }
721
- async _rpcFsRead(handleId, offset, length, pid) {
722
- return _rpc._rpcFsRead(this, handleId, offset, length, pid);
723
- }
724
- async _rpcFsWrite(handleId, offset, bytes, pid) {
725
- return _rpc._rpcFsWrite(this, handleId, offset, bytes, pid);
726
- }
727
- async _rpcFsClose(handleId, pid) { return _rpc._rpcFsClose(this, handleId, pid); }
728
731
  async _rpcFsReadRange(path, offset, length, pid, cred) {
729
732
  return _rpc._rpcFsReadRange(this, path, offset, length, pid, cred);
730
733
  }
731
- async _rpcFsReadRangeUncached(path, offset, length, pid) {
732
- return _rpc._rpcFsReadRangeUncached(this, path, offset, length, pid);
733
- }
734
734
  async _rpcFsReadBatch(requests, pid) {
735
735
  return _rpc._rpcFsReadBatch(this, requests, pid);
736
736
  }
@@ -743,13 +743,8 @@ export class NimbusSession extends CloudflareDurableObject {
743
743
  async _rpcFsAppendAck(writerId, moduleId, operationId, pid) {
744
744
  return _rpc._rpcFsAppendAck(this, writerId, moduleId, operationId, pid);
745
745
  }
746
- async _rpcFsTruncate(path, size, pid) { return _rpc._rpcFsTruncate(this, path, size, pid); }
747
746
  async _rpcHmrRelay(clientId, msg) { return _rpc._rpcHmrRelay(this, clientId, msg); }
748
- async _rpcUnlink(path, pid) { return _rpc._rpcUnlink(this, path, pid); }
749
747
  async _rpcWriteBatch(payload, pid) { return _rpc._rpcWriteBatch(this, payload, pid); }
750
- async _rpcWriteBatchStream(stream, mutationOwner, pid) {
751
- return _rpc._rpcWriteBatchStream(this, stream, mutationOwner, pid);
752
- }
753
748
  async _rpcPutRegistryEntries(entries) { return _rpc._rpcPutRegistryEntries(this, entries); }
754
749
  async _rpcRecordCacheStats(events) { return _rpc._rpcRecordCacheStats(this, events); }
755
750
  async _rpcStdout(pid, data) { return _rpc._rpcStdout(this, pid, data); }
@@ -760,7 +755,7 @@ export class NimbusSession extends CloudflareDurableObject {
760
755
  _emitShellExecDone(pid, cmd, code, durationMs) { return _rpc._emitShellExecDone(this, pid, cmd, code, durationMs); }
761
756
  _reportExternalExit(pid, code, reason) { return _rpc._reportExternalExit(this, pid, code, reason); }
762
757
  // Misc supervisor RPC
763
- async _rpcPrefetch(cwd, entryCode) { return _rpc._rpcPrefetch(this, cwd, entryCode); }
758
+ async _rpcPrefetch(cwd, entryCode) { return (await _rpc._rpcPrefetch(this, cwd, entryCode)); }
764
759
  async _rpcRegisterPort(pid, port) { return _rpc._rpcRegisterPort(this, pid, port); }
765
760
  async _rpcUnregisterPort(port) { return _rpc._rpcUnregisterPort(this, port); }
766
761
  async _rpcRouteLoopback(port, request) { return _rpc._rpcRouteLoopback(this, port, request); }
@@ -817,7 +812,7 @@ export class NimbusSession extends CloudflareDurableObject {
817
812
  }
818
813
  async _rpcInstallRuntime(spec, options) { return _programmatic.rpcInstallRuntime(this, spec, options); }
819
814
  async _rpcEnsureRuntimes(specs, options) { return _programmatic.rpcEnsureRuntimes(this, specs, options); }
820
- async _rpcListRuntimes() { return _programmatic.rpcListRuntimes(this); }
815
+ async _rpcListRuntimes() { return (await _programmatic.rpcListRuntimes(this)); }
821
816
  async _rpcListProcesses() { return _programmatic.rpcListProcesses(this); }
822
817
  async _rpcKillProcess(pid) { return _programmatic.rpcKillProcess(this, pid); }
823
818
  async _rpcWriteProcessInput(pid, data) { return _programmatic.rpcWriteProcessInput(this, pid, data); }
@@ -989,11 +984,7 @@ export class NimbusSession extends CloudflareDurableObject {
989
984
  return _diag.persistRing(this, this.ctx);
990
985
  }
991
986
  /** The session's esbuild facet pool provider; constructing it does no work. */
992
- ensureBundlePool() {
993
- if (!this.bundlePool)
994
- this.bundlePool = new EsbuildBundlePool(this.env, this.ctx);
995
- return this.bundlePool;
996
- }
987
+ ensureBundlePool() { return this.#runtimeServices.ensureBundlePool(); }
997
988
  /**
998
989
  * The session's FacetManager, composed through the one factory every host
999
990
  * uses (`facets/compose.ts`). What is wired here is only what is the
@@ -1002,295 +993,19 @@ export class NimbusSession extends CloudflareDurableObject {
1002
993
  * report that keeps the process table honest. The isolated esbuild
1003
994
  * transform and the durable image-store fallback are the factory's.
1004
995
  */
1005
- ensureFacetManager() {
1006
- if (!this.facetManagerComposed) {
1007
- // The manager is composed over the filesystem, so the filesystem comes
1008
- // first. Cheap and idempotent; every caller already stood it up or is
1009
- // about to.
1010
- this.ensureSqliteFs();
1011
- this.facetManagerComposed = composeFacetManager({
1012
- ctx: this.ctx,
1013
- env: this.env,
1014
- processes: this.processes,
1015
- portRegistry: this.portRegistry,
1016
- vfs: this.sqliteFs,
1017
- ...(this.esbuildService ? { esbuild: this.esbuildService } : {}),
1018
- hooks: {
1019
- onExternalExit: (pid, code, reason) => this._reportExternalExit(pid, code, reason),
1020
- requestLaunchTurn: (notBefore) => { void this._scheduleLaunchTurn(notBefore); },
1021
- notify: (line) => this._notifySession(line),
1022
- onSpawn: (pid, command, longRunning) => {
1023
- const attachedTty = this.processes.get(pid)?.attachedTty === true;
1024
- if (longRunning) {
1025
- try {
1026
- this.processes.openInput(pid);
1027
- }
1028
- catch { }
1029
- }
1030
- // Only surface long-running / user-visible spawns to keep
1031
- // the terminal uncluttered. Short `node <file>` evals also
1032
- // get a line because users want the pid for `logs`/`kill`.
1033
- if (!this.terminal)
1034
- return;
1035
- const label = longRunning ? 'started (long-running)' : 'started';
1036
- this.terminal.write(`\x1b[2m[facet ${label}: pid=${pid} cmd="${command}"]\x1b[0m\r\n`);
1037
- // Structured event so the tabs UI can auto-open a log tab
1038
- // for long-running processes (vite, wrangler dev, etc.).
1039
- notifyTerminalEvent(this.terminal, {
1040
- type: 'spawn', pid, command, longRunning, attachedTty,
1041
- });
1042
- },
1043
- },
1044
- });
1045
- }
1046
- const composed = this.facetManagerComposed;
1047
- // W3.5 Fix B: share the session's lazy esbuildService with the
1048
- // FacetManager so the bundle's ESM→CJS pre-pass doesn't pay
1049
- // wasm-init twice. The session may construct it after the manager
1050
- // exists, so the share is re-offered on every call; FacetManager
1051
- // otherwise lazy-creates its own on first exec — same wasm bytes,
1052
- // same ~10ms init cost, just paid once per surface.
1053
- if (this.esbuildService) {
1054
- composed.manager.setEsbuildService(this.esbuildService);
1055
- }
1056
- return composed;
1057
- }
996
+ ensureFacetManager() { return this.#runtimeServices.ensureFacetManager(); }
1058
997
  /**
1059
998
  * The supervisor-owned WebSocket relay. Lazy, because most sessions never
1060
999
  * open a socket and the sockets it holds are live objects that must not
1061
1000
  * outlive the session.
1062
1001
  */
1063
- _ensureWebSocketRelay() {
1064
- if (!this.webSocketRelay)
1065
- this.webSocketRelay = new WebSocketRelay();
1066
- return this.webSocketRelay;
1067
- }
1002
+ _ensureWebSocketRelay() { return this.#runtimeServices._ensureWebSocketRelay(); }
1068
1003
  /**
1069
1004
  * W8: lazily construct the FacetProcessManager when the first cp* RPC
1070
1005
  * arrives. Wired with adapters that bridge the Nimbus shell command
1071
1006
  * registry to the FacetProcessManager's CommandRegistryLike contract.
1072
1007
  */
1073
- _ensureFacetProcessManager() {
1074
- if (this.facetProcessManager)
1075
- return this.facetProcessManager;
1076
- this.ensureSqliteFs();
1077
- this.ensureFacetManager();
1078
- // FacetProcessManager is statically imported at top-of-file (W8).
1079
- // No lazy-import: workerd doesn't ship CJS require, and the dynamic
1080
- // import would be async — making _ensureFacetProcessManager async
1081
- // would force every cp* RPC entry point to also be async on the
1082
- // promise-resolution path, which is fine but uglier. Compile-time
1083
- // tree-shaking handles unused-when-no-cp-RPC paths.
1084
- // Adapter for FacetManagerLike — wraps the existing FacetManager.exec
1085
- // with a streaming surface. Phase 1 simplification: facet-direct
1086
- // commands are dispatched through the shell registry the same way
1087
- // shell.execute does, but with the per-PID hooks routed.
1088
- const facetMgrAdapter = {
1089
- execStream: async (codeJson, opts, hooks) => {
1090
- // codeJson is a payload from FacetProcessManager._dispatch facet-direct
1091
- // path: {command, args, env, cwd, stdin}. We dispatch through the
1092
- // existing shell registry by resolving the command and invoking
1093
- // it with synthesized output streams that route to hooks.
1094
- let payload;
1095
- try {
1096
- const parsed = CpFacetDirectPayloadSchema.safeParse(JSON.parse(codeJson));
1097
- if (!parsed.success) {
1098
- hooks.onStderr(textBytes('child_process: facet dispatch requires a broker-assigned process pid\n'));
1099
- return 1;
1100
- }
1101
- payload = parsed.data;
1102
- }
1103
- catch {
1104
- hooks.onStderr(textBytes('child_process: invalid facet dispatch payload\n'));
1105
- return 1;
1106
- }
1107
- const registry = this._cpRegistry;
1108
- if (!registry) {
1109
- hooks.onStderr(textBytes('child_process: command registry unavailable\n'));
1110
- return 127;
1111
- }
1112
- const commandName = normalizeCpCommandName(payload.command);
1113
- const cmd = await registry.resolve(commandName);
1114
- if (!cmd) {
1115
- hooks.onStderr(textBytes(`${payload.command}: command not found\n`));
1116
- return 127;
1117
- }
1118
- // Synthesize a CommandContext for the internal shell substrate.
1119
- const stdoutStream = { write: (d) => hooks.onStdout(textBytes(String(d))) };
1120
- const stderrStream = { write: (d) => hooks.onStderr(textBytes(String(d))) };
1121
- const ac = new AbortController();
1122
- const cred = this.processes.cred(payload.processPid);
1123
- const ctx = {
1124
- pid: payload.processPid,
1125
- cred,
1126
- args: payload.args || [],
1127
- env: payload.env || {},
1128
- cwd: payload.cwd || '/home/user',
1129
- vfs: this.sqliteFs.as(cred),
1130
- stdout: stdoutStream,
1131
- stderr: stderrStream,
1132
- signal: ac.signal,
1133
- // For commands that need stdin we pass a tiny adapter.
1134
- stdin: staticStdinReader(payload.stdin || ''),
1135
- setUmask: (mask) => { this.processes.setUmask(payload.processPid, mask); },
1136
- runAs: async (targetCred, argv) => {
1137
- if (argv.length === 0)
1138
- return 0;
1139
- const child = this.processes.spawn(argv.join(' '), argv, payload.cwd, { parentPid: payload.processPid, cred: targetCred });
1140
- let exitCode = 1;
1141
- try {
1142
- exitCode = await cmdRegistryAdapter.runPureBuiltin(child.pid, argv[0], argv.slice(1), payload.env, payload.cwd, payload.stdin, hooks);
1143
- return exitCode;
1144
- }
1145
- finally {
1146
- this.processes.exit(child.pid, exitCode);
1147
- }
1148
- },
1149
- __nimbusCaptureOutput: true,
1150
- };
1151
- try {
1152
- const code = await cmd(ctx);
1153
- return typeof code === 'number' ? code : 0;
1154
- }
1155
- catch (e) {
1156
- hooks.onStderr(textBytes(`${payload.command}: ${e?.message || String(e)}\n`));
1157
- return 1;
1158
- }
1159
- },
1160
- abort: (facetName) => {
1161
- // Best-effort: relay to ctx.facets.abort, mirroring FacetManager.kill.
1162
- try {
1163
- this.ctx.facets?.abort?.(facetName, new Error('SIGKILL'));
1164
- }
1165
- catch { }
1166
- return true;
1167
- },
1168
- };
1169
- // Adapter for CommandRegistryLike. The shared shell registry is
1170
- // attached to `this._cpRegistry` by the shell-init path (see
1171
- // construction near line 2058 — registry passed as ctor arg there).
1172
- const cmdRegistryAdapter = {
1173
- // Consult the live shell registry FIRST so dynamically-registered
1174
- // commands (registerUnixCommands / git / npm / wrangler etc.) are seen
1175
- // even if they're not in the static _CP_PURE_BUILTIN allow-list. Falls
1176
- // back to the static facet-direct table for known facet-only commands.
1177
- // Returns null
1178
- // (→ exit 127) for everything unknown.
1179
- resolve: (name) => {
1180
- const commandName = normalizeCpCommandName(name);
1181
- const registry = this._cpRegistry;
1182
- if (registry && typeof registry.has === 'function' && registry.has(commandName)) {
1183
- // Registered — classify by name. Reuse the static table so
1184
- // facet-direct commands (node/npm/git/...) keep their kind
1185
- // even when they ALSO happen to be registry entries.
1186
- return _classifyCommand(commandName) || { kind: 'pure-builtin' };
1187
- }
1188
- return _classifyCommand(commandName);
1189
- },
1190
- runPureBuiltin: async (pid, name, args, env, cwd, stdin, hooks) => {
1191
- const registry = this._cpRegistry;
1192
- if (!registry) {
1193
- hooks.onStderr(textBytes('cp: registry unavailable\n'));
1194
- return 127;
1195
- }
1196
- const commandName = normalizeCpCommandName(name);
1197
- const cmd = await registry.resolve(commandName);
1198
- if (!cmd) {
1199
- hooks.onStderr(textBytes(`${name}: command not found\n`));
1200
- return 127;
1201
- }
1202
- const cred = this.processes.cred(pid);
1203
- const ac = new AbortController();
1204
- const ctx = {
1205
- pid,
1206
- cred,
1207
- args, env, cwd,
1208
- vfs: this.sqliteFs.as(cred),
1209
- stdout: { write: (d) => hooks.onStdout(textBytes(String(d))) },
1210
- stderr: { write: (d) => hooks.onStderr(textBytes(String(d))) },
1211
- signal: ac.signal,
1212
- stdin: staticStdinReader(stdin),
1213
- setUmask: (mask) => { this.processes.setUmask(pid, mask); },
1214
- runAs: async (targetCred, argv) => {
1215
- if (argv.length === 0)
1216
- return 0;
1217
- const child = this.processes.spawn(argv.join(' '), argv, cwd, { parentPid: pid, cred: targetCred });
1218
- let exitCode = 1;
1219
- try {
1220
- exitCode = await cmdRegistryAdapter.runPureBuiltin(child.pid, argv[0], argv.slice(1), env, cwd, stdin, hooks);
1221
- return exitCode;
1222
- }
1223
- finally {
1224
- this.processes.exit(child.pid, exitCode);
1225
- }
1226
- },
1227
- };
1228
- try {
1229
- const code = await cmd(ctx);
1230
- return typeof code === 'number' ? code : 0;
1231
- }
1232
- catch (e) {
1233
- hooks.onStderr(textBytes(`${name}: ${e?.message || String(e)}\n`));
1234
- return 1;
1235
- }
1236
- },
1237
- };
1238
- // Construct the child-process Loader pool when the binding is available.
1239
- // Unit-test hosts without LOADER continue through direct dispatch.
1240
- let spawnPool;
1241
- try {
1242
- const envAny = this.env;
1243
- if (envAny?.LOADER && typeof envAny.LOADER.get === 'function') {
1244
- spawnPool = new ChildProcessSpawnPool(this.env, this.ctx);
1245
- }
1246
- }
1247
- catch {
1248
- spawnPool = undefined;
1249
- }
1250
- this.facetProcessManager = new FacetProcessManager({
1251
- facetMgr: facetMgrAdapter,
1252
- processes: this.processes,
1253
- vfsForProcess: (pid) => this.sqliteFs.as(this.processes.cred(pid)),
1254
- commandRegistry: cmdRegistryAdapter,
1255
- shellExecutor: {
1256
- execute: async (pid, commandLine, env, cwd, stdin, hooks) => {
1257
- if (!this.shell) {
1258
- hooks.onStderr(textBytes('sh: shell unavailable\n'));
1259
- return 127;
1260
- }
1261
- const cred = this.processes.cred(pid);
1262
- const setUmask = (mask) => { this.processes.setUmask(pid, mask); };
1263
- const runAs = async (_parent, targetCred, argv) => {
1264
- if (argv.length === 0)
1265
- return 0;
1266
- const child = this.processes.spawn(argv.join(' '), argv, cwd, { parentPid: pid, cred: targetCred });
1267
- let exitCode = 1;
1268
- try {
1269
- exitCode = await cmdRegistryAdapter.runPureBuiltin(child.pid, argv[0], argv.slice(1), env, cwd, stdin, hooks);
1270
- return exitCode;
1271
- }
1272
- finally {
1273
- this.processes.exit(child.pid, exitCode);
1274
- }
1275
- };
1276
- const result = await this.shell.execute(String(commandLine), {
1277
- cwd: cwd || '/home/user',
1278
- env: { ...this.shell.env, ...(env || {}) },
1279
- onStdout: hooks.onStdout,
1280
- onStderr: hooks.onStderr,
1281
- stdin,
1282
- isolateShellState: true,
1283
- commandContext: { pid, cred, setUmask },
1284
- runAs,
1285
- });
1286
- return typeof result?.exitCode === 'number' ? result.exitCode : 0;
1287
- },
1288
- },
1289
- ctx: this.ctx,
1290
- spawnPool,
1291
- });
1292
- return this.facetProcessManager;
1293
- }
1008
+ _ensureFacetProcessManager() { return this.#runtimeServices._ensureFacetProcessManager(); }
1294
1009
  /**
1295
1010
  * Set the shell command registry for the W8 broker to dispatch
1296
1011
  * resolved commands. Called from the shell-init path right after
@@ -1304,148 +1019,13 @@ export class NimbusSession extends CloudflareDurableObject {
1304
1019
  * fetch calls across the lifetime of this DO instance. This prevents
1305
1020
  * ephemeral port exhaustion from creating a new worker per fetch.
1306
1021
  */
1307
- ensureFetchProxy(log) {
1308
- if (this.fetchProxyEntrypoint)
1309
- return this.fetchProxyEntrypoint;
1310
- try {
1311
- const env = this.env;
1312
- if (!env?.LOADER?.load) {
1313
- log?.('LOADER.load not available — using global fetch');
1314
- return null;
1315
- }
1316
- // Buffered proxy: reads the entire response body into an ArrayBuffer
1317
- // and returns it in ONE message instead of forwarding a ReadableStream.
1318
- // In workerd local dev, streaming responses across a service-binding
1319
- // RPC fabric opens a separate loopback socket PER chunk (~16KB), which
1320
- // exhausts ephemeral ports for larger installs (npm registry packuments
1321
- // are 500KB-3MB, tarballs up to 5MB). Buffering to arrayBuffer means
1322
- // 1 stub call = 1 loopback connection, not N connections.
1323
- //
1324
- // 32MB cap prevents a malformed giant response from OOMing the proxy
1325
- // isolate. Packages with tarballs larger than 32MB will fail to install
1326
- // cleanly (returned as 413 → caller treats as failed fetch).
1327
- const proxyCode = [
1328
- 'const MAX_BYTES = 32 * 1024 * 1024;',
1329
- 'export default {',
1330
- ' async fetch(request, workerEnv) {',
1331
- ' try {',
1332
- ' const body = await request.json();',
1333
- ' const resp = await fetch(body.url, {',
1334
- ' method: body.method || "GET",',
1335
- ' headers: body.headers || {},',
1336
- ' });',
1337
- ' // Check advertised Content-Length before buffering',
1338
- ' const clStr = resp.headers.get("content-length");',
1339
- ' if (clStr) {',
1340
- ' const cl = parseInt(clStr, 10);',
1341
- ' if (cl > MAX_BYTES) {',
1342
- ' return new Response(',
1343
- ' JSON.stringify({ error: "response too large: " + cl + " bytes (cap " + MAX_BYTES + ")" }),',
1344
- ' { status: 413, headers: { "Content-Type": "application/json" } }',
1345
- ' );',
1346
- ' }',
1347
- ' }',
1348
- ' // Buffer entire body — ONE message, not streamed chunks',
1349
- ' const buf = await resp.arrayBuffer();',
1350
- ' if (buf.byteLength > MAX_BYTES) {',
1351
- ' return new Response(',
1352
- ' JSON.stringify({ error: "response exceeded cap: " + buf.byteLength + " bytes" }),',
1353
- ' { status: 413, headers: { "Content-Type": "application/json" } }',
1354
- ' );',
1355
- ' }',
1356
- ' return new Response(buf, {',
1357
- ' status: resp.status,',
1358
- ' statusText: resp.statusText,',
1359
- ' headers: Object.fromEntries(resp.headers.entries()),',
1360
- ' });',
1361
- ' } catch (e) {',
1362
- ' return new Response(JSON.stringify({ error: e.message }), {',
1363
- ' status: 502,',
1364
- ' headers: { "Content-Type": "application/json" },',
1365
- ' });',
1366
- ' }',
1367
- ' }',
1368
- '};',
1369
- ].join('\n');
1370
- const worker = env.LOADER.load({
1371
- compatibilityDate: CF_COMPAT_DATE,
1372
- compatibilityFlags: ['nodejs_compat'],
1373
- mainModule: 'fetch-proxy.js',
1374
- modules: { 'fetch-proxy.js': proxyCode },
1375
- });
1376
- this.fetchProxyEntrypoint = worker.getEntrypoint();
1377
- log?.('Fetch proxy worker created (singleton)');
1378
- return this.fetchProxyEntrypoint;
1379
- }
1380
- catch (e) {
1381
- log?.(`Fetch proxy creation failed: ${e?.message}`);
1382
- return null;
1383
- }
1384
- }
1022
+ ensureFetchProxy(log) { return this.#runtimeServices.ensureFetchProxy(log); }
1385
1023
  /**
1386
1024
  * Build a FetchFn that routes through the singleton proxy entrypoint.
1387
1025
  * All concurrent fetches share ONE worker — no port exhaustion.
1388
1026
  */
1389
- buildFetchFn(log) {
1390
- const entrypoint = this.ensureFetchProxy(log);
1391
- if (!entrypoint)
1392
- return undefined;
1393
- return async (url, init) => {
1394
- const headers = {};
1395
- if (init?.headers) {
1396
- if (init.headers instanceof Headers) {
1397
- init.headers.forEach((v, k) => { headers[k] = v; });
1398
- }
1399
- else if (typeof init.headers === 'object') {
1400
- Object.assign(headers, init.headers);
1401
- }
1402
- }
1403
- return entrypoint.fetch(new Request('http://fetch-proxy/do-fetch', {
1404
- method: 'POST',
1405
- body: JSON.stringify({ url, method: init?.method || 'GET', headers }),
1406
- }));
1407
- };
1408
- }
1409
- async ensureNpmInstaller(onProgress) {
1410
- this.ensureSqliteFs();
1411
- if (!this.esbuildService) {
1412
- if (!this.sqliteFs)
1413
- throw new Error('Session VFS is not initialized');
1414
- this.esbuildService = new EsbuildService(this.sqliteFs.as(CRED_KERNEL));
1415
- }
1416
- // Lazy-load the installer (+ its ~216 KB resolver/facet/loader-pool
1417
- // subgraph) on first npm use so it stays out of the cold script-eval
1418
- // graph. The install command paths that call this are already async.
1419
- const { NpmInstaller } = await import('../npm/installer.js');
1420
- // ── Lazy fetch-proxy ────────────────────────────────────────────
1421
- // The fetch-proxy is a singleton dynamic worker (LOADER.load) that
1422
- // buffers registry responses to dodge wrangler-local-dev port
1423
- // exhaustion. It is only needed for the in-supervisor npm paths.
1424
- // When the resolver and install paths run in facets (default-on),
1425
- // they use bare globalThis.fetch and need no proxy.
1426
- //
1427
- // workerd has a per-DO cap on concurrent dynamic workers (~5-6
1428
- // empirically). A permanent live proxy worker eats one of those
1429
- // slots for the entire DO lifetime, so the proxy is built only when
1430
- // any facet path is disabled via its env flag.
1431
- const useFacetResolver = this._envFlagDefaultOn('NIMBUS_FACET_RESOLVER');
1432
- const useFacetInstall = this._envFlagDefaultOn('NIMBUS_FACET_NPM_INSTALL');
1433
- const useBatchFacet = this._envFlagDefaultOn('NIMBUS_FACET_NPM_INSTALL_BATCH');
1434
- const needProxy = !(useFacetResolver && useFacetInstall && useBatchFacet);
1435
- const fetchFn = needProxy ? this.buildFetchFn(onProgress) : undefined;
1436
- if (!needProxy) {
1437
- onProgress?.(`[npm] Lazy fetch-proxy: skipped (all facet paths default-on)`);
1438
- }
1439
- this.npmInstaller = new NpmInstaller(this.sqliteFs, this.ctx.storage.sql, {
1440
- esbuild: this.esbuildService,
1441
- bundlePool: this.ensureBundlePool(),
1442
- ctx: this.ctx,
1443
- env: this.env,
1444
- onProgress,
1445
- fetchFn,
1446
- });
1447
- return this.npmInstaller;
1448
- }
1027
+ buildFetchFn(log) { return this.#runtimeServices.buildFetchFn(log); }
1028
+ async ensureNpmInstaller(onProgress) { return this.#runtimeServices.ensureNpmInstaller(onProgress); }
1449
1029
  /**
1450
1030
  * Read an environment flag with default-on semantics. Mirrors the
1451
1031
  * shouldUseFacetPool / shouldUseFacetResolver / shouldUseBatchFacet
@@ -1453,15 +1033,7 @@ export class NimbusSession extends CloudflareDurableObject {
1453
1033
  * lazy-proxy decision uses identical semantics without leaking that
1454
1034
  * private API across modules.
1455
1035
  */
1456
- _envFlagDefaultOn(name) {
1457
- const raw = this.env?.[name];
1458
- if (raw === undefined || raw === null)
1459
- return true;
1460
- const s = String(raw).toLowerCase();
1461
- if (s === '0' || s === '' || s === 'false' || s === 'off' || s === 'no')
1462
- return false;
1463
- return true;
1464
- }
1036
+ _envFlagDefaultOn(name) { return this.#runtimeServices._envFlagDefaultOn(name); }
1465
1037
  // ── Session initialization ────────────────────────────────────────────
1466
1038
  // ── Session initialization ────────────────────────────────────────────
1467
1039
  //
@@ -1487,19 +1059,7 @@ export class NimbusSession extends CloudflareDurableObject {
1487
1059
  */
1488
1060
  _wakeRebuild = null;
1489
1061
  // ── Filesystem seeding ────────────────────────────────────────────────
1490
- ensureGlobalPrefixDirs(prefix) {
1491
- const fs = this.sqliteFs.as(CRED_SESSION_USER);
1492
- const dirs = [
1493
- prefix,
1494
- `${prefix}/lib`,
1495
- `${prefix}/lib/node_modules`,
1496
- `${prefix}/bin`,
1497
- ];
1498
- for (const dir of dirs) {
1499
- if (!fs.exists(dir))
1500
- fs.mkdir(dir, { recursive: true });
1501
- }
1502
- }
1062
+ ensureGlobalPrefixDirs(prefix) { return this.#runtimeServices.ensureGlobalPrefixDirs(prefix); }
1503
1063
  /**
1504
1064
  * The starter content a fresh Nimbus session shows a user: the banner, the
1505
1065
  * sample files, and the Vite starter app.