@nimbus-sh/worker 0.6.0 → 0.8.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.
- package/README.md +2 -2
- package/dist/cirrus-npm-cjs.generated.d.ts +1 -1
- package/dist/cirrus-npm-cjs.generated.js +1 -1
- package/dist/facets/cirrus-real.d.ts.map +1 -1
- package/dist/facets/cirrus-real.js +6 -5
- package/dist/facets/compose.d.ts +148 -0
- package/dist/facets/compose.d.ts.map +1 -0
- package/dist/facets/compose.js +146 -0
- package/dist/facets/durable-images.d.ts +9 -5
- package/dist/facets/durable-images.d.ts.map +1 -1
- package/dist/facets/durable-images.js +17 -7
- package/dist/facets/esbuild-bundle-pool.d.ts +49 -0
- package/dist/facets/esbuild-bundle-pool.d.ts.map +1 -0
- package/dist/facets/esbuild-bundle-pool.js +122 -0
- package/dist/facets/exec-telemetry.d.ts +8 -0
- package/dist/facets/exec-telemetry.d.ts.map +1 -1
- package/dist/facets/manager.d.ts +240 -109
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +677 -342
- package/dist/facets/process.d.ts +20 -16
- package/dist/facets/process.d.ts.map +1 -1
- package/dist/facets/process.js +59 -28
- package/dist/facets/real-vite-fs-shim.js +2 -2
- package/dist/facets/real-vite-hmr.d.ts +3 -1
- package/dist/facets/real-vite-hmr.d.ts.map +1 -1
- package/dist/facets/real-vite-hmr.js +18 -8
- package/dist/facets/vite-dev-server.d.ts +25 -47
- package/dist/facets/vite-dev-server.d.ts.map +1 -1
- package/dist/facets/vite-dev-server.js +185 -219
- package/dist/facets/wasm-image-digest.d.ts +25 -0
- package/dist/facets/wasm-image-digest.d.ts.map +1 -0
- package/dist/facets/wasm-image-digest.js +25 -0
- package/dist/facets/wasm-swap-registry.d.ts +51 -92
- package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
- package/dist/facets/wasm-swap-registry.js +68 -227
- package/dist/facets/ws-terminal.d.ts +9 -31
- package/dist/facets/ws-terminal.d.ts.map +1 -1
- package/dist/facets/ws-terminal.js +58 -51
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +6 -5
- package/dist/hosted/commands.d.ts +12 -0
- package/dist/hosted/commands.d.ts.map +1 -0
- package/dist/hosted/commands.js +1658 -0
- package/dist/hosted/runtime.d.ts +114 -0
- package/dist/hosted/runtime.d.ts.map +1 -0
- package/dist/hosted/runtime.js +288 -0
- package/dist/hosted/services.d.ts +40 -0
- package/dist/hosted/services.d.ts.map +1 -0
- package/dist/hosted/services.js +512 -0
- package/dist/loaders/child-process/spawn-pool.d.ts +3 -2
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +3 -2
- package/dist/loaders/generated-workers.d.ts +2 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +3 -3
- package/dist/loaders/npm-resolve-preamble.d.ts +3 -6
- package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
- package/dist/loaders/npm-resolve-preamble.js +34 -108
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/npm/install-batch-facet.d.ts +3 -2
- package/dist/npm/install-batch-facet.d.ts.map +1 -1
- package/dist/npm/install-batch-facet.js +45 -15
- package/dist/npm/installer.d.ts +22 -17
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +209 -193
- package/dist/npm/pre-bundle-facet.d.ts +1 -1
- package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
- package/dist/npm/pre-bundle-facet.js +1 -1
- package/dist/npm/resolve-facet.d.ts +6 -0
- package/dist/npm/resolve-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.d.ts +21 -20
- package/dist/npm/resolve-one-facet.d.ts.map +1 -1
- package/dist/npm/resolve-one-facet.js +85 -84
- package/dist/npm/semver.d.ts +59 -0
- package/dist/npm/semver.d.ts.map +1 -0
- package/dist/npm/semver.js +226 -0
- package/dist/router/remote-api.js +31 -1
- package/dist/runtime/bash-repl.d.ts +1 -1
- package/dist/runtime/bash-repl.d.ts.map +1 -1
- package/dist/runtime/bash-repl.js +38 -4
- package/dist/runtime/bun-runner.js +1 -1
- package/dist/runtime/html-entrypoint.d.ts +7 -0
- package/dist/runtime/html-entrypoint.d.ts.map +1 -1
- package/dist/runtime/html-entrypoint.js +18 -2
- package/dist/runtime/node-runner.d.ts +4 -1
- package/dist/runtime/node-runner.d.ts.map +1 -1
- package/dist/runtime/node-runner.js +9 -2
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +305 -29
- package/dist/runtime/opencode-facet-runner.js +16 -16
- package/dist/runtime/package-manager.d.ts +35 -122
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +61 -596
- package/dist/runtime/process-logs-api.d.ts +1 -1
- package/dist/runtime/process-logs-api.d.ts.map +1 -1
- package/dist/runtime/process-logs-api.js +1 -1
- package/dist/runtime/python-repl.d.ts +2 -1
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +82 -59
- package/dist/runtime/repl-session.d.ts +9 -47
- package/dist/runtime/repl-session.d.ts.map +1 -1
- package/dist/runtime/repl-session.js +105 -105
- package/dist/runtime/runtime-catalog.d.ts +13 -0
- package/dist/runtime/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime/runtime-catalog.js +88 -0
- package/dist/session/agent.d.ts +1 -21
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/ai.d.ts +5 -1
- package/dist/session/ai.d.ts.map +1 -1
- package/dist/session/ai.js +7 -0
- package/dist/session/helpers.d.ts +38 -0
- package/dist/session/helpers.d.ts.map +1 -1
- package/dist/session/helpers.js +76 -0
- package/dist/session/hibernation.d.ts +2 -1
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +7 -4
- package/dist/session/init-phases.d.ts +1 -1
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +2 -12
- package/dist/session/init.d.ts +15 -36
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +50 -2327
- package/dist/session/loopback.d.ts +2 -29
- package/dist/session/loopback.d.ts.map +1 -1
- package/dist/session/loopback.js +3 -27
- package/dist/session/nimbus-session.d.ts +77 -34
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +126 -514
- package/dist/session/npm-install-port.d.ts +18 -0
- package/dist/session/npm-install-port.d.ts.map +1 -0
- package/dist/session/npm-install-port.js +37 -0
- package/dist/session/port-capability.d.ts +44 -0
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +110 -0
- package/dist/session/programmatic.d.ts +36 -43
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +109 -115
- package/dist/session/routes.d.ts +30 -17
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +42 -131
- package/dist/session/rpc.d.ts +15 -15
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +98 -49
- package/dist/session/serving-port.d.ts +27 -0
- package/dist/session/serving-port.d.ts.map +1 -0
- package/dist/session/serving-port.js +27 -0
- package/dist/session/start-real-vite.d.ts +12 -0
- package/dist/session/start-real-vite.d.ts.map +1 -1
- package/dist/session/start-real-vite.js +4 -4
- package/dist/session/supervisor-op.d.ts +4 -4
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +24 -10
- package/dist/session/supervisor-rpc.d.ts +7 -7
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +4 -0
- package/dist/session/vite-command.d.ts +18 -0
- package/dist/session/vite-command.d.ts.map +1 -0
- package/dist/session/vite-command.js +609 -0
- package/dist/session/ws.d.ts +61 -0
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +122 -12
- package/dist/shell/npm-bin-entrypoints.d.ts +0 -35
- package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
- package/dist/shell/npm-bin-entrypoints.js +12 -85
- package/dist/workspace-host.d.ts +6 -0
- package/dist/workspace-host.d.ts.map +1 -0
- package/dist/workspace-host.js +5 -0
- package/dist/workspace.d.ts +2 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +1 -0
- package/package.json +34 -9
- package/public/_assets/runtime/{node-shims-9e1380d9ee430fcc.js → node-shims-8f8c64edd2a1f0f5.js} +305 -29
- package/public/index.html +1 -1
- package/scripts/bundle-facet-workers.mjs +1 -1
- package/scripts/bundle-npm-cjs.mjs +2 -2
- package/dist/facets/on-demand-bundle-gate.d.ts +0 -65
- package/dist/facets/on-demand-bundle-gate.d.ts.map +0 -1
- package/dist/facets/on-demand-bundle-gate.js +0 -125
- package/dist/npm/install-args.d.ts +0 -21
- package/dist/npm/install-args.d.ts.map +0 -1
- package/dist/npm/install-args.js +0 -70
- package/dist/npm/npm-log.d.ts +0 -31
- package/dist/npm/npm-log.d.ts.map +0 -1
- package/dist/npm/npm-log.js +0 -51
|
@@ -1,24 +1,10 @@
|
|
|
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 { FacetManager } from '../facets/manager.js';
|
|
12
|
-
import { resolveDurableWorkerImage } from '../facets/durable-images.js';
|
|
13
|
-
import { FacetProcessManager } from '../facets/process.js';
|
|
14
|
-
import { ChildProcessSpawnPool } from '../loaders/child-process/spawn-pool.js';
|
|
15
4
|
import { SessionProcessSupervisor } from '@nimbus-sh/core/runtime/session-process-supervisor.js';
|
|
16
5
|
import { PID_GEN_STRIDE } from '@nimbus-sh/core/runtime/process-table.js';
|
|
17
|
-
import { CRED_KERNEL, CRED_SESSION_USER
|
|
6
|
+
import { CRED_KERNEL, CRED_SESSION_USER } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
18
7
|
import { PortRegistry } from '@nimbus-sh/core/runtime/port-registry.js';
|
|
19
|
-
import { EsbuildService } from '@nimbus-sh/core/runtime/esbuild-service.js';
|
|
20
|
-
import { ESBUILD_TRANSFORM_WORKER_ID, esbuildTransformWorkerCode, } from '../facets/esbuild-transform.js';
|
|
21
|
-
import { fetchEsbuildWasmBytes } from '../runtime/esbuild-wasm-bytes.js';
|
|
22
8
|
import { registerAllocObserver } from '@nimbus-sh/platform/heavy-alloc-coord.js';
|
|
23
9
|
// S10: oom-discriminator helpers (recordFailure, getFailures,
|
|
24
10
|
// getLastRpcFrame, getLastFacetId, snapshotForStorage, rehydrateFromStorage)
|
|
@@ -32,58 +18,63 @@ import { registerAllocObserver } from '@nimbus-sh/platform/heavy-alloc-coord.js'
|
|
|
32
18
|
// longer references them directly. Phase 2 A'.5 renamed the function
|
|
33
19
|
// (was getEsbuildWasmBytes; cached) to fetchEsbuildWasmBytes (no
|
|
34
20
|
// supervisor cache; goes through env.ASSETS on demand).
|
|
21
|
+
// S10: oom-discriminator helpers (recordFailure, getFailures,
|
|
22
|
+
// getLastRpcFrame, getLastFacetId, snapshotForStorage, rehydrateFromStorage)
|
|
23
|
+
// moved to sibling modules (-rpc uses recordFailure for _reportExternalExit;
|
|
24
|
+
// -ws uses recordFailure for webSocketError; -diag uses getFailures +
|
|
25
|
+
// snapshotForStorage + rehydrateFromStorage; -routes uses getFailures +
|
|
26
|
+
// getLastRpcFrame + getLastFacetId for /api/_diag/memory). Class file
|
|
27
|
+
// no longer references any of them directly.
|
|
28
|
+
// S10: classifyError, LRU_MAX_ENTRIES, fetchEsbuildWasmBytes moved to
|
|
29
|
+
// sibling modules (-rpc, -routes, esbuild-wasm-bytes); class file no
|
|
30
|
+
// longer references them directly. Phase 2 A'.5 renamed the function
|
|
31
|
+
// (was getEsbuildWasmBytes; cached) to fetchEsbuildWasmBytes (no
|
|
32
|
+
// supervisor cache; goes through env.ASSETS on demand).
|
|
35
33
|
import { adoptCtxExports } from '@nimbus-sh/fabric/composition.js';
|
|
36
|
-
import { NIMBUS_VERSION, DEFAULT_MOUNT_POINTS
|
|
34
|
+
import { NIMBUS_VERSION, DEFAULT_MOUNT_POINTS } from '@nimbus-sh/core/constants.js';
|
|
37
35
|
import { seedBaseFilesystem } from '@nimbus-sh/core/workspace';
|
|
38
|
-
import {
|
|
36
|
+
import { routeSessionLoopback } from './ai.js';
|
|
37
|
+
import { seedProject, SEED_PROJECT_NAME } from '@nimbus-sh/core/vfs/seed-project.js';
|
|
39
38
|
import { BASE_PATH_HEADER } from '../_shared/session-router.js';
|
|
40
39
|
import { ATTACH_BOOTSTRAP_JTI_KEY_PREFIX, SESSION_DESTROYED_KEY } from './keys.js';
|
|
41
40
|
import { appendScrollback } from './state-store.js';
|
|
42
|
-
import {
|
|
41
|
+
import { wireProcessLogSocketBroadcast } from '../runtime/process-logs-api.js';
|
|
42
|
+
// S3: tryEnableReplicas + getReplicaState extracted to ./nimbus-session-replica.ts.
|
|
43
43
|
// S3: tryEnableReplicas + getReplicaState extracted to ./nimbus-session-replica.ts.
|
|
44
|
-
import { wireReplicasOnConstruct as _w12WireReplicasOnConstruct, getReplicaState as _w12GetReplicaState
|
|
44
|
+
import { wireReplicasOnConstruct as _w12WireReplicasOnConstruct, getReplicaState as _w12GetReplicaState } from './replica-routes.js';
|
|
45
45
|
// S5: storage-key constants moved to ./nimbus-session-keys.ts; consumed by
|
|
46
46
|
// sibling modules (-hib, -diag, -ws). The class file itself no longer
|
|
47
47
|
// references any storage key directly.
|
|
48
48
|
// S4: W9 hibernation surface extracted.
|
|
49
|
-
|
|
49
|
+
// S5: storage-key constants moved to ./nimbus-session-keys.ts; consumed by
|
|
50
|
+
// sibling modules (-hib, -diag, -ws). The class file itself no longer
|
|
51
|
+
// references any storage key directly.
|
|
52
|
+
// S4: W9 hibernation surface extracted.
|
|
53
|
+
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';
|
|
50
54
|
import { timers } from '@nimbus-sh/fabric/timers.js';
|
|
51
55
|
import { adoptGeneration, generation } from '@nimbus-sh/fabric/generation.js';
|
|
52
56
|
// S6: initSession (1875 LOC of cmd registrations + boot wiring) extracted.
|
|
57
|
+
// S6: initSession (1875 LOC of cmd registrations + boot wiring) extracted.
|
|
53
58
|
import { initSession as _w11InitSession } from './init.js';
|
|
54
59
|
// S7: webSocket lifecycle (message, close, error, F1 discriminator,
|
|
55
60
|
// _w5SafePersistRing) extracted.
|
|
56
|
-
|
|
61
|
+
// S7: webSocket lifecycle (message, close, error, F1 discriminator,
|
|
62
|
+
// _w5SafePersistRing) extracted.
|
|
63
|
+
import { wsMessage as _wsDoMessage, wsClose as _wsDoClose, wsError as _wsDoError, safePersistRing as _wsDoSafePersistRing } from './ws.js';
|
|
64
|
+
// S8: Supervisor RPC + W8 cp* + legacy VFS impls extracted.
|
|
57
65
|
// S8: Supervisor RPC + W8 cp* + legacy VFS impls extracted.
|
|
58
66
|
import * as _rpc from './rpc.js';
|
|
59
67
|
import { buildSessionSupervisorOps } from './supervisor-op.js';
|
|
60
|
-
|
|
61
|
-
// The supervisor terminates a facet's outbound sockets so inbound frames
|
|
62
|
-
// arrive as supervisor replies (VFS coherence witness 3).
|
|
63
|
-
import { WebSocketRelay } from './ws-relay.js';
|
|
68
|
+
// S9: HTTP fetch routing extracted (combined S9a + S9b).
|
|
64
69
|
// S9: HTTP fetch routing extracted (combined S9a + S9b).
|
|
65
70
|
import * as _routes from './routes.js';
|
|
71
|
+
import * as _portCapability from './port-capability.js';
|
|
72
|
+
// Programmatic SDK RPC surface.
|
|
66
73
|
// Programmatic SDK RPC surface.
|
|
67
74
|
import * as _programmatic from './programmatic.js';
|
|
68
75
|
// S10: heap probe + W5 OOM-ring persistence extracted.
|
|
76
|
+
// S10: heap probe + W5 OOM-ring persistence extracted.
|
|
69
77
|
import * as _diag from './diag.js';
|
|
70
|
-
// ── Pure helpers extracted to ./nimbus-session-helpers.ts (S1) ────────
|
|
71
|
-
//
|
|
72
|
-
// renderNoDevServerHtml, BUNDLER_BIN_PREFIXES, NIMBUS_UNSUPPORTED_BINS,
|
|
73
|
-
// WRANGLER_IGNORED_FLAGS{,_WITH_VALUE}, WRANGLER_UNSUPPORTED_CONFIG_FIELDS,
|
|
74
|
-
// filterWranglerFlags, detectUnsupportedWranglerConfig, _CP_FACET_DIRECT,
|
|
75
|
-
// _CP_PURE_BUILTIN, _classifyCommand, detectBundlerBin, checkNodeModulesGuard
|
|
76
|
-
// all live in the helpers module now.
|
|
77
|
-
//
|
|
78
|
-
// They are imported here (so call sites in this file work unchanged) and
|
|
79
|
-
// re-exported (so external callers importing them from
|
|
80
|
-
// `./nimbus-session.js` keep working — back-compat).
|
|
81
|
-
//
|
|
82
|
-
// (esbuild wasm bytes are fetched from env.ASSETS by
|
|
83
|
-
// src/esbuild-wasm-bytes.ts at pool-construction time; A'.5 dropped
|
|
84
|
-
// the supervisor-resident cache + the SUPERVISOR.getEsbuildWasm RPC.)
|
|
85
|
-
// Helpers needed by this class file's own logic (not just re-export).
|
|
86
|
-
import { _classifyCommand, } from './helpers.js';
|
|
87
78
|
import { z } from 'zod/v4';
|
|
88
79
|
const CpFacetDirectPayloadSchema = z.object({
|
|
89
80
|
command: z.unknown().optional().transform((value) => value == null ? '' : String(value)),
|
|
@@ -170,7 +161,7 @@ Cloud-native development environment on Cloudflare Workers.
|
|
|
170
161
|
| --- | --- |
|
|
171
162
|
| Run JavaScript | \`node hello.js\` |
|
|
172
163
|
| Install npm packages | \`npm install <pkg>\` |
|
|
173
|
-
| Start the starter app | \`cd
|
|
164
|
+
| Start the starter app | \`cd ${SEED_PROJECT_NAME} && npm install && npm run dev\` |
|
|
174
165
|
| Run a Cloudflare Worker locally | \`nimbus-wrangler dev\` |
|
|
175
166
|
|
|
176
167
|
## AI Models
|
|
@@ -243,13 +234,36 @@ The editor opens this file in Markdown preview mode by default. Use
|
|
|
243
234
|
`;
|
|
244
235
|
}
|
|
245
236
|
export class NimbusSession extends CloudflareDurableObject {
|
|
237
|
+
#runtimeServices;
|
|
246
238
|
// this.ctx and this.env are provided by the DurableObject base class
|
|
239
|
+
runtimeWorkspace = null;
|
|
240
|
+
runtimeReady = null;
|
|
241
|
+
get runtimeManager() {
|
|
242
|
+
if (!this.runtimeWorkspace)
|
|
243
|
+
throw new Error('Nimbus runtime is not initialized');
|
|
244
|
+
return this.runtimeWorkspace.runtimes;
|
|
245
|
+
}
|
|
246
|
+
async routeLoopback(port, request) {
|
|
247
|
+
return routeSessionLoopback({ ctx: this.ctx, env: this.env, portRegistry: this.portRegistry }, port, request);
|
|
248
|
+
}
|
|
249
|
+
async ensureRuntimeReady() {
|
|
250
|
+
if (this.shell)
|
|
251
|
+
return;
|
|
252
|
+
this.runtimeReady ??= this.initSession(null).then(() => {
|
|
253
|
+
this._b4Phase = 'drained';
|
|
254
|
+
}).finally(() => { this.runtimeReady = null; });
|
|
255
|
+
await this.runtimeReady;
|
|
256
|
+
}
|
|
247
257
|
sqliteFs = null;
|
|
248
258
|
kernel = null;
|
|
249
259
|
shell = null;
|
|
250
260
|
shellProcessPid = null;
|
|
251
261
|
terminal = null;
|
|
252
|
-
|
|
262
|
+
/** The composed manager is the one field; `.manager` is derived. */
|
|
263
|
+
facetManagerComposed = null;
|
|
264
|
+
get facetManager() {
|
|
265
|
+
return this.facetManagerComposed?.manager ?? null;
|
|
266
|
+
}
|
|
253
267
|
/** W8: child_process broker. Lazy — only constructed when first cp* RPC arrives. */
|
|
254
268
|
facetProcessManager = null;
|
|
255
269
|
/**
|
|
@@ -259,6 +273,12 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
259
273
|
*/
|
|
260
274
|
webSocketRelay = null;
|
|
261
275
|
esbuildService = null;
|
|
276
|
+
/**
|
|
277
|
+
* The session's single esbuild facet pool, shared by the npm installer's
|
|
278
|
+
* pre-bundler and the dev server's on-demand /@modules/ path. Lazy; see
|
|
279
|
+
* ensureBundlePool. Disposed with the installer and dev server.
|
|
280
|
+
*/
|
|
281
|
+
bundlePool = null;
|
|
262
282
|
viteDevServer = null;
|
|
263
283
|
/**
|
|
264
284
|
* runtime primitive support (P5): PID + port the default-Cirrus vite shim is
|
|
@@ -406,7 +426,13 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
406
426
|
*/
|
|
407
427
|
wranglerAliasBannerShown = false;
|
|
408
428
|
constructor(ctx, env) {
|
|
409
|
-
super(ctx, env);
|
|
429
|
+
super(ctx, env);
|
|
430
|
+
this.#runtimeServices = runtimeServices.bindRuntimeServices(this, {
|
|
431
|
+
ctx,
|
|
432
|
+
env,
|
|
433
|
+
notify: (line) => this._notifySession(line),
|
|
434
|
+
requestLaunchTurn: (at) => this._scheduleLaunchTurn(at),
|
|
435
|
+
});
|
|
410
436
|
// In `wrangler dev`, the outer Worker and this DO share a single
|
|
411
437
|
// workerd process, so the `adoptCtxExports(ctx.exports)` call in the
|
|
412
438
|
// outer fetch handler (src/index.ts) is visible here via the
|
|
@@ -656,23 +682,23 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
656
682
|
supervisorOp(envelope) {
|
|
657
683
|
return this.supervisorOps().dispatch(envelope);
|
|
658
684
|
}
|
|
659
|
-
async _rpcReadFile(path, pid) { return _rpc._rpcReadFile(this, path, pid); }
|
|
660
|
-
async _rpcReadFileBytes(path, pid) { return _rpc._rpcReadFileBytes(this, path, pid); }
|
|
685
|
+
async _rpcReadFile(path, pid, cred) { return _rpc._rpcReadFile(this, path, pid, cred); }
|
|
686
|
+
async _rpcReadFileBytes(path, pid, cred) { return _rpc._rpcReadFileBytes(this, path, pid, cred); }
|
|
661
687
|
async _rpcInnerDoFetch(req) { return _rpc._rpcInnerDoFetch(this, req); }
|
|
662
|
-
async _rpcWriteFile(path, content, pid) { return _rpc._rpcWriteFile(this, path, content, pid); }
|
|
688
|
+
async _rpcWriteFile(path, content, pid, cred) { return _rpc._rpcWriteFile(this, path, content, pid, cred); }
|
|
663
689
|
async _rpcWriteProtectedRootFile(rootPath, path, content) {
|
|
664
690
|
return _rpc._rpcWriteProtectedRootFile(this, rootPath, path, content);
|
|
665
691
|
}
|
|
666
|
-
async _rpcStat(path, pid) { return _rpc._rpcStat(this, path, pid); }
|
|
667
|
-
async _rpcLstat(path, pid) { return _rpc._rpcLstat(this, path, pid); }
|
|
692
|
+
async _rpcStat(path, pid, cred) { return _rpc._rpcStat(this, path, pid, cred); }
|
|
693
|
+
async _rpcLstat(path, pid, cred) { return _rpc._rpcLstat(this, path, pid, cred); }
|
|
668
694
|
async _rpcHasLegacySymlinkUnder(path, pid) {
|
|
669
695
|
return _rpc._rpcHasLegacySymlinkUnder(this, path, pid);
|
|
670
696
|
}
|
|
671
697
|
async _rpcUtimes(path, atimeMs, mtimeMs, pid) {
|
|
672
698
|
return _rpc._rpcUtimes(this, path, atimeMs, mtimeMs, pid);
|
|
673
699
|
}
|
|
674
|
-
async _rpcChmod(path, mode, pid) {
|
|
675
|
-
return _rpc._rpcChmod(this, path, mode, pid);
|
|
700
|
+
async _rpcChmod(path, mode, pid, cred) {
|
|
701
|
+
return _rpc._rpcChmod(this, path, mode, pid, cred);
|
|
676
702
|
}
|
|
677
703
|
async _rpcAccess(path, mode, pid) {
|
|
678
704
|
return _rpc._rpcAccess(this, path, mode, pid);
|
|
@@ -683,11 +709,11 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
683
709
|
async _rpcSetUmask(mask, pid) {
|
|
684
710
|
return _rpc._rpcSetUmask(this, mask, pid);
|
|
685
711
|
}
|
|
686
|
-
async _rpcReaddir(path, pid) { return _rpc._rpcReaddir(this, path, pid); }
|
|
687
|
-
async _rpcExists(path, pid) { return _rpc._rpcExists(this, path, pid); }
|
|
688
|
-
async _rpcMkdir(path, pid) { return _rpc._rpcMkdir(this, path, pid); }
|
|
712
|
+
async _rpcReaddir(path, pid, cred) { return _rpc._rpcReaddir(this, path, pid, cred); }
|
|
713
|
+
async _rpcExists(path, pid, cred) { return _rpc._rpcExists(this, path, pid, cred); }
|
|
714
|
+
async _rpcMkdir(path, pid, cred) { return _rpc._rpcMkdir(this, path, pid, cred); }
|
|
689
715
|
async _rpcRmdir(path, pid) { return _rpc._rpcRmdir(this, path, pid); }
|
|
690
|
-
async _rpcRename(from, to, pid) { return _rpc._rpcRename(this, from, to, pid); }
|
|
716
|
+
async _rpcRename(from, to, pid, cred) { return _rpc._rpcRename(this, from, to, pid, cred); }
|
|
691
717
|
async _rpcReadlink(path, pid) { return _rpc._rpcReadlink(this, path, pid); }
|
|
692
718
|
async _rpcSymlink(target, path, pid) { return _rpc._rpcSymlink(this, target, path, pid); }
|
|
693
719
|
async _rpcFsRevision(path, pid) { return _rpc._rpcFsRevision(this, path, pid); }
|
|
@@ -717,8 +743,8 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
717
743
|
return _rpc._rpcFsWrite(this, handleId, offset, bytes, pid);
|
|
718
744
|
}
|
|
719
745
|
async _rpcFsClose(handleId, pid) { return _rpc._rpcFsClose(this, handleId, pid); }
|
|
720
|
-
async _rpcFsReadRange(path, offset, length, pid) {
|
|
721
|
-
return _rpc._rpcFsReadRange(this, path, offset, length, pid);
|
|
746
|
+
async _rpcFsReadRange(path, offset, length, pid, cred) {
|
|
747
|
+
return _rpc._rpcFsReadRange(this, path, offset, length, pid, cred);
|
|
722
748
|
}
|
|
723
749
|
async _rpcFsReadRangeUncached(path, offset, length, pid) {
|
|
724
750
|
return _rpc._rpcFsReadRangeUncached(this, path, offset, length, pid);
|
|
@@ -838,7 +864,15 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
838
864
|
async _rpcUnexposePort(port) { return _programmatic.rpcUnexposePort(this, port); }
|
|
839
865
|
/** Capability-authenticated port route, for an embedder holding the token. */
|
|
840
866
|
async _rpcRouteCapabilityPort(port, capability, request, innerPath) {
|
|
841
|
-
return
|
|
867
|
+
return _portCapability.routeCapabilityPort(this, port, capability, request, innerPath);
|
|
868
|
+
}
|
|
869
|
+
/** The port route's dev-server restore, delegated — lives in routes.ts where the other session routes do. */
|
|
870
|
+
restorePersistedDevServer(onlyPort) {
|
|
871
|
+
return _routes.restorePersistedDevServer(this, onlyPort);
|
|
872
|
+
}
|
|
873
|
+
/** The port route's in-DO HMR accept, delegated — same reason. */
|
|
874
|
+
acceptCirrusHmrWs(request) {
|
|
875
|
+
return _routes.acceptCirrusHmrWs(this, request);
|
|
842
876
|
}
|
|
843
877
|
/**
|
|
844
878
|
* The port route's recovery seam: a durable application journalled but
|
|
@@ -855,7 +889,11 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
855
889
|
this.ensureFacetManager();
|
|
856
890
|
return this.facetManager.ensureDurableAppOnPort(port);
|
|
857
891
|
}
|
|
858
|
-
async _rpcDeleteFile(path, options) { return _programmatic.rpcDeleteFile(this, path, options); }
|
|
892
|
+
async _rpcDeleteFile(path, options, cred) { return _programmatic.rpcDeleteFile(this, path, options, cred); }
|
|
893
|
+
/** Colocated embedders only (DO stub); not on the remote dispatcher. See `rpcSpawnWorker`. */
|
|
894
|
+
async _rpcSpawnWorker(workerCode, command, cwd, opts) {
|
|
895
|
+
return _programmatic.rpcSpawnWorker(this, workerCode, command, cwd, opts);
|
|
896
|
+
}
|
|
859
897
|
async _rpcDestroy(options) { return _programmatic.rpcDestroy(this, options); }
|
|
860
898
|
// Legacy VFS (direct method calls)
|
|
861
899
|
vfsReadFile(path) { return _rpc.vfsReadFile(this, path); }
|
|
@@ -944,6 +982,7 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
944
982
|
console.warn('[nimbus/W5] ring rehydrate failed:', e?.message);
|
|
945
983
|
});
|
|
946
984
|
}
|
|
985
|
+
return this.sqliteFs;
|
|
947
986
|
}
|
|
948
987
|
// ── W5 Lever 5: ring buffer persistence on DO storage ─────────────────
|
|
949
988
|
// Storage key W5_RING_STORAGE_KEY lives in ./nimbus-session-keys.ts (S5).
|
|
@@ -967,309 +1006,29 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
967
1006
|
_w5PersistRing() {
|
|
968
1007
|
return _diag.persistRing(this, this.ctx);
|
|
969
1008
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const assets = Reflect.get(this.env, 'ASSETS');
|
|
982
|
-
if (!assets || typeof assets.fetch !== 'function') {
|
|
983
|
-
throw new Error('Nimbus: env.ASSETS unavailable for isolated esbuild transform');
|
|
984
|
-
}
|
|
985
|
-
const worker = await loader.get(ESBUILD_TRANSFORM_WORKER_ID, async () => esbuildTransformWorkerCode(await fetchEsbuildWasmBytes({ ASSETS: assets })));
|
|
986
|
-
const transformClass = worker.getDurableObjectClass('EsbuildTransformFacet');
|
|
987
|
-
const facet = this.ctx.facets.get(`esbuild-transform-${ESBUILD_TRANSFORM_WORKER_ID}`, async () => ({ class: transformClass }));
|
|
988
|
-
return facet.transform(code, options);
|
|
989
|
-
},
|
|
990
|
-
onSpawn: (pid, command, longRunning) => {
|
|
991
|
-
const attachedTty = this.processes.get(pid)?.attachedTty === true;
|
|
992
|
-
if (longRunning) {
|
|
993
|
-
try {
|
|
994
|
-
this.processes.openInput(pid);
|
|
995
|
-
}
|
|
996
|
-
catch { }
|
|
997
|
-
}
|
|
998
|
-
// Only surface long-running / user-visible spawns to keep
|
|
999
|
-
// the terminal uncluttered. Short `node <file>` evals also
|
|
1000
|
-
// get a line because users want the pid for `logs`/`kill`.
|
|
1001
|
-
if (!this.terminal)
|
|
1002
|
-
return;
|
|
1003
|
-
const label = longRunning ? 'started (long-running)' : 'started';
|
|
1004
|
-
this.terminal.write(`\x1b[2m[facet ${label}: pid=${pid} cmd="${command}"]\x1b[0m\r\n`);
|
|
1005
|
-
// Structured event so the tabs UI can auto-open a log tab
|
|
1006
|
-
// for long-running processes (vite, wrangler dev, etc.).
|
|
1007
|
-
notifyTerminalEvent(this.terminal, {
|
|
1008
|
-
type: 'spawn', pid, command, longRunning, attachedTty,
|
|
1009
|
-
});
|
|
1010
|
-
},
|
|
1011
|
-
// The fallback resolver a self-owned durable spawn re-drives
|
|
1012
|
-
// through: read the image blobs the spawn persisted under
|
|
1013
|
-
// `.nimbus/images/<sha256>` and restore the launch's env and
|
|
1014
|
-
// modules from them. An embedder-owned launch answers its own
|
|
1015
|
-
// bookkeeping through resolveWorkerLaunch instead — this hook is
|
|
1016
|
-
// only the fallback for applications nobody else is keeping, and
|
|
1017
|
-
// because it cannot re-mint a live globalOutbound binding the
|
|
1018
|
-
// spawn path refuses such a launch under it.
|
|
1019
|
-
resolveWorkerLaunchFallback: async (recipe) => {
|
|
1020
|
-
this.ensureSqliteFs();
|
|
1021
|
-
return resolveDurableWorkerImage(this.sqliteFs, recipe);
|
|
1022
|
-
},
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
if (this.facetManager && this.sqliteFs) {
|
|
1026
|
-
this.facetManager.setVfs(this.sqliteFs);
|
|
1027
|
-
// W3.5 Fix B: share the session's lazy esbuildService with the
|
|
1028
|
-
// FacetManager so the bundle's ESM→CJS pre-pass doesn't pay
|
|
1029
|
-
// wasm-init twice. If the session hasn't constructed one yet,
|
|
1030
|
-
// FacetManager will lazy-create its own on first exec — same
|
|
1031
|
-
// wasm bytes, same ~10ms init cost, just paid once per surface.
|
|
1032
|
-
if (this.esbuildService) {
|
|
1033
|
-
this.facetManager.setEsbuildService(this.esbuildService);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1009
|
+
/** The session's esbuild facet pool provider; constructing it does no work. */
|
|
1010
|
+
ensureBundlePool() { return this.#runtimeServices.ensureBundlePool(); }
|
|
1011
|
+
/**
|
|
1012
|
+
* The session's FacetManager, composed through the one factory every host
|
|
1013
|
+
* uses (`facets/compose.ts`). What is wired here is only what is the
|
|
1014
|
+
* session's: the terminal a spawn is announced on, the scrollback a notice
|
|
1015
|
+
* survives in, the alarm that grants a launch its next turn, and the exit
|
|
1016
|
+
* report that keeps the process table honest. The isolated esbuild
|
|
1017
|
+
* transform and the durable image-store fallback are the factory's.
|
|
1018
|
+
*/
|
|
1019
|
+
ensureFacetManager() { return this.#runtimeServices.ensureFacetManager(); }
|
|
1037
1020
|
/**
|
|
1038
1021
|
* The supervisor-owned WebSocket relay. Lazy, because most sessions never
|
|
1039
1022
|
* open a socket and the sockets it holds are live objects that must not
|
|
1040
1023
|
* outlive the session.
|
|
1041
1024
|
*/
|
|
1042
|
-
_ensureWebSocketRelay() {
|
|
1043
|
-
if (!this.webSocketRelay)
|
|
1044
|
-
this.webSocketRelay = new WebSocketRelay();
|
|
1045
|
-
return this.webSocketRelay;
|
|
1046
|
-
}
|
|
1025
|
+
_ensureWebSocketRelay() { return this.#runtimeServices._ensureWebSocketRelay(); }
|
|
1047
1026
|
/**
|
|
1048
1027
|
* W8: lazily construct the FacetProcessManager when the first cp* RPC
|
|
1049
1028
|
* arrives. Wired with adapters that bridge the Nimbus shell command
|
|
1050
1029
|
* registry to the FacetProcessManager's CommandRegistryLike contract.
|
|
1051
1030
|
*/
|
|
1052
|
-
_ensureFacetProcessManager() {
|
|
1053
|
-
if (this.facetProcessManager)
|
|
1054
|
-
return this.facetProcessManager;
|
|
1055
|
-
this.ensureSqliteFs();
|
|
1056
|
-
this.ensureFacetManager();
|
|
1057
|
-
// FacetProcessManager is statically imported at top-of-file (W8).
|
|
1058
|
-
// No lazy-import: workerd doesn't ship CJS require, and the dynamic
|
|
1059
|
-
// import would be async — making _ensureFacetProcessManager async
|
|
1060
|
-
// would force every cp* RPC entry point to also be async on the
|
|
1061
|
-
// promise-resolution path, which is fine but uglier. Compile-time
|
|
1062
|
-
// tree-shaking handles unused-when-no-cp-RPC paths.
|
|
1063
|
-
// Adapter for FacetManagerLike — wraps the existing FacetManager.exec
|
|
1064
|
-
// with a streaming surface. Phase 1 simplification: facet-direct
|
|
1065
|
-
// commands are dispatched through the shell registry the same way
|
|
1066
|
-
// shell.execute does, but with the per-PID hooks routed.
|
|
1067
|
-
const facetMgrAdapter = {
|
|
1068
|
-
execStream: async (codeJson, opts, hooks) => {
|
|
1069
|
-
// codeJson is a payload from FacetProcessManager._dispatch facet-direct
|
|
1070
|
-
// path: {command, args, env, cwd, stdin}. We dispatch through the
|
|
1071
|
-
// existing shell registry by resolving the command and invoking
|
|
1072
|
-
// it with synthesized output streams that route to hooks.
|
|
1073
|
-
let payload;
|
|
1074
|
-
try {
|
|
1075
|
-
const parsed = CpFacetDirectPayloadSchema.safeParse(JSON.parse(codeJson));
|
|
1076
|
-
if (!parsed.success) {
|
|
1077
|
-
hooks.onStderr('child_process: facet dispatch requires a broker-assigned process pid\n');
|
|
1078
|
-
return 1;
|
|
1079
|
-
}
|
|
1080
|
-
payload = parsed.data;
|
|
1081
|
-
}
|
|
1082
|
-
catch {
|
|
1083
|
-
hooks.onStderr('child_process: invalid facet dispatch payload\n');
|
|
1084
|
-
return 1;
|
|
1085
|
-
}
|
|
1086
|
-
const registry = this._cpRegistry;
|
|
1087
|
-
if (!registry) {
|
|
1088
|
-
hooks.onStderr('child_process: command registry unavailable\n');
|
|
1089
|
-
return 127;
|
|
1090
|
-
}
|
|
1091
|
-
const commandName = normalizeCpCommandName(payload.command);
|
|
1092
|
-
const cmd = await registry.resolve(commandName);
|
|
1093
|
-
if (!cmd) {
|
|
1094
|
-
hooks.onStderr(`${payload.command}: command not found\n`);
|
|
1095
|
-
return 127;
|
|
1096
|
-
}
|
|
1097
|
-
// Synthesize a CommandContext for the internal shell substrate.
|
|
1098
|
-
const stdoutStream = { write: (d) => hooks.onStdout(String(d)) };
|
|
1099
|
-
const stderrStream = { write: (d) => hooks.onStderr(String(d)) };
|
|
1100
|
-
const ac = new AbortController();
|
|
1101
|
-
const cred = this.processes.cred(payload.processPid);
|
|
1102
|
-
const ctx = {
|
|
1103
|
-
pid: payload.processPid,
|
|
1104
|
-
cred,
|
|
1105
|
-
args: payload.args || [],
|
|
1106
|
-
env: payload.env || {},
|
|
1107
|
-
cwd: payload.cwd || '/home/user',
|
|
1108
|
-
vfs: this.sqliteFs.as(cred),
|
|
1109
|
-
stdout: stdoutStream,
|
|
1110
|
-
stderr: stderrStream,
|
|
1111
|
-
signal: ac.signal,
|
|
1112
|
-
// For commands that need stdin we pass a tiny adapter.
|
|
1113
|
-
stdin: staticStdinReader(payload.stdin || ''),
|
|
1114
|
-
setUmask: (mask) => { this.processes.setUmask(payload.processPid, mask); },
|
|
1115
|
-
runAs: async (targetCred, argv) => {
|
|
1116
|
-
if (argv.length === 0)
|
|
1117
|
-
return 0;
|
|
1118
|
-
const child = this.processes.spawn(argv.join(' '), argv, payload.cwd, { parentPid: payload.processPid, cred: targetCred });
|
|
1119
|
-
let exitCode = 1;
|
|
1120
|
-
try {
|
|
1121
|
-
exitCode = await cmdRegistryAdapter.runPureBuiltin(child.pid, argv[0], argv.slice(1), payload.env, payload.cwd, payload.stdin, hooks);
|
|
1122
|
-
return exitCode;
|
|
1123
|
-
}
|
|
1124
|
-
finally {
|
|
1125
|
-
this.processes.exit(child.pid, exitCode);
|
|
1126
|
-
}
|
|
1127
|
-
},
|
|
1128
|
-
__nimbusCaptureOutput: true,
|
|
1129
|
-
};
|
|
1130
|
-
try {
|
|
1131
|
-
const code = await cmd(ctx);
|
|
1132
|
-
return typeof code === 'number' ? code : 0;
|
|
1133
|
-
}
|
|
1134
|
-
catch (e) {
|
|
1135
|
-
hooks.onStderr(`${payload.command}: ${e?.message || String(e)}\n`);
|
|
1136
|
-
return 1;
|
|
1137
|
-
}
|
|
1138
|
-
},
|
|
1139
|
-
abort: (facetName) => {
|
|
1140
|
-
// Best-effort: relay to ctx.facets.abort, mirroring FacetManager.kill.
|
|
1141
|
-
try {
|
|
1142
|
-
this.ctx.facets?.abort?.(facetName, new Error('SIGKILL'));
|
|
1143
|
-
}
|
|
1144
|
-
catch { }
|
|
1145
|
-
return true;
|
|
1146
|
-
},
|
|
1147
|
-
};
|
|
1148
|
-
// Adapter for CommandRegistryLike. The shared shell registry is
|
|
1149
|
-
// attached to `this._cpRegistry` by the shell-init path (see
|
|
1150
|
-
// construction near line 2058 — registry passed as ctor arg there).
|
|
1151
|
-
const cmdRegistryAdapter = {
|
|
1152
|
-
// Consult the live shell registry FIRST so dynamically-registered
|
|
1153
|
-
// commands (registerUnixCommands / git / npm / wrangler etc.) are seen
|
|
1154
|
-
// even if they're not in the static _CP_PURE_BUILTIN allow-list. Falls
|
|
1155
|
-
// back to the static facet-direct table for known facet-only commands.
|
|
1156
|
-
// Returns null
|
|
1157
|
-
// (→ exit 127) for everything unknown.
|
|
1158
|
-
resolve: (name) => {
|
|
1159
|
-
const commandName = normalizeCpCommandName(name);
|
|
1160
|
-
const registry = this._cpRegistry;
|
|
1161
|
-
if (registry && typeof registry.has === 'function' && registry.has(commandName)) {
|
|
1162
|
-
// Registered — classify by name. Reuse the static table so
|
|
1163
|
-
// facet-direct commands (node/npm/git/...) keep their kind
|
|
1164
|
-
// even when they ALSO happen to be registry entries.
|
|
1165
|
-
return _classifyCommand(commandName) || { kind: 'pure-builtin' };
|
|
1166
|
-
}
|
|
1167
|
-
return _classifyCommand(commandName);
|
|
1168
|
-
},
|
|
1169
|
-
runPureBuiltin: async (pid, name, args, env, cwd, stdin, hooks) => {
|
|
1170
|
-
const registry = this._cpRegistry;
|
|
1171
|
-
if (!registry) {
|
|
1172
|
-
hooks.onStderr('cp: registry unavailable\n');
|
|
1173
|
-
return 127;
|
|
1174
|
-
}
|
|
1175
|
-
const commandName = normalizeCpCommandName(name);
|
|
1176
|
-
const cmd = await registry.resolve(commandName);
|
|
1177
|
-
if (!cmd) {
|
|
1178
|
-
hooks.onStderr(`${name}: command not found\n`);
|
|
1179
|
-
return 127;
|
|
1180
|
-
}
|
|
1181
|
-
const cred = this.processes.cred(pid);
|
|
1182
|
-
const ac = new AbortController();
|
|
1183
|
-
const ctx = {
|
|
1184
|
-
pid,
|
|
1185
|
-
cred,
|
|
1186
|
-
args, env, cwd,
|
|
1187
|
-
vfs: this.sqliteFs.as(cred),
|
|
1188
|
-
stdout: { write: (d) => hooks.onStdout(String(d)) },
|
|
1189
|
-
stderr: { write: (d) => hooks.onStderr(String(d)) },
|
|
1190
|
-
signal: ac.signal,
|
|
1191
|
-
stdin: staticStdinReader(stdin),
|
|
1192
|
-
setUmask: (mask) => { this.processes.setUmask(pid, mask); },
|
|
1193
|
-
runAs: async (targetCred, argv) => {
|
|
1194
|
-
if (argv.length === 0)
|
|
1195
|
-
return 0;
|
|
1196
|
-
const child = this.processes.spawn(argv.join(' '), argv, cwd, { parentPid: pid, cred: targetCred });
|
|
1197
|
-
let exitCode = 1;
|
|
1198
|
-
try {
|
|
1199
|
-
exitCode = await cmdRegistryAdapter.runPureBuiltin(child.pid, argv[0], argv.slice(1), env, cwd, stdin, hooks);
|
|
1200
|
-
return exitCode;
|
|
1201
|
-
}
|
|
1202
|
-
finally {
|
|
1203
|
-
this.processes.exit(child.pid, exitCode);
|
|
1204
|
-
}
|
|
1205
|
-
},
|
|
1206
|
-
};
|
|
1207
|
-
try {
|
|
1208
|
-
const code = await cmd(ctx);
|
|
1209
|
-
return typeof code === 'number' ? code : 0;
|
|
1210
|
-
}
|
|
1211
|
-
catch (e) {
|
|
1212
|
-
hooks.onStderr(`${name}: ${e?.message || String(e)}\n`);
|
|
1213
|
-
return 1;
|
|
1214
|
-
}
|
|
1215
|
-
},
|
|
1216
|
-
};
|
|
1217
|
-
// Construct the child-process Loader pool when the binding is available.
|
|
1218
|
-
// Unit-test hosts without LOADER continue through direct dispatch.
|
|
1219
|
-
let spawnPool;
|
|
1220
|
-
try {
|
|
1221
|
-
const envAny = this.env;
|
|
1222
|
-
if (envAny?.LOADER && typeof envAny.LOADER.get === 'function') {
|
|
1223
|
-
spawnPool = new ChildProcessSpawnPool(this.env, this.ctx);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
catch {
|
|
1227
|
-
spawnPool = undefined;
|
|
1228
|
-
}
|
|
1229
|
-
this.facetProcessManager = new FacetProcessManager({
|
|
1230
|
-
facetMgr: facetMgrAdapter,
|
|
1231
|
-
processes: this.processes,
|
|
1232
|
-
vfsForProcess: (pid) => this.sqliteFs.as(this.processes.cred(pid)),
|
|
1233
|
-
commandRegistry: cmdRegistryAdapter,
|
|
1234
|
-
shellExecutor: {
|
|
1235
|
-
execute: async (pid, commandLine, env, cwd, stdin, hooks) => {
|
|
1236
|
-
if (!this.shell) {
|
|
1237
|
-
hooks.onStderr('sh: shell unavailable\n');
|
|
1238
|
-
return 127;
|
|
1239
|
-
}
|
|
1240
|
-
const cred = this.processes.cred(pid);
|
|
1241
|
-
const setUmask = (mask) => { this.processes.setUmask(pid, mask); };
|
|
1242
|
-
const runAs = async (_parent, targetCred, argv) => {
|
|
1243
|
-
if (argv.length === 0)
|
|
1244
|
-
return 0;
|
|
1245
|
-
const child = this.processes.spawn(argv.join(' '), argv, cwd, { parentPid: pid, cred: targetCred });
|
|
1246
|
-
let exitCode = 1;
|
|
1247
|
-
try {
|
|
1248
|
-
exitCode = await cmdRegistryAdapter.runPureBuiltin(child.pid, argv[0], argv.slice(1), env, cwd, stdin, hooks);
|
|
1249
|
-
return exitCode;
|
|
1250
|
-
}
|
|
1251
|
-
finally {
|
|
1252
|
-
this.processes.exit(child.pid, exitCode);
|
|
1253
|
-
}
|
|
1254
|
-
};
|
|
1255
|
-
const result = await this.shell.execute(String(commandLine), {
|
|
1256
|
-
cwd: cwd || '/home/user',
|
|
1257
|
-
env: { ...this.shell.env, ...(env || {}) },
|
|
1258
|
-
onStdout: (d) => hooks.onStdout(String(d)),
|
|
1259
|
-
onStderr: (d) => hooks.onStderr(String(d)),
|
|
1260
|
-
stdin,
|
|
1261
|
-
isolateShellState: true,
|
|
1262
|
-
commandContext: { pid, cred, setUmask },
|
|
1263
|
-
runAs,
|
|
1264
|
-
});
|
|
1265
|
-
return typeof result?.exitCode === 'number' ? result.exitCode : 0;
|
|
1266
|
-
},
|
|
1267
|
-
},
|
|
1268
|
-
ctx: this.ctx,
|
|
1269
|
-
spawnPool,
|
|
1270
|
-
});
|
|
1271
|
-
return this.facetProcessManager;
|
|
1272
|
-
}
|
|
1031
|
+
_ensureFacetProcessManager() { return this.#runtimeServices._ensureFacetProcessManager(); }
|
|
1273
1032
|
/**
|
|
1274
1033
|
* Set the shell command registry for the W8 broker to dispatch
|
|
1275
1034
|
* resolved commands. Called from the shell-init path right after
|
|
@@ -1283,146 +1042,13 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
1283
1042
|
* fetch calls across the lifetime of this DO instance. This prevents
|
|
1284
1043
|
* ephemeral port exhaustion from creating a new worker per fetch.
|
|
1285
1044
|
*/
|
|
1286
|
-
ensureFetchProxy(log) {
|
|
1287
|
-
if (this.fetchProxyEntrypoint)
|
|
1288
|
-
return this.fetchProxyEntrypoint;
|
|
1289
|
-
try {
|
|
1290
|
-
const env = this.env;
|
|
1291
|
-
if (!env?.LOADER?.load) {
|
|
1292
|
-
log?.('LOADER.load not available — using global fetch');
|
|
1293
|
-
return null;
|
|
1294
|
-
}
|
|
1295
|
-
// Buffered proxy: reads the entire response body into an ArrayBuffer
|
|
1296
|
-
// and returns it in ONE message instead of forwarding a ReadableStream.
|
|
1297
|
-
// In workerd local dev, streaming responses across a service-binding
|
|
1298
|
-
// RPC fabric opens a separate loopback socket PER chunk (~16KB), which
|
|
1299
|
-
// exhausts ephemeral ports for larger installs (npm registry packuments
|
|
1300
|
-
// are 500KB-3MB, tarballs up to 5MB). Buffering to arrayBuffer means
|
|
1301
|
-
// 1 stub call = 1 loopback connection, not N connections.
|
|
1302
|
-
//
|
|
1303
|
-
// 32MB cap prevents a malformed giant response from OOMing the proxy
|
|
1304
|
-
// isolate. Packages with tarballs larger than 32MB will fail to install
|
|
1305
|
-
// cleanly (returned as 413 → caller treats as failed fetch).
|
|
1306
|
-
const proxyCode = [
|
|
1307
|
-
'const MAX_BYTES = 32 * 1024 * 1024;',
|
|
1308
|
-
'export default {',
|
|
1309
|
-
' async fetch(request, workerEnv) {',
|
|
1310
|
-
' try {',
|
|
1311
|
-
' const body = await request.json();',
|
|
1312
|
-
' const resp = await fetch(body.url, {',
|
|
1313
|
-
' method: body.method || "GET",',
|
|
1314
|
-
' headers: body.headers || {},',
|
|
1315
|
-
' });',
|
|
1316
|
-
' // Check advertised Content-Length before buffering',
|
|
1317
|
-
' const clStr = resp.headers.get("content-length");',
|
|
1318
|
-
' if (clStr) {',
|
|
1319
|
-
' const cl = parseInt(clStr, 10);',
|
|
1320
|
-
' if (cl > MAX_BYTES) {',
|
|
1321
|
-
' return new Response(',
|
|
1322
|
-
' JSON.stringify({ error: "response too large: " + cl + " bytes (cap " + MAX_BYTES + ")" }),',
|
|
1323
|
-
' { status: 413, headers: { "Content-Type": "application/json" } }',
|
|
1324
|
-
' );',
|
|
1325
|
-
' }',
|
|
1326
|
-
' }',
|
|
1327
|
-
' // Buffer entire body — ONE message, not streamed chunks',
|
|
1328
|
-
' const buf = await resp.arrayBuffer();',
|
|
1329
|
-
' if (buf.byteLength > MAX_BYTES) {',
|
|
1330
|
-
' return new Response(',
|
|
1331
|
-
' JSON.stringify({ error: "response exceeded cap: " + buf.byteLength + " bytes" }),',
|
|
1332
|
-
' { status: 413, headers: { "Content-Type": "application/json" } }',
|
|
1333
|
-
' );',
|
|
1334
|
-
' }',
|
|
1335
|
-
' return new Response(buf, {',
|
|
1336
|
-
' status: resp.status,',
|
|
1337
|
-
' statusText: resp.statusText,',
|
|
1338
|
-
' headers: Object.fromEntries(resp.headers.entries()),',
|
|
1339
|
-
' });',
|
|
1340
|
-
' } catch (e) {',
|
|
1341
|
-
' return new Response(JSON.stringify({ error: e.message }), {',
|
|
1342
|
-
' status: 502,',
|
|
1343
|
-
' headers: { "Content-Type": "application/json" },',
|
|
1344
|
-
' });',
|
|
1345
|
-
' }',
|
|
1346
|
-
' }',
|
|
1347
|
-
'};',
|
|
1348
|
-
].join('\n');
|
|
1349
|
-
const worker = env.LOADER.load({
|
|
1350
|
-
compatibilityDate: CF_COMPAT_DATE,
|
|
1351
|
-
compatibilityFlags: ['nodejs_compat'],
|
|
1352
|
-
mainModule: 'fetch-proxy.js',
|
|
1353
|
-
modules: { 'fetch-proxy.js': proxyCode },
|
|
1354
|
-
});
|
|
1355
|
-
this.fetchProxyEntrypoint = worker.getEntrypoint();
|
|
1356
|
-
log?.('Fetch proxy worker created (singleton)');
|
|
1357
|
-
return this.fetchProxyEntrypoint;
|
|
1358
|
-
}
|
|
1359
|
-
catch (e) {
|
|
1360
|
-
log?.(`Fetch proxy creation failed: ${e?.message}`);
|
|
1361
|
-
return null;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1045
|
+
ensureFetchProxy(log) { return this.#runtimeServices.ensureFetchProxy(log); }
|
|
1364
1046
|
/**
|
|
1365
1047
|
* Build a FetchFn that routes through the singleton proxy entrypoint.
|
|
1366
1048
|
* All concurrent fetches share ONE worker — no port exhaustion.
|
|
1367
1049
|
*/
|
|
1368
|
-
buildFetchFn(log) {
|
|
1369
|
-
|
|
1370
|
-
if (!entrypoint)
|
|
1371
|
-
return undefined;
|
|
1372
|
-
return async (url, init) => {
|
|
1373
|
-
const headers = {};
|
|
1374
|
-
if (init?.headers) {
|
|
1375
|
-
if (init.headers instanceof Headers) {
|
|
1376
|
-
init.headers.forEach((v, k) => { headers[k] = v; });
|
|
1377
|
-
}
|
|
1378
|
-
else if (typeof init.headers === 'object') {
|
|
1379
|
-
Object.assign(headers, init.headers);
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
return entrypoint.fetch(new Request('http://fetch-proxy/do-fetch', {
|
|
1383
|
-
method: 'POST',
|
|
1384
|
-
body: JSON.stringify({ url, method: init?.method || 'GET', headers }),
|
|
1385
|
-
}));
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1388
|
-
async ensureNpmInstaller(onProgress) {
|
|
1389
|
-
this.ensureSqliteFs();
|
|
1390
|
-
if (!this.esbuildService) {
|
|
1391
|
-
if (!this.sqliteFs)
|
|
1392
|
-
throw new Error('Session VFS is not initialized');
|
|
1393
|
-
this.esbuildService = new EsbuildService(this.sqliteFs.as(CRED_KERNEL));
|
|
1394
|
-
}
|
|
1395
|
-
// Lazy-load the installer (+ its ~216 KB resolver/facet/loader-pool
|
|
1396
|
-
// subgraph) on first npm use so it stays out of the cold script-eval
|
|
1397
|
-
// graph. The install command paths that call this are already async.
|
|
1398
|
-
const { NpmInstaller } = await import('../npm/installer.js');
|
|
1399
|
-
// ── Lazy fetch-proxy ────────────────────────────────────────────
|
|
1400
|
-
// The fetch-proxy is a singleton dynamic worker (LOADER.load) that
|
|
1401
|
-
// buffers registry responses to dodge wrangler-local-dev port
|
|
1402
|
-
// exhaustion. It is only needed for the in-supervisor npm paths.
|
|
1403
|
-
// When the resolver and install paths run in facets (default-on),
|
|
1404
|
-
// they use bare globalThis.fetch and need no proxy.
|
|
1405
|
-
//
|
|
1406
|
-
// workerd has a per-DO cap on concurrent dynamic workers (~5-6
|
|
1407
|
-
// empirically). A permanent live proxy worker eats one of those
|
|
1408
|
-
// slots for the entire DO lifetime, so the proxy is built only when
|
|
1409
|
-
// any facet path is disabled via its env flag.
|
|
1410
|
-
const useFacetResolver = this._envFlagDefaultOn('NIMBUS_FACET_RESOLVER');
|
|
1411
|
-
const useFacetInstall = this._envFlagDefaultOn('NIMBUS_FACET_NPM_INSTALL');
|
|
1412
|
-
const useBatchFacet = this._envFlagDefaultOn('NIMBUS_FACET_NPM_INSTALL_BATCH');
|
|
1413
|
-
const needProxy = !(useFacetResolver && useFacetInstall && useBatchFacet);
|
|
1414
|
-
const fetchFn = needProxy ? this.buildFetchFn(onProgress) : undefined;
|
|
1415
|
-
if (!needProxy) {
|
|
1416
|
-
onProgress?.(`[npm] Lazy fetch-proxy: skipped (all facet paths default-on)`);
|
|
1417
|
-
}
|
|
1418
|
-
this.npmInstaller = new NpmInstaller(this.sqliteFs, this.ctx.storage.sql, {
|
|
1419
|
-
esbuild: this.esbuildService,
|
|
1420
|
-
ctx: this.ctx,
|
|
1421
|
-
env: this.env,
|
|
1422
|
-
onProgress,
|
|
1423
|
-
fetchFn,
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1050
|
+
buildFetchFn(log) { return this.#runtimeServices.buildFetchFn(log); }
|
|
1051
|
+
async ensureNpmInstaller(onProgress) { return this.#runtimeServices.ensureNpmInstaller(onProgress); }
|
|
1426
1052
|
/**
|
|
1427
1053
|
* Read an environment flag with default-on semantics. Mirrors the
|
|
1428
1054
|
* shouldUseFacetPool / shouldUseFacetResolver / shouldUseBatchFacet
|
|
@@ -1430,15 +1056,7 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
1430
1056
|
* lazy-proxy decision uses identical semantics without leaking that
|
|
1431
1057
|
* private API across modules.
|
|
1432
1058
|
*/
|
|
1433
|
-
_envFlagDefaultOn(name) {
|
|
1434
|
-
const raw = this.env?.[name];
|
|
1435
|
-
if (raw === undefined || raw === null)
|
|
1436
|
-
return true;
|
|
1437
|
-
const s = String(raw).toLowerCase();
|
|
1438
|
-
if (s === '0' || s === '' || s === 'false' || s === 'off' || s === 'no')
|
|
1439
|
-
return false;
|
|
1440
|
-
return true;
|
|
1441
|
-
}
|
|
1059
|
+
_envFlagDefaultOn(name) { return this.#runtimeServices._envFlagDefaultOn(name); }
|
|
1442
1060
|
// ── Session initialization ────────────────────────────────────────────
|
|
1443
1061
|
// ── Session initialization ────────────────────────────────────────────
|
|
1444
1062
|
//
|
|
@@ -1446,7 +1064,7 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
1446
1064
|
// command registrations + boot wiring). The class retains `initSession`
|
|
1447
1065
|
// as a delegator per plan §IX.4 R1. Visibility relaxed (was `private`)
|
|
1448
1066
|
// so the SessionInternal interface declares it.
|
|
1449
|
-
initSession(ws) {
|
|
1067
|
+
initSession(ws, options) {
|
|
1450
1068
|
// A destroyed session id being legitimately re-initialized (shell WS
|
|
1451
1069
|
// attach, or SDK ready via ensureProgrammaticReady which routes here)
|
|
1452
1070
|
// lifts the tombstone so the recreated session's log-janitor can arm
|
|
@@ -1455,22 +1073,16 @@ export class NimbusSession extends CloudflareDurableObject {
|
|
|
1455
1073
|
// Cast pattern (per plan §IX recommendation 1, used here only because
|
|
1456
1074
|
// initSession reads this.ctx + this.env extensively; siblings that need
|
|
1457
1075
|
// ctx/env take them as separate explicit args per DEFECT-D1).
|
|
1458
|
-
return _w11InitSession(this, ws);
|
|
1076
|
+
return _w11InitSession(this, ws, options);
|
|
1459
1077
|
}
|
|
1078
|
+
/**
|
|
1079
|
+
* The rebuild in flight for a shell socket that woke this instance from
|
|
1080
|
+
* hibernation, so every frame that arrives while it runs awaits the one
|
|
1081
|
+
* build instead of starting its own. See session/ws.ts bindShellSocket.
|
|
1082
|
+
*/
|
|
1083
|
+
_wakeRebuild = null;
|
|
1460
1084
|
// ── Filesystem seeding ────────────────────────────────────────────────
|
|
1461
|
-
ensureGlobalPrefixDirs(prefix) {
|
|
1462
|
-
const fs = this.sqliteFs.as(CRED_SESSION_USER);
|
|
1463
|
-
const dirs = [
|
|
1464
|
-
prefix,
|
|
1465
|
-
`${prefix}/lib`,
|
|
1466
|
-
`${prefix}/lib/node_modules`,
|
|
1467
|
-
`${prefix}/bin`,
|
|
1468
|
-
];
|
|
1469
|
-
for (const dir of dirs) {
|
|
1470
|
-
if (!fs.exists(dir))
|
|
1471
|
-
fs.mkdir(dir, { recursive: true });
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1085
|
+
ensureGlobalPrefixDirs(prefix) { return this.#runtimeServices.ensureGlobalPrefixDirs(prefix); }
|
|
1474
1086
|
/**
|
|
1475
1087
|
* The starter content a fresh Nimbus session shows a user: the banner, the
|
|
1476
1088
|
* sample files, and the Vite starter app.
|