@nimbus-sh/worker 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +2 -2
  2. package/dist/_shared/preview-host.d.ts +26 -8
  3. package/dist/_shared/preview-host.d.ts.map +1 -1
  4. package/dist/_shared/preview-host.js +51 -11
  5. package/dist/cirrus-npm-cjs.generated.d.ts +1 -1
  6. package/dist/cirrus-npm-cjs.generated.js +1 -1
  7. package/dist/facets/cirrus-real.d.ts.map +1 -1
  8. package/dist/facets/cirrus-real.js +6 -5
  9. package/dist/facets/compose.d.ts +148 -0
  10. package/dist/facets/compose.d.ts.map +1 -0
  11. package/dist/facets/compose.js +146 -0
  12. package/dist/facets/durable-images.d.ts +18 -5
  13. package/dist/facets/durable-images.d.ts.map +1 -1
  14. package/dist/facets/durable-images.js +36 -7
  15. package/dist/facets/durable-slots.d.ts +1 -1
  16. package/dist/facets/durable-slots.d.ts.map +1 -1
  17. package/dist/facets/durable-slots.js +2 -1
  18. package/dist/facets/esbuild-bundle-pool.d.ts +49 -0
  19. package/dist/facets/esbuild-bundle-pool.d.ts.map +1 -0
  20. package/dist/facets/esbuild-bundle-pool.js +122 -0
  21. package/dist/facets/exec-telemetry.d.ts +8 -0
  22. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  23. package/dist/facets/manager.d.ts +337 -123
  24. package/dist/facets/manager.d.ts.map +1 -1
  25. package/dist/facets/manager.js +1100 -449
  26. package/dist/facets/process.d.ts +20 -16
  27. package/dist/facets/process.d.ts.map +1 -1
  28. package/dist/facets/process.js +59 -28
  29. package/dist/facets/real-vite-fs-shim.js +2 -2
  30. package/dist/facets/resident-identity.d.ts +23 -0
  31. package/dist/facets/resident-identity.d.ts.map +1 -0
  32. package/dist/facets/resident-identity.js +31 -0
  33. package/dist/facets/vite-dev-server.d.ts +25 -47
  34. package/dist/facets/vite-dev-server.d.ts.map +1 -1
  35. package/dist/facets/vite-dev-server.js +185 -219
  36. package/dist/facets/wasm-image-digest.d.ts +25 -0
  37. package/dist/facets/wasm-image-digest.d.ts.map +1 -0
  38. package/dist/facets/wasm-image-digest.js +25 -0
  39. package/dist/facets/wasm-swap-registry.d.ts +51 -92
  40. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  41. package/dist/facets/wasm-swap-registry.js +68 -227
  42. package/dist/git/commands.d.ts +0 -1
  43. package/dist/git/commands.d.ts.map +1 -1
  44. package/dist/git/commands.js +0 -3
  45. package/dist/git/network-facet.d.ts.map +1 -1
  46. package/dist/git/network-facet.js +6 -5
  47. package/dist/loaders/child-process/spawn-pool.d.ts +3 -2
  48. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
  49. package/dist/loaders/child-process/spawn-pool.js +3 -2
  50. package/dist/loaders/generated-workers.d.ts +2 -2
  51. package/dist/loaders/generated-workers.d.ts.map +1 -1
  52. package/dist/loaders/generated-workers.js +3 -3
  53. package/dist/loaders/npm-resolve-preamble.d.ts +3 -6
  54. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  55. package/dist/loaders/npm-resolve-preamble.js +34 -108
  56. package/dist/node-shims-artifact.generated.js +3 -3
  57. package/dist/npm/install-batch-facet.d.ts +3 -2
  58. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  59. package/dist/npm/install-batch-facet.js +45 -15
  60. package/dist/npm/installer.d.ts +22 -17
  61. package/dist/npm/installer.d.ts.map +1 -1
  62. package/dist/npm/installer.js +209 -193
  63. package/dist/npm/pre-bundle-facet.d.ts +1 -1
  64. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  65. package/dist/npm/pre-bundle-facet.js +1 -1
  66. package/dist/npm/resolve-facet.d.ts +6 -0
  67. package/dist/npm/resolve-facet.d.ts.map +1 -1
  68. package/dist/npm/resolve-one-facet.d.ts +21 -20
  69. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  70. package/dist/npm/resolve-one-facet.js +85 -84
  71. package/dist/npm/semver.d.ts +59 -0
  72. package/dist/npm/semver.d.ts.map +1 -0
  73. package/dist/npm/semver.js +226 -0
  74. package/dist/router/index.d.ts.map +1 -1
  75. package/dist/router/index.js +47 -10
  76. package/dist/router/public-directory.d.ts +5 -1
  77. package/dist/router/public-directory.d.ts.map +1 -1
  78. package/dist/router/public-directory.js +9 -2
  79. package/dist/router/remote-api.d.ts +18 -0
  80. package/dist/router/remote-api.d.ts.map +1 -1
  81. package/dist/router/remote-api.js +64 -5
  82. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  83. package/dist/runtime/cpython-resident.js +3 -1
  84. package/dist/runtime/html-entrypoint.d.ts +7 -0
  85. package/dist/runtime/html-entrypoint.d.ts.map +1 -1
  86. package/dist/runtime/html-entrypoint.js +18 -2
  87. package/dist/runtime/node-runner.d.ts +3 -0
  88. package/dist/runtime/node-runner.d.ts.map +1 -1
  89. package/dist/runtime/node-runner.js +7 -0
  90. package/dist/runtime/node-shims.d.ts.map +1 -1
  91. package/dist/runtime/node-shims.js +305 -29
  92. package/dist/runtime/opencode-facet-runner.js +2 -2
  93. package/dist/runtime/package-manager.d.ts +44 -1
  94. package/dist/runtime/package-manager.d.ts.map +1 -1
  95. package/dist/runtime/package-manager.js +168 -3
  96. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  97. package/dist/runtime/ruby-resident.js +2 -0
  98. package/dist/session/agent.d.ts.map +1 -1
  99. package/dist/session/agent.js +28 -1
  100. package/dist/session/helpers.d.ts +38 -0
  101. package/dist/session/helpers.d.ts.map +1 -1
  102. package/dist/session/helpers.js +76 -0
  103. package/dist/session/init-phases.d.ts +1 -1
  104. package/dist/session/init-phases.d.ts.map +1 -1
  105. package/dist/session/init-phases.js +2 -12
  106. package/dist/session/init.d.ts +15 -1
  107. package/dist/session/init.d.ts.map +1 -1
  108. package/dist/session/init.js +91 -603
  109. package/dist/session/keys.d.ts +4 -0
  110. package/dist/session/keys.d.ts.map +1 -1
  111. package/dist/session/keys.js +4 -0
  112. package/dist/session/nimbus-session.d.ts +90 -23
  113. package/dist/session/nimbus-session.d.ts.map +1 -1
  114. package/dist/session/nimbus-session.js +200 -110
  115. package/dist/session/npm-install-port.d.ts +16 -0
  116. package/dist/session/npm-install-port.d.ts.map +1 -0
  117. package/dist/session/npm-install-port.js +37 -0
  118. package/dist/session/port-capability.d.ts +90 -0
  119. package/dist/session/port-capability.d.ts.map +1 -1
  120. package/dist/session/port-capability.js +219 -9
  121. package/dist/session/programmatic.d.ts +125 -5
  122. package/dist/session/programmatic.d.ts.map +1 -1
  123. package/dist/session/programmatic.js +439 -89
  124. package/dist/session/routes.d.ts +39 -17
  125. package/dist/session/routes.d.ts.map +1 -1
  126. package/dist/session/routes.js +67 -131
  127. package/dist/session/rpc.d.ts +39 -16
  128. package/dist/session/rpc.d.ts.map +1 -1
  129. package/dist/session/rpc.js +137 -130
  130. package/dist/session/serving-port.d.ts +27 -0
  131. package/dist/session/serving-port.d.ts.map +1 -0
  132. package/dist/session/serving-port.js +27 -0
  133. package/dist/session/start-real-vite.d.ts +12 -0
  134. package/dist/session/start-real-vite.d.ts.map +1 -1
  135. package/dist/session/start-real-vite.js +4 -4
  136. package/dist/session/supervisor-op.d.ts +41 -4
  137. package/dist/session/supervisor-op.d.ts.map +1 -1
  138. package/dist/session/supervisor-op.js +101 -73
  139. package/dist/session/supervisor-rpc.d.ts +7 -7
  140. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  141. package/dist/session/supervisor-rpc.js +6 -3
  142. package/dist/session/vite-command.d.ts +18 -0
  143. package/dist/session/vite-command.d.ts.map +1 -0
  144. package/dist/session/vite-command.js +609 -0
  145. package/dist/session/ws.d.ts +61 -0
  146. package/dist/session/ws.d.ts.map +1 -1
  147. package/dist/session/ws.js +122 -12
  148. package/dist/shell/npm-bin-entrypoints.d.ts +0 -35
  149. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  150. package/dist/shell/npm-bin-entrypoints.js +12 -85
  151. package/package.json +24 -4
  152. package/public/_assets/runtime/{node-shims-9e1380d9ee430fcc.js → node-shims-8f8c64edd2a1f0f5.js} +305 -29
  153. package/public/index.html +1 -1
  154. package/scripts/bundle-facet-workers.mjs +1 -1
  155. package/scripts/bundle-npm-cjs.mjs +2 -2
  156. package/dist/facets/on-demand-bundle-gate.d.ts +0 -65
  157. package/dist/facets/on-demand-bundle-gate.d.ts.map +0 -1
  158. package/dist/facets/on-demand-bundle-gate.js +0 -125
  159. package/dist/npm/install-args.d.ts +0 -21
  160. package/dist/npm/install-args.d.ts.map +0 -1
  161. package/dist/npm/install-args.js +0 -70
  162. package/dist/npm/npm-log.d.ts +0 -31
  163. package/dist/npm/npm-log.d.ts.map +0 -1
  164. package/dist/npm/npm-log.js +0 -51
@@ -19,11 +19,13 @@ import type { ProcessEntry } from '@nimbus-sh/core/runtime/process-table.js';
19
19
  import { SessionProcessSupervisor } from '@nimbus-sh/core/runtime/session-process-supervisor.js';
20
20
  import type { CredentialedVfs, SqliteVFS, VfsStat } from '@nimbus-sh/core/vfs/sqlite-vfs.js';
21
21
  import type { PortRegistry } from '@nimbus-sh/core/runtime/port-registry.js';
22
+ import { type PortVisibility } from '../session/port-capability.js';
22
23
  import { TurnBudget } from '@nimbus-sh/fabric/turn-budget.js';
23
24
  import { EsbuildService, type TransformResult } from '@nimbus-sh/core/runtime/esbuild-service.js';
24
25
  import { type ProcessHostFactory, type ResidentCodeSpec } from '@nimbus-sh/fabric/process-fabric.js';
25
26
  import { type OpencodeRunnerOptions } from '../runtime/opencode-facet-runner.js';
26
27
  import { type FacetBundleProfile } from '@nimbus-sh/core/runtime/bundle-profile.js';
28
+ import { type WasmImageRecord } from './wasm-image-digest.js';
27
29
  type EsbuildTransformOptions = NonNullable<Parameters<EsbuildService['transform']>[1]>;
28
30
  type LargeEsmTransform = (code: string, options: EsbuildTransformOptions) => Promise<TransformResult>;
29
31
  /** Result returned from a facet execution */
@@ -74,34 +76,6 @@ export interface StagedArtifactExecResult extends FacetExecResult {
74
76
  /** For the resident server path: the loopback port the facet is bound to. */
75
77
  port?: number;
76
78
  }
77
- /**
78
- * Reserve held back from a one-shot facet's lifetime so a program that runs
79
- * out of time is still alive to say so.
80
- *
81
- * A one-shot exec is ALREADY bounded: `_execWithTimeout` kills it at
82
- * FACET_TIMEOUT_MS with exit 124 and "[process killed: timeout after 30s]".
83
- * The entry drain must therefore not be a second, tighter, independent
84
- * timeout. Measured against a deployed Worker, floating async work of 5s /
85
- * 15s / 25s completes and 40s is killed by that outer bound at exactly 30s —
86
- * so the fixed 8s budget this used to carry was abandoning programs 22
87
- * seconds before anything actually required it.
88
- *
89
- * The drain therefore runs to the outer bound MINUS this reserve, which is
90
- * what buys the facet time to flush and report the honest "still in flight"
91
- * reason instead of the supervisor's generic kill. The reserve has to cover
92
- * the longest tail a facet can have after the drain: settling pending RPC,
93
- * writing back __vfsWrites (bounded by MAX_RPC_SAFE_PAYLOAD_BYTES; a 20 MiB
94
- * write-back measures ~1.5s), draining children, then reportExit.
95
- */
96
- export declare const ONE_SHOT_EXIT_RESERVE_MS = 3000;
97
- /**
98
- * Budget used when the supervisor did not stamp an absolute deadline on the
99
- * payload. The deadline is the real bound — see `entryDeadlineAt` — because
100
- * it is measured from the supervisor's own timer rather than restarted when
101
- * the drain begins, so a slow module init cannot push the drain past the kill
102
- * and lose the honest message.
103
- */
104
- export declare const ONE_SHOT_ENTRY_DEADLINE_MS: number;
105
79
  /**
106
80
  * How long a RESIDENT facet settles its startup before answering its boot
107
81
  * call. It keeps running afterwards, so this is not a lifetime decision: the
@@ -117,8 +91,11 @@ export declare const RESIDENT_BOOT_SETTLE_MS = 1000;
117
91
  * servers, requests in flight. A promise is not a handle: a program whose
118
92
  * last act leaves `new Promise(() => {})` unsettled prints its output and
119
93
  * exits 0. Counting unsettled promises as work was a real divergence from
120
- * that — such a program burned the whole facet lifetime and was then
121
- * reported as having not finished.
94
+ * that — such a program's drain used to end early and it was
95
+ * reported as having not finished. A user-invoked program runs this loop
96
+ * with NO deadline — Node itself has no wall-clock kill; the only endings
97
+ * are the program's exit and a signal. Callers that still pass a finite
98
+ * deadline (the resident boot settle) arm the expiry timer.
122
99
  *
123
100
  * Three kinds of handle, each owned by the shim that creates them:
124
101
  *
@@ -131,19 +108,12 @@ export declare const RESIDENT_BOOT_SETTLE_MS = 1000;
131
108
  * - listening SERVERS (`__portRegistry`), open until the program closes
132
109
  * them.
133
110
  *
134
- * The bound is a REAL wall-clock deadline, armed as a timer rather than
135
- * compared against `Date.now()`: a `setTimeout(0)` turn in workerd costs
136
- * ~5µs, so the pass budget this loop used to carry (50k) expired after
137
- * ~150ms and silently overrode every longer deadline the callers declared —
138
- * anything slower than that, including an ordinary network fetch, was
139
- * abandoned mid-flight and reported as a clean exit.
140
- *
141
111
  * The loop subscribes to the exit promise ONCE — a per-pass
142
112
  * `exitPromise.then()` allocates a promise every iteration — and yields
143
113
  * through the raw setTimeout so its own ticks don't inflate the timer count
144
114
  * it watches.
145
115
  */
146
- export declare const ENTRYPOINT_EVENT_LOOP = "\nfunction __nimbusHandleCount(__name) {\n const __value = globalThis[__name];\n return typeof __value === \"number\" ? __value : 0;\n}\n\n// Work an entrypoint's STARTUP has to settle before it can be called booted.\nfunction __nimbusPendingStartupWork() {\n return __nimbusHandleCount(\"__nimbusPendingTimers\") + __nimbusHandleCount(\"__nimbusPendingOps\");\n}\n\n// The above, plus the handles a program holds open on purpose. A bound port\n// keeps a Node process alive, and it keeps a one-shot facet alive too.\nfunction __nimbusLiveHandles() {\n const __servers = globalThis.__portRegistry;\n const __bound = __servers && typeof __servers.size === \"number\" ? __servers.size : 0;\n return __nimbusPendingStartupWork() + __bound;\n}\n\nasync function __nimbusRunEventLoop(__countHandles, __exitPromise, __deadlineMs, __minPasses) {\n let __exited = false;\n if (__exitPromise && typeof __exitPromise.then === \"function\") {\n __exitPromise.then(() => { __exited = true; }, () => { __exited = true; });\n }\n const __rawSetTimeout = (typeof globalThis.__nimbusRawSetTimeout === \"function\")\n ? globalThis.__nimbusRawSetTimeout\n : globalThis.setTimeout;\n const __rawClearTimeout = (typeof globalThis.__nimbusRawClearTimeout === \"function\")\n ? globalThis.__nimbusRawClearTimeout\n : globalThis.clearTimeout;\n let __expired = false;\n const __deadline = __rawSetTimeout(() => { __expired = true; }, __deadlineMs);\n let __pass = 0;\n while (!__exited && !__expired && (__pass < __minPasses || __countHandles() > 0)) {\n // The warm-up passes give a settling microtask chain its turns and cost\n // ~5\u00B5s each; past them the loop is waiting on wall-clock work, where\n // spinning at 0ms would burn the isolate's CPU for the whole deadline.\n await new Promise((resolve) => __rawSetTimeout(resolve, __pass < __minPasses ? 0 : 1));\n __pass++;\n }\n try { __rawClearTimeout(__deadline); } catch {}\n // `pending` is what the caller reports when it gives up: a one-shot program\n // still holding a handle did NOT finish, and exiting 0 would claim it did.\n return { passes: __pass, pending: __exited ? 0 : __countHandles() };\n}\n\n// An ESM entry's own evaluation promise (top-level await) is the one promise\n// that IS a handle \u2014 the module has not finished loading until it settles.\n// Answers true when process.exit won the race instead.\nasync function __nimbusAwaitEntryEvaluation(__entryResult) {\n if (!__entryResult || typeof __entryResult.then !== \"function\") return false;\n const __exit = {};\n const __raced = await Promise.race([\n __entryResult.then(() => null),\n __nimbusProcessExitPromise.then(() => __exit, () => __exit),\n ]);\n return __raced === __exit;\n}\n\n// A one-shot facet's lifetime IS the loop: it runs the program until Node\n// would exit, or until the lifetime budget runs out.\nasync function __nimbusRunEntrypointToExit(__entryResult, __deadlineMs) {\n if (await __nimbusAwaitEntryEvaluation(__entryResult)) return { passes: 0, pending: 0 };\n return await __nimbusRunEventLoop(__nimbusLiveHandles, __nimbusProcessExitPromise, __deadlineMs, 4);\n}\n\n// A resident facet keeps running after the call that boots it returns, so it\n// settles startup and nothing more. The handles it holds open deliberately \u2014\n// its listening port \u2014 are the point of it, not a reason to make the shell's\n// prompt wait.\nasync function __nimbusSettleEntrypointStartup(__entryResult, __deadlineMs) {\n if (await __nimbusAwaitEntryEvaluation(__entryResult)) return { passes: 0, pending: 0 };\n return await __nimbusRunEventLoop(\n __nimbusPendingStartupWork, __nimbusProcessExitPromise, __deadlineMs, 4,\n );\n}\n";
116
+ export declare const ENTRYPOINT_EVENT_LOOP = "\nfunction __nimbusHandleCount(__name) {\n const __value = globalThis[__name];\n return typeof __value === \"number\" ? __value : 0;\n}\n\n// Work an entrypoint's STARTUP has to settle before it can be called booted.\nfunction __nimbusPendingStartupWork() {\n return __nimbusHandleCount(\"__nimbusPendingTimers\") + __nimbusHandleCount(\"__nimbusPendingOps\");\n}\n\n// The above, plus the handles a program holds open on purpose. A bound port\n// keeps a Node process alive, and it keeps a one-shot facet alive too.\nfunction __nimbusLiveHandles() {\n const __servers = globalThis.__portRegistry;\n const __bound = __servers && typeof __servers.size === \"number\" ? __servers.size : 0;\n return __nimbusPendingStartupWork() + __bound;\n}\n\nasync function __nimbusRunEventLoop(__countHandles, __exitPromise, __deadlineMs, __minPasses) {\n let __exited = false;\n if (__exitPromise && typeof __exitPromise.then === \"function\") {\n __exitPromise.then(() => { __exited = true; }, () => { __exited = true; });\n }\n const __rawSetTimeout = (typeof globalThis.__nimbusRawSetTimeout === \"function\")\n ? globalThis.__nimbusRawSetTimeout\n : globalThis.setTimeout;\n const __rawClearTimeout = (typeof globalThis.__nimbusRawClearTimeout === \"function\")\n ? globalThis.__nimbusRawClearTimeout\n : globalThis.clearTimeout;\n let __expired = false;\n let __pass = 0;\n // A user-invoked program runs until it exits or is killed \u2014 there is no\n // wall-clock deadline, so no expiry timer is armed at all. (Callers that\n // still pass a finite deadline get the timer for compatibility.)\n const __deadline = Number.isFinite(__deadlineMs)\n ? __rawSetTimeout(() => { __expired = true; }, __deadlineMs)\n : null;\n while (!__exited && !__expired && (__pass < __minPasses || __countHandles() > 0)) {\n // The warm-up passes give a settling microtask chain its turns and cost\n // ~5\u00B5s each; past them the loop is waiting on wall-clock work, where\n // spinning at 0ms would burn the isolate's CPU indefinitely.\n await new Promise((resolve) => __rawSetTimeout(resolve, __pass < __minPasses ? 0 : 1));\n __pass++;\n }\n if (__deadline !== null) { try { __rawClearTimeout(__deadline); } catch {} }\n // `pending` is what the caller reports when it gives up: a one-shot program\n // still holding a handle did NOT finish, and exiting 0 would claim it did.\n return { passes: __pass, pending: __exited ? 0 : __countHandles() };\n}\n\n// An ESM entry's own evaluation promise (top-level await) is the one promise\n// that IS a handle \u2014 the module has not finished loading until it settles.\n// Answers true when process.exit won the race instead.\nasync function __nimbusAwaitEntryEvaluation(__entryResult) {\n if (!__entryResult || typeof __entryResult.then !== \"function\") return false;\n const __exit = {};\n const __raced = await Promise.race([\n __entryResult.then(() => null),\n __nimbusProcessExitPromise.then(() => __exit, () => __exit),\n ]);\n return __raced === __exit;\n}\n\n// A one-shot facet's lifetime IS the loop: it runs the program until Node\n// would exit, or until the lifetime budget runs out.\nasync function __nimbusRunEntrypointToExit(__entryResult, __deadlineMs) {\n if (await __nimbusAwaitEntryEvaluation(__entryResult)) return { passes: 0, pending: 0 };\n return await __nimbusRunEventLoop(__nimbusLiveHandles, __nimbusProcessExitPromise, __deadlineMs, 4);\n}\n\n// A resident facet keeps running after the call that boots it returns, so it\n// settles startup and nothing more. The handles it holds open deliberately \u2014\n// its listening port \u2014 are the point of it, not a reason to make the shell's\n// prompt wait.\nasync function __nimbusSettleEntrypointStartup(__entryResult, __deadlineMs) {\n if (await __nimbusAwaitEntryEvaluation(__entryResult)) return { passes: 0, pending: 0 };\n return await __nimbusRunEventLoop(\n __nimbusPendingStartupWork, __nimbusProcessExitPromise, __deadlineMs, 4,\n );\n}\n";
147
117
  /**
148
118
  * A generated facet's module map: its main module plus whatever side modules
149
119
  * the VFS bundle had to be partitioned across.
@@ -155,14 +125,32 @@ interface GeneratedNodeFacetCode {
155
125
  /**
156
126
  * Generate one-shot runtime code with a plain fetch handler.
157
127
  */
158
- export declare function generateEntrypointCode(userCode: string, vfsState: FacetVfsState, usesSqlite: boolean, shims: string): Promise<GeneratedNodeFacetCode>;
128
+ export declare function generateEntrypointCode(userCode: string, vfsState: FacetVfsState, usesSqlite: boolean, shims: string, wasmImports?: readonly FacetWasmImport[]): Promise<GeneratedNodeFacetCode>;
129
+ /** One wasm image the generated main module imports from the module map. */
130
+ export interface FacetWasmImport {
131
+ /** The module-map name the boot spec carries the image under. */
132
+ moduleName: string;
133
+ /** The absolute VFS path the program reads the same bytes from. */
134
+ vfsPath: string;
135
+ /**
136
+ * A content key for the same bytes, for an image the program never reads
137
+ * from the filesystem — one inlined in a package's own source as base64.
138
+ * The seam recognises the bytes instead of the path.
139
+ */
140
+ digest?: string;
141
+ }
142
+ /** The module-map name a precompiled wasm image travels under. */
143
+ export declare function facetWasmModuleName(index: number): string;
159
144
  /**
160
- * Generate a long-running Node entrypoint.
161
- *
162
- * Same core shim/VFS machinery as foreground node execution, but the
163
- * compiled user entry is booted once and the exported entrypoint keeps
164
- * serving HTTP requests from the shimmed http.Server registry.
145
+ * The wasm imports one launch stages: the images its options name, then
146
+ * every image the closure walk recorded (FacetVfsState.wasmImages) that the
147
+ * options did not already name by path. One member per path; the closure's
148
+ * record supplies the digest an option without one lacks.
165
149
  */
150
+ export declare function facetWasmImports(named: readonly {
151
+ vfsPath: string;
152
+ digest: string | undefined;
153
+ }[], closure: readonly WasmImageRecord[]): FacetWasmImport[];
166
154
  export declare function generateLongRunningNodeCode(userCode: string, vfsState: FacetVfsState, opts: {
167
155
  argv?: string[];
168
156
  env?: Record<string, string>;
@@ -172,6 +160,8 @@ export declare function generateLongRunningNodeCode(userCode: string, vfsState:
172
160
  stdin?: string;
173
161
  attachedTty?: boolean;
174
162
  cred: ProcessEntry['cred'];
163
+ /** Wasm images the generated main module imports and parks in the seam. */
164
+ wasmImports?: readonly FacetWasmImport[];
175
165
  }, usesSqlite: boolean, shims: string, pacer?: TurnBudget): Promise<GeneratedNodeFacetCode>;
176
166
  /**
177
167
  * Result of preparing facet VFS state.
@@ -237,6 +227,11 @@ interface FacetVfsState {
237
227
  serializedMetadata?: string;
238
228
  /** Move the bundle out of the main module when combined state exceeds its ceiling. */
239
229
  bundleSideModulesRequired?: boolean;
230
+ /**
231
+ * The wasm images the closure holds, by path and content digest — see
232
+ * `collectClosureWasmImages`. A launch stages each as a wasm map entry.
233
+ */
234
+ wasmImages?: readonly WasmImageRecord[];
240
235
  /**
241
236
  * Memoized `bundleUsesNodeSqlite(entryCode, bundle)`. Answered while the raw
242
237
  * cells are still in hand so `releaseSerializedSources` can drop them — it is
@@ -262,38 +257,37 @@ interface FacetVfsState {
262
257
  * `bundleSource`, `serializedManifest` and `serializedMetadata` are total
263
258
  * encodings of `bundle`, `manifest` and `metadata` — no caller can distinguish
264
259
  * a state carrying both from one carrying only the serialized halves, because
265
- * `generateEntrypointCode` reads the serialized halves and nothing else does.
260
+ * both facet generators read the serialized halves and nothing else does.
266
261
  * Holding both doubles the cost of a cached entry for its whole lifetime, and
267
262
  * that lifetime spans execs.
268
263
  *
269
- * Only for states on the one-shot cached path, and applied there whether or
270
- * not the entry turns out small enough to retain: the invocation being served
271
- * reads the serialized forms too. `spawnNode` and `_stageOpencodeFacet` build
272
- * their own uncached states and genuinely re-read the raw cells
273
- * (`_serializeBundleForFacet`, `assertStagedBundleFitsRpcPayload`); neither
274
- * goes through here.
264
+ * Applied by `_buildProcessBundle` to every state it builds, whether or not
265
+ * the entry turns out small enough to retain: the launch being served reads
266
+ * the serialized forms too. `_stageOpencodeFacet` builds its own uncached
267
+ * state and genuinely re-reads the raw cells
268
+ * (`assertStagedBundleFitsRpcPayload`); it does not go through here.
275
269
  */
276
270
  export declare function releaseSerializedSources(vfsState: FacetVfsState): void;
277
271
  /**
278
- * Drop everything a resident launch has finished reading, in place.
279
- *
280
- * The one-shot path releases its map at LOADER.load; this is the same policy
281
- * for the path `spawnNode` takes, which is the path every attached-TTY npm bin
282
- * takes — how a real agentic CLI starts. The generated source is a total
283
- * encoding of the cells, the manifest and the metadata, and the only thing the
284
- * rest of a launch reads off the state is `cursor`. Everything else is a second
285
- * copy of the largest thing this DO builds — 22.9 MB for pi — held for exactly
286
- * as long as the facet takes to boot on it.
272
+ * Drop the serialized forms once a module map has been generated from them.
287
273
  *
288
- * Holding it reset the session isolate with exceededMemory, and an isolate
289
- * reset tears the terminal WebSocket down with no exit frame: the dead screen
290
- * reading "[process terminal closed]".
274
+ * The generated source is a total encoding of all three: every byte of the
275
+ * bundle expression, the manifest and the metadata is inside it. Holding them
276
+ * afterwards keeps a second copy of the largest thing this DO builds alive for
277
+ * as long as the facet runs — for pi, 22.7 MB across the ~20 s window in which
278
+ * the isolate was being reset. For the one-shot path that window is the run;
279
+ * for a resident launch — the path every attached-TTY npm bin takes, how a
280
+ * real agentic CLI starts — it is the boot, and holding the copy across it
281
+ * reset the session isolate with exceededMemory, which tears the terminal
282
+ * WebSocket down with no exit frame: the dead screen reading "[process
283
+ * terminal closed]".
291
284
  *
292
- * An emptied state can still generate a map — it would just generate one with
293
- * no program in it — so this marks the state instead of trusting callers to
294
- * stop.
285
+ * Only for a state the prefetch cache refused. A retained entry's serialized
286
+ * forms ARE the entry, and a later launch is served from them. An emptied
287
+ * state could still generate a map — one with no program in it — so this
288
+ * marks the state instead of trusting callers to stop.
295
289
  */
296
- export declare function releaseResidentLaunchSources(vfsState: FacetVfsState): void;
290
+ export declare function releaseGeneratedSources(vfsState: FacetVfsState): void;
297
291
  interface FacetVfsBundleSource {
298
292
  expression: string;
299
293
  imports: string;
@@ -350,13 +344,35 @@ export declare function assertStagedBundleFitsRpcPayload(serialized: string, bun
350
344
  * computed-path requires). Bounded to package.json + 1 main-entry file
351
345
  * per package — sub-agent §Q3 quantified the worst-case cumulative
352
346
  * budget impact (~322 KiB for fastify, ~1.7 MiB for ts-jest).
347
+ *
348
+ * `requiredPaths` is the static require closure. A package the closure
349
+ * already reached — but reached only through a SUBPATH — has its main entry
350
+ * skipped; see `mainIsSpeculative`.
353
351
  */
354
352
  export declare function greedyAddMainEntries(vfs: CredentialedVfs, cwd: string, bundle: Record<string, string | Uint8Array>, budgetState: {
355
353
  totalBytes: number;
356
354
  fileCount: number;
357
- }): {
355
+ }, requiredPaths?: ReadonlySet<string>): {
358
356
  added: number;
359
357
  };
358
+ /**
359
+ * The packages a computed `require(name)` inside the program can plausibly
360
+ * name: the project root's own runtime `dependencies`, plus every package
361
+ * ONE `dependencies` hop from a package that already owns a file in the
362
+ * static closure. Never devDependencies, never a second hop.
363
+ *
364
+ * Unbounded, the greedy oversample read every installed package's main:
365
+ * for `node -e "import('got')"` in got's repo — a one-file static closure —
366
+ * that was 1,526 files / 10.9 MB from 706 packages, which every later pass
367
+ * re-scanned and esbuild-wasm transformed, and the exec path's 20 s bundle
368
+ * deadline fired on a program that reads none of it. A bound that followed
369
+ * dependency edges from the project's devDependencies reached all 772 of
370
+ * them (measured), because a library repo's dev toolchain reaches the whole
371
+ * tree. Computed requires almost always target a declared runtime
372
+ * dependency of the package doing the requiring, so the bound is one hop
373
+ * over `dependencies` only. Directories, sorted for a stable bundle.
374
+ */
375
+ export declare function speculativePackageDirs(vfs: CredentialedVfs, cwdStripped: string, bundle: Record<string, string | Uint8Array>): string[];
360
376
  /**
361
377
  * X.5-Z3: scan every JS source already in `bundle` for static
362
378
  * `fs.readFileSync(path.resolve(__dirname, "<rel>"))` shapes and pull
@@ -488,7 +504,21 @@ export declare function addBinTargetSiblings(vfs: CredentialedVfs, scriptPath: s
488
504
  fileCount: number;
489
505
  }, bundleProfile: FacetBundleProfile): {
490
506
  added: number;
507
+ wasmPaths: string[];
491
508
  };
509
+ /**
510
+ * The wasm images a program's closure holds, by path and content digest.
511
+ *
512
+ * Two sources, one record: a `.wasm` cell the walk already staged (digested
513
+ * from the cell, no second read), and a `.wasm` file the bin-package pass
514
+ * saw but did not stage because it is over the bundle's per-file cap —
515
+ * esbuild-wasm's 11.9 MiB image is the motivating one. A launch stages each
516
+ * as a module-map member the loader compiles, registered under both keys,
517
+ * so the program's own `new WebAssembly.Module(bytes)` is answered from the
518
+ * map whether it read the bytes by path or carried them inline. A file that
519
+ * cannot be read is left out; the seam's refusal names the module later.
520
+ */
521
+ export declare function collectClosureWasmImages(vfs: Pick<CredentialedVfs, 'readFile'>, bundle: Record<string, string | Uint8Array | FacetVfsDenial>, unstagedPaths: readonly string[]): WasmImageRecord[];
492
522
  /**
493
523
  * Stage the paths an earlier run of the same entry read synchronously and did
494
524
  * not have.
@@ -576,7 +606,7 @@ export declare const BUNDLE_PRECOMPILE_LOOP: string;
576
606
  * behaviour for code paths that don't have esbuild handy).
577
607
  *
578
608
  */
579
- export declare function buildPrefetchBundle(vfs: CredentialedVfs, scriptPath: string | undefined, cwd: string, entryCode: string, esbuild?: EsbuildService, bundleProfile?: FacetBundleProfile, observedReads?: ReadonlySet<string>, pacer?: TurnBudget, isolatedTransform?: LargeEsmTransform): Promise<FacetVfsState>;
609
+ export declare function buildPrefetchBundle(vfs: CredentialedVfs, scriptPath: string | undefined, cwd: string, entryCode: string, esbuild?: EsbuildService, bundleProfile?: FacetBundleProfile, observedReads?: ReadonlySet<string>, pacer?: TurnBudget, isolatedTransform?: LargeEsmTransform, maxBundleBytes?: number): Promise<FacetVfsState>;
580
610
  /**
581
611
  * Optional hooks wired in by NimbusSession. Kept as callbacks so
582
612
  * FacetManager stays unaware of the session / log-store types.
@@ -598,7 +628,7 @@ export interface FacetManagerHooks {
598
628
  * genuinely re-enters the object: a fresh turn is both a released thread and
599
629
  * a fresh CPU budget, and a launch needs each for a different reason.
600
630
  */
601
- requestLaunchTurn?: () => void;
631
+ requestLaunchTurn?: (notBefore?: number) => void;
602
632
  /**
603
633
  * Put a line in front of the user, whether or not a terminal is attached.
604
634
  *
@@ -628,6 +658,12 @@ export interface FacetManagerHooks {
628
658
  resolveWorkerLaunchFallback?: (recipe: WorkerRecipe) => Promise<ResolvedWorkerLaunch | null>;
629
659
  }
630
660
  export interface LongRunningWorkerSpawnOptions {
661
+ /** Interpreter residents share Node's atomic derived-owner claim. */
662
+ resident?: {
663
+ runtime: 'ruby' | 'python';
664
+ argv: string[];
665
+ };
666
+ restart?: ResidentRestartPolicy;
631
667
  port?: number;
632
668
  /** Inline modules: source text, or small wasm carried by value. */
633
669
  modules?: Record<string, string | {
@@ -639,6 +675,20 @@ export interface LongRunningWorkerSpawnOptions {
639
675
  * 34.3 MiB, past what any single RPC value may carry.
640
676
  */
641
677
  vfsWasmModules?: Record<string, string>;
678
+ /**
679
+ * Module name → VFS path of a content-addressed module SOURCE, read as
680
+ * UTF-8 when the facet loads — the same by-path posture as
681
+ * `vfsWasmModules`, through the same kernel image reader, for module text
682
+ * an embedder keeps in its own content store rather than carrying by value.
683
+ * Each path must name its own digest (`…/<sha256>.js`, see fabric's
684
+ * `facetImagePath`); the loader verifies it on read.
685
+ */
686
+ vfsTextModules?: Record<string, string>;
687
+ /**
688
+ * The module the isolate boots from; `workerCode` is placed under this name
689
+ * in the module map. Default `'worker.js'`.
690
+ */
691
+ mainModule?: string;
642
692
  compatibilityFlags?: string[];
643
693
  compatibilityDate?: string;
644
694
  env?: ResidentCodeSpec['env'];
@@ -658,6 +708,29 @@ export interface LongRunningWorkerSpawnOptions {
658
708
  };
659
709
  };
660
710
  }
711
+ /** The main module name a worker launch boots from unless told otherwise. */
712
+ export declare const DEFAULT_WORKER_MAIN_MODULE = "worker.js";
713
+ /**
714
+ * The narrow handle `spawnWorker` returns beside the pid: the process's own
715
+ * inbound surface, bound to the resident handle's route target, for an
716
+ * embedder that invokes the worker directly rather than through a registered
717
+ * port. It carries NO release — `kill(pid)` remains the one lifecycle owner,
718
+ * and this handle is dead once that pid is; nothing here can end, restart or
719
+ * detach the process.
720
+ */
721
+ export interface WorkerFacet {
722
+ /** Inbound HTTP, on the facet's `handleHttpRequest`. */
723
+ fetch(request: Request): Promise<Response>;
724
+ /** Inbound WebSocket upgrade, on the facet's `handleWebSocketRequest`. */
725
+ connect(request: Request): Promise<Response>;
726
+ }
727
+ /** What `spawnWorker` answers with. */
728
+ export interface SpawnedWorker {
729
+ pid: number;
730
+ /** The runner's startProcess payload. */
731
+ boot: unknown;
732
+ facet: WorkerFacet;
733
+ }
661
734
  /** What `spawnNode` needs to build and boot one resident Node process. */
662
735
  export interface ResidentSpawnOptions {
663
736
  argv?: string[];
@@ -675,6 +748,19 @@ export interface ResidentSpawnOptions {
675
748
  /** The launch inputs a re-drive rebuilds a worker from: content digests and
676
749
  * transport, never env or credentials — the embedder resolves those. */
677
750
  export interface WorkerRecipe {
751
+ /**
752
+ * Set only for an interpreter resident the SESSION launched itself — a
753
+ * `python`/`ruby` socket server whose image is the runtime the session
754
+ * installed. It is the runtime and argv of that interpreter, never a
755
+ * boolean, and it is what routes the recipe's re-drive to the session's own
756
+ * image-store fallback (`resolveWorkerLaunchFallback`) rather than the
757
+ * embedder's `resolveWorkerLaunch`: the session owns that image and its
758
+ * bookkeeping, and no embedder was asked about the launch. A worker recipe
759
+ * WITHOUT it — every embedder-driven `spawnWorker` — re-drives through the
760
+ * embedder's resolver, with the fallback consulted only when no embedder
761
+ * hook is composed.
762
+ */
763
+ resident?: LongRunningWorkerSpawnOptions['resident'];
678
764
  kind: 'worker';
679
765
  /** The durable application this process belongs to, keyed by the embedder. */
680
766
  owner: string;
@@ -687,17 +773,53 @@ export interface WorkerRecipe {
687
773
  cwd: string;
688
774
  compatibilityDate: string;
689
775
  compatibilityFlags: string[];
690
- startArgs: unknown;
776
+ startArgs?: unknown;
777
+ /**
778
+ * The main module the launch booted from, when it was not the default —
779
+ * so a re-drive whose resolver answers modules alone still boots the same
780
+ * one. Rows written before the field existed booted `worker.js`.
781
+ */
782
+ mainModule?: string;
783
+ }
784
+ export type ResidentRestartPolicy = 'never' | 'on-failure';
785
+ /** The env var a launch reads its restart policy from — set by startProcess({ restart }) and `nimbus start --restart`. */
786
+ export declare const RESTART_POLICY_ENV = "NIMBUS_RESTART";
787
+ /** One application as `apps.list` reports it — every stamped identity, live or not. */
788
+ export interface ResidentAppSummary {
789
+ owner: string;
790
+ name: string | null;
791
+ port: number | null;
792
+ pid: number | null;
793
+ status: 'running' | 'starting' | 'stopped' | 'failed';
794
+ visibility: PortVisibility;
795
+ capability: string | null;
796
+ restart: ResidentRestartPolicy;
797
+ /** Set with status 'failed': what went wrong, in the user's terms. */
798
+ diagnostic: string | null;
799
+ }
800
+ /** What a pid's journal row says about who it is. */
801
+ export interface ResidentIdentity {
802
+ owner: string | undefined;
803
+ ephemeral: boolean;
804
+ port: number | undefined;
691
805
  }
692
806
  /** What the embedder supplies for a re-driven worker launch. */
693
807
  export interface ResolvedWorkerLaunch {
808
+ startArgs?: unknown;
694
809
  /** Null is the absent answer — the same JSON the image blob carries. */
695
810
  env: ResidentCodeSpec['env'] | null;
696
811
  globalOutbound: ResidentCodeSpec['globalOutbound'];
697
- /** Module name → source text, including the `worker.js` main module. */
812
+ /** Module name → source text, including the main module under its name. */
698
813
  modules: Record<string, string>;
699
814
  /** Module name → VFS path of a wasm image, restored with the launch. */
700
815
  vfsWasmModules?: Record<string, string>;
816
+ /** Module name → VFS path of a content-addressed module source, restored with the launch. */
817
+ vfsTextModules?: Record<string, string>;
818
+ /**
819
+ * The name in `modules` the launch boots from. Absent, the recipe's own
820
+ * `mainModule` decides, then `'worker.js'`.
821
+ */
822
+ mainModule?: string;
701
823
  }
702
824
  export declare class FacetManager {
703
825
  private ctx;
@@ -741,7 +863,6 @@ export declare class FacetManager {
741
863
  * journal recovery rides the first pump.
742
864
  */
743
865
  private readonly launchPump;
744
- private timedOutProcessIds;
745
866
  private _pairedServeFacet;
746
867
  /**
747
868
  * W3.5 Fix B: lazily-created EsbuildService for the ESM→CJS pre-pass
@@ -783,6 +904,15 @@ export declare class FacetManager {
783
904
  private residencyProfiles;
784
905
  /** In-flight request-driven durable-app ensures, single-flight per port. */
785
906
  private ensureInflight;
907
+ /** Per-pid chain of journal-row amendments; see `_amendRow`. */
908
+ private rowAmendments;
909
+ /**
910
+ * pid → the derived owner it duplicates: the second live instance of an
911
+ * identity. Not journalled (nothing re-drives it), so this is the only
912
+ * record of why `expose(pid)` refuses it.
913
+ */
914
+ private ephemeralPids;
915
+ private residentClaims;
786
916
  private static readonly RESIDENCY_PROFILE_MAX_ENTRIES;
787
917
  /**
788
918
  * A program that reads a directory of data files misses once per file, so
@@ -792,6 +922,23 @@ export declare class FacetManager {
792
922
  */
793
923
  private static readonly RESIDENCY_PROFILE_MAX_PATHS;
794
924
  constructor(ctx: DurableObjectState, env: unknown, processes: SessionProcessSupervisor, portRegistry: PortRegistry, host: ProcessHostFactory, hooks?: FacetManagerHooks);
925
+ /**
926
+ * The process is over. Every end-of-life passes through here: a clean
927
+ * exit, a kill, a timeout, a crash. Only one of them owes anything more
928
+ * than the journal row's release — a crash under 'on-failure' is re-driven
929
+ * from the row, after a backoff, while the row is still in storage so a
930
+ * reset inside the backoff window recovers it like any other resident.
931
+ */
932
+ private _onResidentTerminal;
933
+ /** Claim identity AND write its recovery row in one serializable storage transaction. */
934
+ private _claimResident;
935
+ private _releaseResidentClaim;
936
+ /**
937
+ * Amend one journal row in place — port stamp, owner adoption, settle —
938
+ * serialized per pid so two amendments in flight on the same row cannot
939
+ * interleave their read and write and lose one another's fields.
940
+ */
941
+ private _amendRow;
795
942
  setVfs(vfs: SqliteVFS): void;
796
943
  /**
797
944
  * The env/ctx pair every loader-backed runtime builds its facet pools
@@ -823,39 +970,56 @@ export declare class FacetManager {
823
970
  */
824
971
  setEsbuildService(esbuild: EsbuildService): void;
825
972
  /**
826
- * buildPrefetchBundle wrapped in a global-revision-keyed cache. On a hit
827
- * (same key AND the VFS hasn't been mutated since) it returns the memoized
828
- * bundle + pre-built facet source, skipping the full VFS walk + esbuild
829
- * pass + source construction. See `prefetchBundleCache` for the
830
- * correctness argument behind the conservative global-revision watermark.
831
- *
832
- * The bundle source and manifest are computed once on the miss path and
833
- * stored so subsequent hits skip rebuilding them too.
973
+ * The pacer every launch is built under: the session's alarm-driven turn
974
+ * pump, the deployment's chunk bound, and the one check a suspended launch
975
+ * makes when it resumes — that the process it is building for still exists.
834
976
  */
977
+ private _launchPacer;
835
978
  /**
836
- * Bound the prefetch build, so a cache miss cannot be a silent hang.
979
+ * Assemble the filesystem bundle a process boots on, across as many
980
+ * Durable Object turns as it takes.
981
+ *
982
+ * The one builder for every Node process this manager starts. A one-shot
983
+ * exec and a resident launch used to own two copies of this: exec's was
984
+ * memoized behind the prefetch cache and raced a wall-clock deadline in a
985
+ * single turn; the resident's was paged with a TurnBudget and never cached.
986
+ * Two paths, one job — and a tree large enough to page on one path hit the
987
+ * deadline on the other, failing every `node -e` in it with "assembling the
988
+ * filesystem bundle … exceeded". What the two callers genuinely differ in is
989
+ * the entry, the working directory and the process they build for; that is
990
+ * all they supply. Everything else — the revision-keyed cache and its stale
991
+ * eviction, the residency profile a previous miss learned, the reachable-set
992
+ * walk and its enrichment passes, the ESM→CJS transform, the manifest and
993
+ * metadata, the module-map serialization with its side-module split, the
994
+ * `node:sqlite` answer, and the release of the raw cells once they are
995
+ * serialized — happens here, once, and yields the turn whenever a chunk's
996
+ * worth of it has been done.
837
997
  *
838
- * The build was awaited entirely OUTSIDE `_execWithTimeout`, which wraps
839
- * only `_execViaLoader`. So every timeout in the system — the 30 s facet
840
- * bound, the 60 s bin-dispatch bound — sat downstream of a step that could
841
- * take arbitrarily long, and a heavy build wedged the Durable Object with
842
- * nothing able to report it. Observed as a terminal that goes quiet and
843
- * never returns, with no exit record for the process.
998
+ * There is no deadline. A launch that spans turns costs turns, not a held
999
+ * thread, so a large tree is not a defect to be reported at N seconds; the
1000
+ * pacer's `stillWanted` check is what ends a build nothing will use — a
1001
+ * process killed while its build was suspended throws from the next resume,
1002
+ * and the caller reports that as it reports any other launch failure.
844
1003
  *
845
- * WHAT THIS CAN AND CANNOT CATCH, stated plainly because the difference
846
- * decides whether a given hang is fixed by it. The build is asynchronous —
847
- * it awaits the VFS walk and the esbuild ESM→CJS pass — so a stall at any
848
- * of those points is caught and reported here. A stall inside ONE
849
- * synchronous stretch is not: a JS stack that never yields cannot be raced
850
- * by anything in the same isolate, so serializing a multi-megabyte bundle
851
- * in a single pass still wedges, and the deadline fires only once the stack
852
- * finally unwinds. That class needs the work bounded at its INPUT rather
853
- * than timed at its edge, which is a separate change; this one converts
854
- * every interruptible stall from a silent wedge into a loud failure, and
855
- * makes the remaining class the only one left to explain.
856
- */
857
- private _withBundleBuildDeadline;
858
- private _buildPrefetchBundleCached;
1004
+ * The returned state carries its serialized forms (`bundleSource`,
1005
+ * `serializedManifest`, `serializedMetadata`) and has already released the
1006
+ * raw ones: `generateEntrypointCode` and `generateLongRunningNodeCode` read
1007
+ * the serialized forms and nothing else. A state the cache retained belongs
1008
+ * to the cache — a caller must not release its serialized forms either
1009
+ * (`cacheRetained` says which); one the cache refused belongs to the caller
1010
+ * alone, and `releaseGeneratedSources` drops it once a map is generated.
1011
+ *
1012
+ * A session without a filesystem gets an empty state: there is nothing to
1013
+ * stage and nothing to yield for.
1014
+ */
1015
+ /**
1016
+ * The closure's wasm images as module-map members for a one-shot facet,
1017
+ * read by path under the process's own credential. An image that cannot
1018
+ * be read is left out; the program's compile then meets the seam's own
1019
+ * refusal, which names the module.
1020
+ */
1021
+ private _wasmModulesByValue;
1022
+ private _buildProcessBundle;
859
1023
  /**
860
1024
  * Admit an entry and evict, oldest first, until the LRU is inside BOTH its
861
1025
  * entry count and its byte bound.
@@ -932,6 +1096,8 @@ export declare class FacetManager {
932
1096
  /** Return stdout/stderr in the result while keeping supervisor RPC
933
1097
  * available for VFS and child_process operations. */
934
1098
  captureOutput?: boolean;
1099
+ /** Shell abort (Ctrl+C): aborting this aborts the in-flight run. */
1100
+ signal?: AbortSignal;
935
1101
  }): Promise<FacetExecResult>;
936
1102
  /**
937
1103
  * W5 Lever 5: push a DiagFailure into the OOM ring for every facet
@@ -1074,10 +1240,16 @@ export declare class FacetManager {
1074
1240
  private _warmOpencodeServer;
1075
1241
  /** Recent stderr/stdout tail for a pid, for fail-loud diagnostics. */
1076
1242
  private _processLogTail;
1243
+ /**
1244
+ * A launch that fails before its process is running reports the same way
1245
+ * regardless of which phase failed: the pid is exited, the terminal event
1246
+ * recorded, and the session notified. Callers do their phase-specific
1247
+ * cleanup (ports, tracked RPC resources) first and pass a reason that names
1248
+ * the phase.
1249
+ */
1250
+ private _failLaunch;
1077
1251
  /** Flush files written by the script back to the supervisor's VFS. */
1078
1252
  private _flushVfsWrites;
1079
- /** Execution timeout. */
1080
- private _execWithTimeout;
1081
1253
  /**
1082
1254
  * Re-drive a journalled launch after an instance reset. What the journal
1083
1255
  * row carries is the recipe and nothing else: env and credentials are never
@@ -1118,10 +1290,21 @@ export declare class FacetManager {
1118
1290
  private _runResidentLaunch;
1119
1291
  private _residentLaunchBody;
1120
1292
  /**
1121
- * Spawn a long-running dynamic Worker, boot it, and return its boot payload.
1293
+ * The exit code of a launched process that has already ended, or null
1294
+ * while it runs. What a caller that started a resident reads to tell a
1295
+ * server that is up from a program that finished during its boot.
1296
+ */
1297
+ processExitCode(pid: number): number | null;
1298
+ /**
1299
+ * Spawn a long-running dynamic Worker, boot it, and return its boot payload
1300
+ * beside the pid and the process's own inbound facet.
1122
1301
  *
1123
1302
  * The shared primitive for any runtime that serves over
1124
- * handleHttpRequest(Request) — the python and ruby socket servers today.
1303
+ * handleHttpRequest(Request) — the python and ruby socket servers today —
1304
+ * and for an embedder's own Worker-class program: `workerCode` boots as
1305
+ * `opts.mainModule` (default `worker.js`), `opts.modules` ride inline,
1306
+ * `opts.vfsTextModules` and `opts.vfsWasmModules` are read by path when the
1307
+ * facet loads.
1125
1308
  *
1126
1309
  * The interpreter image it carries is the memory that should not sit in the
1127
1310
  * session's own isolate — ruby's interpreter+stdlib alone is 34.3 MiB — and
@@ -1129,30 +1312,61 @@ export declare class FacetManager {
1129
1312
  * no readiness coupling back into the session: the runner answers
1130
1313
  * startProcess with its boot payload and the caller waits on that one
1131
1314
  * promise, so nothing polls the port to decide the process is up.
1315
+ *
1316
+ * The returned `facet` is bound to the resident handle's route target — the
1317
+ * same target a registered port routes to — so a port-less process can be
1318
+ * invoked directly. It has no release: `kill(pid)` is the one lifecycle
1319
+ * owner, and the facet is dead once the pid is.
1132
1320
  */
1133
- spawnWorker(workerCode: string, command: string, cwd: string, opts?: LongRunningWorkerSpawnOptions): Promise<{
1134
- pid: number;
1135
- boot: unknown;
1136
- }>;
1321
+ spawnWorker(workerCode: string, command: string, cwd: string, opts?: LongRunningWorkerSpawnOptions): Promise<SpawnedWorker>;
1137
1322
  /** `attempt` is the journal's re-drive budget, as `_spawnResident` carries it. */
1138
1323
  private _spawnWorker;
1139
1324
  /**
1140
- * A resident process announcing it bound `port`. When the port is reserved,
1141
- * the reservation's owner is stamped onto this pid's journal row — the
1142
- * reservation is what declares which application the port serves, and a
1143
- * resident that binds it inherits the whole durable contract: the row names
1144
- * the port `ensureDurableAppOnPort` looks up, the minted capability is
1145
- * re-adopted rather than retired, and `removeDurableApp` can find the launch
1146
- * by owner.
1325
+ * A resident process announcing it bound `port`.
1326
+ *
1327
+ * The stamp is unconditional: the pid's journal row gets `{ port }`
1328
+ * whether or not anything reserved it, which is what lets
1329
+ * `ensureDurableAppOnPort` re-drive ANY resident a reset killed — the
1330
+ * scoped URLs need no capability, so this alone makes every server's
1331
+ * preview survive a reset on demand.
1332
+ *
1333
+ * The capability is bound to identity, not to the port. The stored
1334
+ * capability is re-adopted only when the row's owner IS the reservation's
1335
+ * owner; any other occupant — an unrelated server, a pid outside the
1336
+ * resident lifecycle, the ephemeral second instance of an identity —
1337
+ * retires it and mints fresh, so a shared link 404s rather than reaching
1338
+ * a program it was never handed out for. An EXPLICIT reservation (an
1339
+ * embedder's `ensureDurableApp`) is the one exception, and it is the
1340
+ * landed contract: the embedder declared the port, so the row adopts the
1341
+ * reservation's owner and the capability with it.
1147
1342
  *
1148
- * A pid with no journal row — a process outside the resident lifecycle —
1149
- * registering on a reserved port takes it as today: the stored capability
1150
- * is retired and a fresh one is minted, the reservation stays with the
1151
- * owner. An accidental port reuse inside one session therefore cannot
1152
- * inherit the public capability; same-session processes are one trust
1153
- * domain, so this is hygiene, not a security boundary.
1343
+ * Under an injected `$PORT`, a resident that binds a different port is
1344
+ * registered anyway — the server must not break — but the row records
1345
+ * the mismatch, so `apps.list` reports it as failed and the user is told.
1154
1346
  */
1155
1347
  private _registerResidentPort;
1348
+ /**
1349
+ * Who a pid is. One resolver, in precedence order: the ephemeral-duplicate
1350
+ * mark, the journal row (the owner a launch this manager made was stamped
1351
+ * with — derived from the launch's own cwd+argv, or adopted from an
1352
+ * explicit reservation), and finally the process table. The table knows
1353
+ * cwd and argv for every pid, so a serving process nothing journalled — the
1354
+ * in-process Vite dev server, real-vite, a staged artifact, any wrapper pid
1355
+ * a builtin adopted — has the same derived identity shape as a resident
1356
+ * and answers to the same app verbs. It just cannot be re-driven after a
1357
+ * reset: only a journal row carries a recipe. Null only for a pid that is
1358
+ * neither journalled nor running.
1359
+ */
1360
+ residentIdentity(pid: number): Promise<ResidentIdentity | null>;
1361
+ /**
1362
+ * Every stamped identity — the reservations, and the journal rows that
1363
+ * carry an owner — folded one row per owner. A live pid in THIS instance
1364
+ * makes it running (or starting, until its launch settles and its port
1365
+ * is registered); a mismatch diagnostic makes it failed; everything else
1366
+ * is stopped, which for a row a reset left behind means re-drivable on
1367
+ * request.
1368
+ */
1369
+ listResidentApps(): Promise<ResidentAppSummary[]>;
1156
1370
  registerPort(pid: number, port: number): Promise<void>;
1157
1371
  waitForRouteablePorts(pid: number, timeoutMs?: number): Promise<number[]>;
1158
1372
  finishProcess(pid: number, exitCode: number, reason?: string): void;