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