@nimbus-sh/worker 0.2.3 → 0.4.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/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/esbuild-wasm-bundle.generated.d.ts +1 -1
- package/dist/esbuild-wasm-bundle.generated.js +1 -1
- package/dist/facets/cirrus-real.js +1 -1
- package/dist/facets/manager.d.ts +43 -129
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +107 -318
- 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 +3 -0
- package/dist/facets/real-vite-hmr.js +1 -1
- package/dist/facets/vite-dev-server.d.ts +4 -4
- package/dist/facets/vite-dev-server.js +5 -5
- 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 +39 -15
- 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.d.ts.map +1 -1
- package/dist/index.js +9 -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 +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 -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.d.ts +1 -1
- package/dist/npm/install-batch-facet.js +1 -1
- package/dist/npm/installer.d.ts +6 -6
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +28 -27
- package/dist/npm/pre-bundle-facet.d.ts +2 -2
- package/dist/npm/pre-bundle-facet.js +5 -5
- package/dist/npm/r2-cache.d.ts +1 -1
- 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 +2 -2
- package/dist/router/remote-api.js +9 -1
- package/dist/runtime/bun-repl.d.ts +1 -1
- package/dist/runtime/bun-repl.js +3 -3
- package/dist/runtime/esbuild-wasm-bytes.js +1 -1
- 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/node-shims-artifact.js +1 -1
- package/dist/runtime/node-shims.js +2 -1
- package/dist/runtime/opencode-artifact.js +1 -1
- package/dist/runtime/opentui-wasm-bytes.js +1 -1
- 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/runtime/sqlite-wasm-bytes.js +1 -1
- package/dist/session/diag.js +1 -1
- package/dist/session/hibernation.d.ts +20 -53
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +57 -194
- package/dist/session/init-phases.d.ts +2 -3
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +3 -2
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +8 -4
- package/dist/session/keys.d.ts +14 -37
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +18 -37
- package/dist/session/nimbus-session.d.ts +10 -15
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +28 -31
- 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 +39 -5
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +150 -19
- package/dist/session/routes.d.ts +2 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +96 -20
- package/dist/session/rpc.d.ts +36 -9
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +84 -24
- package/dist/session/start-real-vite.d.ts.map +1 -1
- package/dist/session/start-real-vite.js +3 -1
- 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 +7 -4
- package/dist/session/ws.d.ts +1 -3
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +4 -3
- package/dist/wrangler/nimbus-wrangler.js +1 -1
- package/package.json +4 -2
- package/scripts/bundle-esbuild-wasm.mjs +2 -2
- package/scripts/bundle-facet-workers.mjs +5 -7
- package/scripts/bundle-sqlite-wasm.mjs +1 -1
- 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,26 +22,30 @@ 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
|
-
import { recordFailure, getLastRpcFrame, getLastFacetId } from '@nimbus-sh/
|
|
29
|
-
import { classifyError } from '@nimbus-sh/
|
|
30
|
-
import {
|
|
29
|
+
import { recordFailure, getLastRpcFrame, getLastFacetId } from '@nimbus-sh/platform/oom-discriminator.js';
|
|
30
|
+
import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
|
|
31
|
+
import { TurnBudget, PacedWork, turnChunkMaxBytes } from '@nimbus-sh/fabric/turn-budget.js';
|
|
32
|
+
import { onColdStart } from '@nimbus-sh/fabric/generation.js';
|
|
33
|
+
import { FencedWork, } from '@nimbus-sh/fabric/fenced-work.js';
|
|
31
34
|
import { EsbuildService } from '@nimbus-sh/core/runtime/esbuild-service.js';
|
|
32
35
|
import { isExecDiagEnabled, recordExecTelemetry } from './exec-telemetry.js';
|
|
33
|
-
import { disposeRpcResource, disposeRpcResources } from '@nimbus-sh/
|
|
36
|
+
import { disposeRpcResource, disposeRpcResources } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
34
37
|
import { sqliteWasmModuleEntry } from './opencode-staging.js';
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
38
|
+
import { ImageStore, } from '@nimbus-sh/fabric/image-store.js';
|
|
39
|
+
import { ProcessFabric, } from '@nimbus-sh/fabric/process-fabric.js';
|
|
40
|
+
import { createLoadedWorkerEntrypoint, getNimbusCtxExports, } from '@nimbus-sh/fabric/workerd-facet-host.js';
|
|
37
41
|
import { SQLITE_WASM_MODULE_NAME, } from '../runtime/opencode-facet-runner.js';
|
|
38
42
|
import { parsePortFromArgv, resolveLongRunningPort } from '@nimbus-sh/core/runtime/long-running-handle.js';
|
|
39
43
|
import { DEFAULT_FACET_BUNDLE_PROFILE, } from '@nimbus-sh/core/runtime/bundle-profile.js';
|
|
40
|
-
import { BUNDLE_BUILD_DEADLINE_MS, CF_COMPAT_DATE,
|
|
41
|
-
import {
|
|
44
|
+
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, PREFETCH_CACHE_MAX_BYTES, } from '@nimbus-sh/core/constants.js';
|
|
45
|
+
import { MAX_RPC_SAFE_PAYLOAD_BYTES } from '@nimbus-sh/platform/limits.js';
|
|
42
46
|
import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
43
|
-
import { acquireSupervisorAllocation } from '@nimbus-sh/
|
|
44
|
-
import { prefetchBundleStart, prefetchBundleEnd, setPrefetchCacheBytes, } from '@nimbus-sh/
|
|
47
|
+
import { acquireSupervisorAllocation } from '@nimbus-sh/platform/heavy-alloc-coord.js';
|
|
48
|
+
import { prefetchBundleStart, prefetchBundleEnd, setPrefetchCacheBytes, } from '@nimbus-sh/platform/diag-counters.js';
|
|
45
49
|
/**
|
|
46
50
|
* Detect & restore a Uint8Array that's been JSON-mangled to a
|
|
47
51
|
* {"0":n,"1":n,...} object during the result-envelope round-trip.
|
|
@@ -3147,19 +3151,11 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3147
3151
|
};
|
|
3148
3152
|
}
|
|
3149
3153
|
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;
|
|
3154
|
+
/** 'running' is the measured common case: the platform's reset strikes
|
|
3155
|
+
* seconds after a launch settles, while the resident runs. */
|
|
3156
|
+
function residentLaunchDoing(record) {
|
|
3157
|
+
return record.phase === 'running' ? 'running' : 'starting';
|
|
3158
|
+
}
|
|
3163
3159
|
export class FacetManager {
|
|
3164
3160
|
ctx;
|
|
3165
3161
|
env;
|
|
@@ -3183,29 +3179,25 @@ export class FacetManager {
|
|
|
3183
3179
|
/** NIMBUS_DEBUG=1: placement diagnostics into the process log store. */
|
|
3184
3180
|
debugEnabled = false;
|
|
3185
3181
|
processRpcResources = new Map();
|
|
3186
|
-
/** pid → the boot images its facet loads from; the image sweep's root set. */
|
|
3187
|
-
residentImages = new Map();
|
|
3188
3182
|
/**
|
|
3189
|
-
*
|
|
3190
|
-
*
|
|
3191
|
-
*
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
*
|
|
3196
|
-
*
|
|
3183
|
+
* The content-addressed boot-image store (fabric's image-store.ts),
|
|
3184
|
+
* writing through this session's kernel-credentialed VFS and rooted off the
|
|
3185
|
+
* live process table.
|
|
3186
|
+
*/
|
|
3187
|
+
imageStore = new ImageStore(() => this._imageBlobs(), (pid) => this.processes.get(pid)?.state === 'running');
|
|
3188
|
+
/**
|
|
3189
|
+
* The resident-launch journal (fabric's fenced-work.ts): the durable
|
|
3190
|
+
* record of every resident this session owes the user, and its recovery
|
|
3191
|
+
* after an instance reset. This manager supplies what a launch IS — the
|
|
3192
|
+
* inputs `_spawnResident` re-drives from — and how its loss is reported.
|
|
3197
3193
|
*/
|
|
3198
|
-
|
|
3199
|
-
/** Whether this instance has already read the journal a reset leaves behind. */
|
|
3200
|
-
launchesRecovered = false;
|
|
3194
|
+
launchJournal;
|
|
3201
3195
|
/**
|
|
3202
|
-
*
|
|
3203
|
-
*
|
|
3204
|
-
*
|
|
3205
|
-
* correct: rows from a previous instance are recovery's to consume, never
|
|
3206
|
-
* this hook's.
|
|
3196
|
+
* The granting side of the launch budget (fabric's turn-budget.ts). The
|
|
3197
|
+
* session's alarm re-enters the object through `pumpResidentLaunches`;
|
|
3198
|
+
* journal recovery rides the first pump.
|
|
3207
3199
|
*/
|
|
3208
|
-
|
|
3200
|
+
launchPump;
|
|
3209
3201
|
timedOutProcessIds = new Set();
|
|
3210
3202
|
// attach-pid → serve-pid: the resident serve facet a bare-`opencode` dual
|
|
3211
3203
|
// spawn created as an OS-child of the attach TUI. When the attach process
|
|
@@ -3276,42 +3268,65 @@ export class FacetManager {
|
|
|
3276
3268
|
? Reflect.get(env, 'NIMBUS_DEBUG')
|
|
3277
3269
|
: undefined;
|
|
3278
3270
|
this.debugEnabled = debugVar === '1' || debugVar === 'true';
|
|
3271
|
+
this.launchJournal = new FencedWork(ctx.storage, {
|
|
3272
|
+
generationBase: () => this.processes.pidBase,
|
|
3273
|
+
waitUntil: (promise) => this.ctx.waitUntil(promise),
|
|
3274
|
+
redrive: (record, attempt) => this._spawnResident(record.code, record.opts, attempt),
|
|
3275
|
+
onRedrive: (record) => this.hooks.notify?.('\x1b[2m[nimbus: the session restarted while '
|
|
3276
|
+
+ `"${record.command}" was ${residentLaunchDoing(record)} — restarting it]\x1b[0m\r\n`),
|
|
3277
|
+
onAbandoned: (record) => this.hooks.notify?.('\x1b[2m[nimbus: the session restarted again while '
|
|
3278
|
+
+ `"${record.command}" was ${residentLaunchDoing(record)} — leaving it stopped]\x1b[0m\r\n`),
|
|
3279
|
+
onRedriveFailed: (record, e) => this.hooks.notify?.(`\x1b[2m[nimbus: "${record.command}" could not be restarted: `
|
|
3280
|
+
+ `${errorMessage(e)}]\x1b[0m\r\n`),
|
|
3281
|
+
});
|
|
3282
|
+
this.launchPump = new PacedWork(ctx, {
|
|
3283
|
+
requestTurn: hooks.requestLaunchTurn?.bind(hooks),
|
|
3284
|
+
});
|
|
3285
|
+
// A reset that killed a resident launch left journal rows behind; the
|
|
3286
|
+
// first pump after the reset drains this reconciliation before any
|
|
3287
|
+
// waiter resumes, OFF the constructor's init gate.
|
|
3288
|
+
onColdStart(ctx, () => this.launchJournal.recoverInterrupted());
|
|
3279
3289
|
// The journal row of a resident lives for the PROCESS's lifetime, so its
|
|
3280
3290
|
// release belongs on the one seam every end-of-life passes through —
|
|
3281
3291
|
// exit, kill, self-reported exit and timeout abort all mark the table.
|
|
3282
3292
|
// Rooted on waitUntil: the hook fires synchronously inside whatever turn
|
|
3283
3293
|
// ended the process, and the delete must not be a floating promise there.
|
|
3284
3294
|
this.processes.setOnTerminal((pid) => {
|
|
3285
|
-
this.ctx.waitUntil(this.
|
|
3295
|
+
this.ctx.waitUntil(this.launchJournal.release(pid));
|
|
3286
3296
|
});
|
|
3287
3297
|
}
|
|
3288
3298
|
setVfs(vfs) { this.vfs = vfs; }
|
|
3289
3299
|
/**
|
|
3290
|
-
* The
|
|
3291
|
-
*
|
|
3292
|
-
*
|
|
3293
|
-
*
|
|
3294
|
-
* built from: the root listing gained an entry the moment an image landed,
|
|
3295
|
-
* so the next spawn of an identical program generated different text and
|
|
3296
|
-
* addressed a different image. Existing before the first walk makes it
|
|
3297
|
-
* stable.
|
|
3298
|
-
*
|
|
3299
|
-
* Sited on the exec path and not in setVfs, because setVfs runs while the
|
|
3300
|
-
* Durable Object is coming up — including on every wake — and a synchronous
|
|
3301
|
-
* filesystem write there costs the session its startup. Measured: a
|
|
3302
|
-
* throwaway built that way stopped accepting terminal connections at all,
|
|
3303
|
-
* while the same build without it served them.
|
|
3300
|
+
* The env/ctx pair every loader-backed runtime builds its facet pools
|
|
3301
|
+
* from. A pool is constructed from exactly these two, so the manager
|
|
3302
|
+
* exposes them as one narrow accessor rather than every runtime reaching
|
|
3303
|
+
* into its private fields.
|
|
3304
3304
|
*/
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3305
|
+
loaderHost() {
|
|
3306
|
+
return { env: this.env, ctx: this.ctx };
|
|
3307
|
+
}
|
|
3308
|
+
/**
|
|
3309
|
+
* The image store's disk: this session's VFS, as the kernel — the store is
|
|
3310
|
+
* written by the kernel and read by processes through supervisor bindings
|
|
3311
|
+
* that enforce their own credential. Mode 0644 at creation, as POSIX has
|
|
3312
|
+
* it, is what makes the read succeed for any process by construction; the
|
|
3313
|
+
* store itself decides nothing about modes.
|
|
3314
|
+
*/
|
|
3315
|
+
_imageBlobs() {
|
|
3316
|
+
const vfs = this.vfs;
|
|
3317
|
+
if (!vfs) {
|
|
3318
|
+
throw new Error('Nimbus: a resident process needs a session filesystem to materialize its boot image');
|
|
3311
3319
|
}
|
|
3312
|
-
|
|
3320
|
+
const fs = vfs.as(CRED_KERNEL);
|
|
3321
|
+
return {
|
|
3322
|
+
mkdirp: (dir) => fs.mkdir(dir, { recursive: true, mode: 0o755 }),
|
|
3323
|
+
sizeOf: (path) => (fs.exists(path) ? fs.lstat(path).size : null),
|
|
3324
|
+
writeFile: (path, bytes) => fs.writeFile(path, bytes, { mode: 0o644 }),
|
|
3325
|
+
writeRange: (path, offset, bytes) => fs.writeRange(path, offset, bytes),
|
|
3326
|
+
list: (dir) => fs.readdir(dir).map((entry) => entry.name),
|
|
3327
|
+
unlink: (path) => fs.unlink(path),
|
|
3328
|
+
};
|
|
3313
3329
|
}
|
|
3314
|
-
imageStoreDirReady = false;
|
|
3315
3330
|
/**
|
|
3316
3331
|
* W3.5 Fix B: hand the FacetManager a pre-warmed EsbuildService for
|
|
3317
3332
|
* the ESM→CJS bundle pre-pass. NimbusSession already lazy-creates one
|
|
@@ -3619,7 +3634,7 @@ export class FacetManager {
|
|
|
3619
3634
|
const diagOn = isExecDiagEnabled();
|
|
3620
3635
|
const __bundleStart = diagOn ? Date.now() : 0;
|
|
3621
3636
|
if (this.vfs)
|
|
3622
|
-
this.
|
|
3637
|
+
this.imageStore.ensureDir();
|
|
3623
3638
|
const processVfs = this.vfs?.as(entry.cred);
|
|
3624
3639
|
const credKey = `${entry.cred.uid}:${entry.cred.gid}:${entry.cred.groups.join(',')}`;
|
|
3625
3640
|
const vfsState = processVfs
|
|
@@ -3929,7 +3944,7 @@ export class FacetManager {
|
|
|
3929
3944
|
// home dirs (~/.local/share/opencode, …) via fs.promises.mkdir; those and
|
|
3930
3945
|
// other writes flush live through the SUPERVISOR RPC bridge.
|
|
3931
3946
|
if (this.vfs)
|
|
3932
|
-
this.
|
|
3947
|
+
this.imageStore.ensureDir();
|
|
3933
3948
|
const processVfs = this.vfs?.as(entry.cred);
|
|
3934
3949
|
const vfsState = processVfs
|
|
3935
3950
|
? await buildPrefetchBundle(processVfs, undefined, opts.cwd, '', this.esbuild || undefined)
|
|
@@ -4138,6 +4153,7 @@ export class FacetManager {
|
|
|
4138
4153
|
.finally(() => {
|
|
4139
4154
|
this.releaseProcessRpcResources(pid);
|
|
4140
4155
|
}));
|
|
4156
|
+
await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, port);
|
|
4141
4157
|
this.portRegistry.register(port, pid);
|
|
4142
4158
|
return { pid, exitCode: 0, stdout: '', stderr: '', vfsWrites: {} };
|
|
4143
4159
|
}
|
|
@@ -4174,7 +4190,7 @@ export class FacetManager {
|
|
|
4174
4190
|
* The reader the fabric completes a boot spec's by-path members with.
|
|
4175
4191
|
*
|
|
4176
4192
|
* Reads as CRED_KERNEL because that is who WROTE them: the generated images
|
|
4177
|
-
* are kernel-owned (`
|
|
4193
|
+
* are kernel-owned (`_imageBlobs`) and the runtime wasm images
|
|
4178
4194
|
* are installed by the kernel. Uncached because these are the session's
|
|
4179
4195
|
* largest files — a ruby interpreter image is 34.3 MiB — and pinning one in
|
|
4180
4196
|
* the VFS content LRU for the life of the session is what once crashed the
|
|
@@ -4188,82 +4204,11 @@ export class FacetManager {
|
|
|
4188
4204
|
const fs = vfs.as(CRED_KERNEL);
|
|
4189
4205
|
return { readFile: (path) => fs.readFileUncached(path) };
|
|
4190
4206
|
}
|
|
4191
|
-
/**
|
|
4192
|
-
* Materialize generated module sources in the content-addressed image store
|
|
4193
|
-
* and return the `vfsTextModules` map naming them.
|
|
4194
|
-
*
|
|
4195
|
-
* A resident process's module map is sized by the user's disk, so it does
|
|
4196
|
-
* not ride inside the boot spec — see ResidentCodeSpec.vfsTextModules.
|
|
4197
|
-
* Writing it here, once, is what lets the session stop holding it: after
|
|
4198
|
-
* this returns, the only thing it keeps is a path.
|
|
4199
|
-
*
|
|
4200
|
-
* The store is written by the kernel and read by the process, so nothing
|
|
4201
|
-
* here depends on which credential spawned what. Digest collisions are the
|
|
4202
|
-
* hash's problem; everything else is idempotent — an image already present
|
|
4203
|
-
* at its own digest is already the bytes we were about to write.
|
|
4204
|
-
*/
|
|
4205
|
-
async _materializeFacetImages(pid, modules, pacer) {
|
|
4206
|
-
const vfs = this.vfs;
|
|
4207
|
-
if (!vfs) {
|
|
4208
|
-
throw new Error('Nimbus: a resident process needs a session filesystem to materialize its boot image');
|
|
4209
|
-
}
|
|
4210
|
-
const fs = vfs.as(CRED_KERNEL);
|
|
4211
|
-
const images = {};
|
|
4212
|
-
const sources = new Map();
|
|
4213
|
-
for (const [moduleName, source] of Object.entries(modules)) {
|
|
4214
|
-
const path = facetImagePath(await facetImageDigest(source));
|
|
4215
|
-
images[moduleName] = path;
|
|
4216
|
-
sources.set(path, source);
|
|
4217
|
-
await pacer.spend(source.length);
|
|
4218
|
-
}
|
|
4219
|
-
// Register the WHOLE root set here, in one synchronous step, before any
|
|
4220
|
-
// byte of it exists on disk. That ordering is the entire protocol between
|
|
4221
|
-
// a launch and the sweep: an image is rooted from before it is written, so
|
|
4222
|
-
// a sweep can never observe a file this launch has written but not yet
|
|
4223
|
-
// claimed. The old loop achieved it by not awaiting at all, which read as
|
|
4224
|
-
// "the writes must not be interrupted" — they may be. What must not be
|
|
4225
|
-
// interrupted is the gap between writing and rooting, and rooting first
|
|
4226
|
-
// closes it for every write that follows, however many turns they span.
|
|
4227
|
-
this.residentImages.set(pid, [...sources.keys()]);
|
|
4228
|
-
fs.mkdir(FACET_IMAGE_DIR, { recursive: true, mode: 0o755 });
|
|
4229
|
-
for (const [path, source] of sources) {
|
|
4230
|
-
const stored = path.replace(/^\/+/, '');
|
|
4231
|
-
const bytes = new TextEncoder().encode(source);
|
|
4232
|
-
// An image at its full size is a COMPLETE one: a write only ever grows
|
|
4233
|
-
// the file from offset zero, so a write cut short by a reset leaves a
|
|
4234
|
-
// strictly shorter file and fails this test. Size is enough of a check
|
|
4235
|
-
// because the reader verifies the digest before the loader sees it.
|
|
4236
|
-
if (fs.exists(stored) && fs.lstat(stored).size === bytes.byteLength) {
|
|
4237
|
-
await pacer.spend(bytes.byteLength);
|
|
4238
|
-
continue;
|
|
4239
|
-
}
|
|
4240
|
-
// Sliced because the platform resets the object over what ONE TURN has
|
|
4241
|
-
// outstanding, not over what it eventually writes — pi's 22.9 MB map
|
|
4242
|
-
// went in as a single write and took the session down with it ~25% of
|
|
4243
|
-
// the time. Spending between slices is what puts the rest of the image
|
|
4244
|
-
// on later turns; the slice bound is what keeps any one of them small.
|
|
4245
|
-
let offset = 0;
|
|
4246
|
-
do {
|
|
4247
|
-
const slice = bytes.subarray(offset, offset + FACET_IMAGE_WRITE_SLICE_BYTES);
|
|
4248
|
-
// The first slice REPLACES the file, so an interrupted write's remains
|
|
4249
|
-
// are truncated to a known length rather than left as a tail past this
|
|
4250
|
-
// content — and the mode is chosen here, at creation, as POSIX has it.
|
|
4251
|
-
if (offset === 0)
|
|
4252
|
-
fs.writeFile(stored, slice, { mode: 0o644 });
|
|
4253
|
-
else
|
|
4254
|
-
fs.writeRange(stored, offset, slice);
|
|
4255
|
-
offset += slice.byteLength;
|
|
4256
|
-
await pacer.spend(slice.byteLength);
|
|
4257
|
-
} while (offset < bytes.byteLength);
|
|
4258
|
-
}
|
|
4259
|
-
this._sweepFacetImages(fs);
|
|
4260
|
-
return images;
|
|
4261
|
-
}
|
|
4262
4207
|
/**
|
|
4263
4208
|
* Fail a paced launch whose process went away while it was suspended.
|
|
4264
4209
|
*
|
|
4265
|
-
* Between turns anything may happen to the process — a kill, a reap,
|
|
4266
|
-
*
|
|
4210
|
+
* Between turns anything may happen to the process — a kill, a reap, an
|
|
4211
|
+
* image sweep that has already unrooted its images. Continuing would
|
|
4267
4212
|
* spend further turns building a facet for a pid nothing will ever attach
|
|
4268
4213
|
* to, and would write image files the next sweep immediately collects.
|
|
4269
4214
|
*/
|
|
@@ -4272,44 +4217,6 @@ export class FacetManager {
|
|
|
4272
4217
|
return;
|
|
4273
4218
|
throw new Error(`Nimbus: resident launch for pid ${pid} was cancelled while it was suspended`);
|
|
4274
4219
|
}
|
|
4275
|
-
/**
|
|
4276
|
-
* Drop every image no running process boots from.
|
|
4277
|
-
*
|
|
4278
|
-
* Content addressing means a changed program writes a NEW image rather than
|
|
4279
|
-
* replacing one, so a watch loop — or simply a session that runs a few
|
|
4280
|
-
* different programs — would otherwise leave one bundle-sized file behind
|
|
4281
|
-
* per distinct version. The root set is the process table, which is exact:
|
|
4282
|
-
* an image is live for precisely as long as the process that boots from it.
|
|
4283
|
-
* Nothing is left for a TTL or an eviction heuristic to guess at, and after
|
|
4284
|
-
* a DO reset the table is empty so every orphan goes.
|
|
4285
|
-
*/
|
|
4286
|
-
_sweepFacetImages(fs) {
|
|
4287
|
-
const live = new Set();
|
|
4288
|
-
for (const [pid, paths] of this.residentImages) {
|
|
4289
|
-
if (this.processes.get(pid)?.state === 'running') {
|
|
4290
|
-
for (const path of paths)
|
|
4291
|
-
live.add(path);
|
|
4292
|
-
}
|
|
4293
|
-
else {
|
|
4294
|
-
this.residentImages.delete(pid);
|
|
4295
|
-
}
|
|
4296
|
-
}
|
|
4297
|
-
let entries;
|
|
4298
|
-
try {
|
|
4299
|
-
entries = fs.readdir(FACET_IMAGE_DIR);
|
|
4300
|
-
}
|
|
4301
|
-
catch {
|
|
4302
|
-
return;
|
|
4303
|
-
}
|
|
4304
|
-
for (const entry of entries) {
|
|
4305
|
-
if (live.has(`/${FACET_IMAGE_DIR}/${entry.name}`))
|
|
4306
|
-
continue;
|
|
4307
|
-
try {
|
|
4308
|
-
fs.unlink(`${FACET_IMAGE_DIR}/${entry.name}`);
|
|
4309
|
-
}
|
|
4310
|
-
catch { /* already gone */ }
|
|
4311
|
-
}
|
|
4312
|
-
}
|
|
4313
4220
|
/**
|
|
4314
4221
|
* The one way this manager boots a resident process. Every resident process
|
|
4315
4222
|
* is a facet of this session; there is nothing to place and nothing here
|
|
@@ -4340,125 +4247,16 @@ export class FacetManager {
|
|
|
4340
4247
|
this.vfs?.activateAppendWriter(pid, writerId);
|
|
4341
4248
|
}
|
|
4342
4249
|
/**
|
|
4343
|
-
*
|
|
4344
|
-
*
|
|
4345
|
-
*
|
|
4346
|
-
* is genuinely a new invocation — the session's alarm. Without such a host
|
|
4347
|
-
* there is no fresh turn to be had, and the launch continues on this one
|
|
4348
|
-
* rather than hanging: that is exactly the single-turn launch this path has
|
|
4349
|
-
* always performed, so a harness or a runtime without alarms loses the
|
|
4350
|
-
* responsiveness but keeps the behaviour.
|
|
4351
|
-
*/
|
|
4352
|
-
launchScheduler = {
|
|
4353
|
-
nextTurn: (chunkEnded) => new Promise((resume) => {
|
|
4354
|
-
this.launchWaiters.push({ resume, chunkEnded });
|
|
4355
|
-
if (this.hooks.requestLaunchTurn) {
|
|
4356
|
-
this.hooks.requestLaunchTurn();
|
|
4357
|
-
return;
|
|
4358
|
-
}
|
|
4359
|
-
setTimeout(() => { void this.pumpResidentLaunches(); }, 0);
|
|
4360
|
-
}),
|
|
4361
|
-
};
|
|
4362
|
-
/**
|
|
4363
|
-
* Run one chunk of every launch waiting for a turn.
|
|
4364
|
-
*
|
|
4365
|
-
* Awaits the chunk each resumed launch then performs, so the invocation that
|
|
4366
|
-
* granted the turn is the invocation that pays for the work — rather than
|
|
4367
|
-
* releasing it into a handler's microtask drain, where nothing owns it and
|
|
4368
|
-
* the runtime may tear the context down mid-chunk.
|
|
4369
|
-
*/
|
|
4370
|
-
async pumpResidentLaunches() {
|
|
4371
|
-
await this._recoverInterruptedLaunches();
|
|
4372
|
-
const waiting = this.launchWaiters;
|
|
4373
|
-
if (waiting.length === 0)
|
|
4374
|
-
return;
|
|
4375
|
-
this.launchWaiters = [];
|
|
4376
|
-
for (const waiter of waiting)
|
|
4377
|
-
waiter.resume();
|
|
4378
|
-
await Promise.all(waiting.map((waiter) => waiter.chunkEnded));
|
|
4379
|
-
}
|
|
4380
|
-
/**
|
|
4381
|
-
* Re-drive the launches a previous instance was building when it was reset.
|
|
4382
|
-
*
|
|
4383
|
-
* The platform resets a session Durable Object over what one turn has
|
|
4384
|
-
* outstanding in storage ("Internal error in Durable Object storage caused
|
|
4385
|
-
* object to be reset"), and a launch is the largest writer this session has.
|
|
4386
|
-
* Everything the launch held was in memory, so the process it was building
|
|
4387
|
-
* and the terminal watching it both went with the instance, and until now
|
|
4388
|
-
* the user was told nothing at all.
|
|
4389
|
-
*
|
|
4390
|
-
* Sited on the pump because the pump is what an alarm calls, and a launch
|
|
4391
|
-
* that was suspended has an alarm armed for it — a reset during a chunk
|
|
4392
|
-
* fails that alarm, and the platform re-delivers it to the instance that
|
|
4393
|
-
* replaces this one. So the first turn after a reset is already this one.
|
|
4394
|
-
*
|
|
4395
|
-
* Runs once per instance: the journal only changes when a launch of THIS
|
|
4396
|
-
* instance starts or settles, and those are rows this instance wrote.
|
|
4397
|
-
*/
|
|
4398
|
-
async _recoverInterruptedLaunches() {
|
|
4399
|
-
if (this.launchesRecovered)
|
|
4400
|
-
return;
|
|
4401
|
-
this.launchesRecovered = true;
|
|
4402
|
-
const journal = await this.ctx.storage.list({
|
|
4403
|
-
prefix: RESIDENT_LAUNCH_KEY_PREFIX,
|
|
4404
|
-
});
|
|
4405
|
-
for (const [key, record] of journal) {
|
|
4406
|
-
// A pid at or below this instance's base was allocated by a PREVIOUS one
|
|
4407
|
-
// (process-table.ts, PID_GEN_STRIDE), so its launch never finished; above
|
|
4408
|
-
// the base is this instance's own, still running. Same predicate as
|
|
4409
|
-
// `session/rpc.ts` uses to attribute a prior generation's pid.
|
|
4410
|
-
if (!(record.pid > 0 && record.pid <= this.processes.pidBase))
|
|
4411
|
-
continue;
|
|
4412
|
-
await this.ctx.storage.delete(key);
|
|
4413
|
-
// 'running' is the measured common case: the platform's reset strikes
|
|
4414
|
-
// seconds after a launch settles, while the resident runs.
|
|
4415
|
-
const doing = record.phase === 'running' ? 'running' : 'starting';
|
|
4416
|
-
if (record.attempt >= RESIDENT_LAUNCH_MAX_ATTEMPT) {
|
|
4417
|
-
this.hooks.notify?.('\x1b[2m[nimbus: the session restarted again while '
|
|
4418
|
-
+ `"${record.command}" was ${doing} — leaving it stopped]\x1b[0m\r\n`);
|
|
4419
|
-
continue;
|
|
4420
|
-
}
|
|
4421
|
-
this.hooks.notify?.('\x1b[2m[nimbus: the session restarted while '
|
|
4422
|
-
+ `"${record.command}" was ${doing} — restarting it]\x1b[0m\r\n`);
|
|
4423
|
-
// Not awaited: this call is running inside the alarm that granted the
|
|
4424
|
-
// turn, and the launch it starts asks for turns of its own through that
|
|
4425
|
-
// same alarm — awaiting it here would be waiting on an alarm that cannot
|
|
4426
|
-
// be scheduled until this one returns.
|
|
4427
|
-
this.ctx.waitUntil(this._spawnResident(record.code, record.opts, record.attempt + 1)
|
|
4428
|
-
.catch((e) => {
|
|
4429
|
-
this.hooks.notify?.(`\x1b[2m[nimbus: "${record.command}" could not be restarted: `
|
|
4430
|
-
+ `${errorMessage(e)}]\x1b[0m\r\n`);
|
|
4431
|
-
}));
|
|
4432
|
-
}
|
|
4433
|
-
}
|
|
4434
|
-
/**
|
|
4435
|
-
* Record a launch as in flight, so an instance that replaces this one knows
|
|
4436
|
-
* it never finished. Best-effort: a launch that cannot be journalled still
|
|
4437
|
-
* runs, and a reset then costs exactly what it cost before the journal.
|
|
4438
|
-
*
|
|
4439
|
-
* Synced, not merely put: `await put()` resolves before durability, and the
|
|
4440
|
-
* reset this journal exists for destroys every write its turn still had
|
|
4441
|
-
* outstanding — measured live, a launch killed in its first chunks left NO
|
|
4442
|
-
* row for the replacement instance to find, which is how the recovery this
|
|
4443
|
-
* feeds sat inert while its own test stayed green. `sync()` is the storage
|
|
4444
|
-
* layer's durability barrier: the row is on disk before the launch performs
|
|
4445
|
-
* its first byte of real work. What remains is a reset between the put and
|
|
4446
|
-
* the sync's completion — and a launch that dies there has not started, so
|
|
4447
|
-
* losing its row costs a retype, not a recovery.
|
|
4250
|
+
* Grant every suspended launch a chunk of this turn — the session's alarm
|
|
4251
|
+
* calls this, and journal recovery rides the first pump. See fabric's
|
|
4252
|
+
* `PacedWork.pump` for the ownership argument.
|
|
4448
4253
|
*/
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
this.journalledPids.add(record.pid);
|
|
4452
|
-
await this.ctx.storage.put(`${RESIDENT_LAUNCH_KEY_PREFIX}${record.pid}`, record);
|
|
4453
|
-
await this.ctx.storage.sync();
|
|
4454
|
-
}
|
|
4455
|
-
catch (e) {
|
|
4456
|
-
console.warn('[nimbus] resident launch journal write failed:', errorMessage(e));
|
|
4457
|
-
}
|
|
4254
|
+
pumpResidentLaunches() {
|
|
4255
|
+
return this.launchPump.pump();
|
|
4458
4256
|
}
|
|
4459
4257
|
/** Whether any launch is suspended waiting for a turn. */
|
|
4460
4258
|
get hasPendingLaunchTurns() {
|
|
4461
|
-
return this.
|
|
4259
|
+
return this.launchPump.hasPending;
|
|
4462
4260
|
}
|
|
4463
4261
|
/** Allocate a free loopback port for a resident server facet (from 4096 up). */
|
|
4464
4262
|
_allocateLoopbackPort() {
|
|
@@ -4695,7 +4493,7 @@ export class FacetManager {
|
|
|
4695
4493
|
*/
|
|
4696
4494
|
async _runResidentLaunch(entry, code, command, opts, attempt) {
|
|
4697
4495
|
const cwd = opts.cwd || '/home/user';
|
|
4698
|
-
const pacer = new
|
|
4496
|
+
const pacer = new TurnBudget(this.launchPump, turnChunkMaxBytes(this.env), () => this._assertLaunchStillOwned(entry.pid));
|
|
4699
4497
|
// Journalled before the first byte of work. A launch that FAILS deletes
|
|
4700
4498
|
// its row on the way out — its process has already been exited and the
|
|
4701
4499
|
// user notified, so there is nothing left to owe. A launch that SETTLES
|
|
@@ -4713,12 +4511,12 @@ export class FacetManager {
|
|
|
4713
4511
|
attempt,
|
|
4714
4512
|
phase: 'starting',
|
|
4715
4513
|
};
|
|
4716
|
-
await this.
|
|
4514
|
+
await this.launchJournal.journal(record);
|
|
4717
4515
|
try {
|
|
4718
4516
|
await this._residentLaunchBody(entry, code, command, cwd, opts, pacer);
|
|
4719
4517
|
}
|
|
4720
4518
|
catch (e) {
|
|
4721
|
-
await this.
|
|
4519
|
+
await this.launchJournal.release(entry.pid);
|
|
4722
4520
|
throw e;
|
|
4723
4521
|
}
|
|
4724
4522
|
finally {
|
|
@@ -4728,24 +4526,8 @@ export class FacetManager {
|
|
|
4728
4526
|
// its running life with a fresh re-drive budget. If the process already
|
|
4729
4527
|
// ended inside the launch body's own settlement, the terminal hook has
|
|
4730
4528
|
// released the row — do not write it back.
|
|
4731
|
-
if (this.
|
|
4732
|
-
await this.
|
|
4733
|
-
}
|
|
4734
|
-
}
|
|
4735
|
-
/**
|
|
4736
|
-
* The journal row's one release: the process is over, nothing is owed.
|
|
4737
|
-
* Synced so an instance reset moments later cannot roll the delete back and
|
|
4738
|
-
* resurrect a process the user watched end.
|
|
4739
|
-
*/
|
|
4740
|
-
async _releaseResidentJournal(pid) {
|
|
4741
|
-
if (!this.journalledPids.delete(pid))
|
|
4742
|
-
return;
|
|
4743
|
-
try {
|
|
4744
|
-
await this.ctx.storage.delete(`${RESIDENT_LAUNCH_KEY_PREFIX}${pid}`);
|
|
4745
|
-
await this.ctx.storage.sync();
|
|
4746
|
-
}
|
|
4747
|
-
catch (e) {
|
|
4748
|
-
console.warn('[nimbus] resident launch journal delete failed:', errorMessage(e));
|
|
4529
|
+
if (this.launchJournal.has(entry.pid)) {
|
|
4530
|
+
await this.launchJournal.journal({ ...record, attempt: 0, phase: 'running' });
|
|
4749
4531
|
}
|
|
4750
4532
|
}
|
|
4751
4533
|
async _residentLaunchBody(entry, code, command, cwd, opts, pacer) {
|
|
@@ -4758,7 +4540,7 @@ export class FacetManager {
|
|
|
4758
4540
|
}
|
|
4759
4541
|
}
|
|
4760
4542
|
if (this.vfs)
|
|
4761
|
-
this.
|
|
4543
|
+
this.imageStore.ensureDir();
|
|
4762
4544
|
const diagOn = isExecDiagEnabled();
|
|
4763
4545
|
const __bundleStart = diagOn ? Date.now() : 0;
|
|
4764
4546
|
const processVfs = this.vfs?.as(entry.cred);
|
|
@@ -4803,7 +4585,7 @@ export class FacetManager {
|
|
|
4803
4585
|
let handle;
|
|
4804
4586
|
let resourcesTracked = false;
|
|
4805
4587
|
try {
|
|
4806
|
-
const vfsTextModules = await this.
|
|
4588
|
+
const vfsTextModules = await this.imageStore.materialize(entry.pid, {
|
|
4807
4589
|
'worker.js': generatedWorker.code,
|
|
4808
4590
|
...generatedWorker.modules,
|
|
4809
4591
|
}, pacer);
|
|
@@ -4893,6 +4675,9 @@ export class FacetManager {
|
|
|
4893
4675
|
await handle.booted();
|
|
4894
4676
|
}
|
|
4895
4677
|
if (opts.port && opts.port > 0 && opts.port < 65536) {
|
|
4678
|
+
// A new process on a port retires the previous occupant's preview
|
|
4679
|
+
// capability, so a URL handed out for that one cannot reach this one.
|
|
4680
|
+
await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, opts.port);
|
|
4896
4681
|
this.portRegistry.register(opts.port, entry.pid);
|
|
4897
4682
|
}
|
|
4898
4683
|
}
|
|
@@ -4960,6 +4745,9 @@ export class FacetManager {
|
|
|
4960
4745
|
resourcesTracked = true;
|
|
4961
4746
|
this.portRegistry.bindFacetStub(entry.pid, handle.routeTarget);
|
|
4962
4747
|
if (opts.port && opts.port > 0 && opts.port < 65536) {
|
|
4748
|
+
// A new process on a port retires the previous occupant's preview
|
|
4749
|
+
// capability, so a URL handed out for that one cannot reach this one.
|
|
4750
|
+
await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, opts.port);
|
|
4963
4751
|
this.portRegistry.register(opts.port, entry.pid);
|
|
4964
4752
|
}
|
|
4965
4753
|
return { pid: entry.pid, boot: await handle.booted() };
|
|
@@ -4980,8 +4768,9 @@ export class FacetManager {
|
|
|
4980
4768
|
throw e;
|
|
4981
4769
|
}
|
|
4982
4770
|
}
|
|
4983
|
-
registerPort(pid, port) {
|
|
4771
|
+
async registerPort(pid, port) {
|
|
4984
4772
|
if (port > 0 && port < 65536) {
|
|
4773
|
+
await clearPortCapability({ ctx: this.ctx, portRegistry: this.portRegistry }, port);
|
|
4985
4774
|
this.portRegistry.register(port, pid);
|
|
4986
4775
|
}
|
|
4987
4776
|
}
|
|
@@ -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;AAGpE,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;AAsFD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAC/B,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,UAAU,CAAC,CAyDrB"}
|
|
@@ -145,6 +145,9 @@ function chunkModuleEntries(env) {
|
|
|
145
145
|
}
|
|
146
146
|
return chunkEntriesInflight;
|
|
147
147
|
}
|
|
148
|
+
// A 'staged' boot spec reaching the fabric assembles through
|
|
149
|
+
// assembleOpencodeFacetConfig below; the worker's composition root
|
|
150
|
+
// (src/index.ts) hands it to the fabric with composeFabric.
|
|
148
151
|
/**
|
|
149
152
|
* Assemble the full Worker Loader config for an opencode facet from a stage
|
|
150
153
|
* spec. Returns the config WITHOUT the SUPERVISOR env binding — the caller
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
// 3. Facet-side: ESM source strings for the ws shim + chokidar shim.
|
|
42
42
|
// All are emitted by the generators below.
|
|
43
43
|
import { WorkerEntrypoint } from 'cloudflare:workers';
|
|
44
|
-
import { disposeRpcResource, useRpcResource } from '@nimbus-sh/
|
|
44
|
+
import { disposeRpcResource, useRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
45
45
|
// CLN-3 (2026-05-11): supervisor-side debug gate. Mirrors the facet-side
|
|
46
46
|
// `globalThis.__cirrusDebug` flag declared at cirrus-real.ts:160. When
|
|
47
47
|
// `false` (default), the hot-path console.log calls in `HmrBridge`
|