@nimbus-sh/worker 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +2 -2
  2. package/dist/cirrus-npm-cjs.generated.d.ts +1 -1
  3. package/dist/cirrus-npm-cjs.generated.js +1 -1
  4. package/dist/facets/cirrus-real.d.ts.map +1 -1
  5. package/dist/facets/cirrus-real.js +6 -5
  6. package/dist/facets/compose.d.ts +148 -0
  7. package/dist/facets/compose.d.ts.map +1 -0
  8. package/dist/facets/compose.js +146 -0
  9. package/dist/facets/durable-images.d.ts +9 -5
  10. package/dist/facets/durable-images.d.ts.map +1 -1
  11. package/dist/facets/durable-images.js +17 -7
  12. package/dist/facets/esbuild-bundle-pool.d.ts +49 -0
  13. package/dist/facets/esbuild-bundle-pool.d.ts.map +1 -0
  14. package/dist/facets/esbuild-bundle-pool.js +122 -0
  15. package/dist/facets/exec-telemetry.d.ts +8 -0
  16. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  17. package/dist/facets/manager.d.ts +240 -109
  18. package/dist/facets/manager.d.ts.map +1 -1
  19. package/dist/facets/manager.js +677 -342
  20. package/dist/facets/process.d.ts +20 -16
  21. package/dist/facets/process.d.ts.map +1 -1
  22. package/dist/facets/process.js +59 -28
  23. package/dist/facets/real-vite-fs-shim.js +2 -2
  24. package/dist/facets/real-vite-hmr.d.ts +3 -1
  25. package/dist/facets/real-vite-hmr.d.ts.map +1 -1
  26. package/dist/facets/real-vite-hmr.js +18 -8
  27. package/dist/facets/vite-dev-server.d.ts +25 -47
  28. package/dist/facets/vite-dev-server.d.ts.map +1 -1
  29. package/dist/facets/vite-dev-server.js +185 -219
  30. package/dist/facets/wasm-image-digest.d.ts +25 -0
  31. package/dist/facets/wasm-image-digest.d.ts.map +1 -0
  32. package/dist/facets/wasm-image-digest.js +25 -0
  33. package/dist/facets/wasm-swap-registry.d.ts +51 -92
  34. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  35. package/dist/facets/wasm-swap-registry.js +68 -227
  36. package/dist/facets/ws-terminal.d.ts +9 -31
  37. package/dist/facets/ws-terminal.d.ts.map +1 -1
  38. package/dist/facets/ws-terminal.js +58 -51
  39. package/dist/git/network-facet.d.ts.map +1 -1
  40. package/dist/git/network-facet.js +6 -5
  41. package/dist/hosted/commands.d.ts +12 -0
  42. package/dist/hosted/commands.d.ts.map +1 -0
  43. package/dist/hosted/commands.js +1658 -0
  44. package/dist/hosted/runtime.d.ts +114 -0
  45. package/dist/hosted/runtime.d.ts.map +1 -0
  46. package/dist/hosted/runtime.js +288 -0
  47. package/dist/hosted/services.d.ts +40 -0
  48. package/dist/hosted/services.d.ts.map +1 -0
  49. package/dist/hosted/services.js +512 -0
  50. package/dist/loaders/child-process/spawn-pool.d.ts +3 -2
  51. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
  52. package/dist/loaders/child-process/spawn-pool.js +3 -2
  53. package/dist/loaders/generated-workers.d.ts +2 -2
  54. package/dist/loaders/generated-workers.d.ts.map +1 -1
  55. package/dist/loaders/generated-workers.js +3 -3
  56. package/dist/loaders/npm-resolve-preamble.d.ts +3 -6
  57. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  58. package/dist/loaders/npm-resolve-preamble.js +34 -108
  59. package/dist/node-shims-artifact.generated.js +3 -3
  60. package/dist/npm/install-batch-facet.d.ts +3 -2
  61. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  62. package/dist/npm/install-batch-facet.js +45 -15
  63. package/dist/npm/installer.d.ts +22 -17
  64. package/dist/npm/installer.d.ts.map +1 -1
  65. package/dist/npm/installer.js +209 -193
  66. package/dist/npm/pre-bundle-facet.d.ts +1 -1
  67. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  68. package/dist/npm/pre-bundle-facet.js +1 -1
  69. package/dist/npm/resolve-facet.d.ts +6 -0
  70. package/dist/npm/resolve-facet.d.ts.map +1 -1
  71. package/dist/npm/resolve-one-facet.d.ts +21 -20
  72. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  73. package/dist/npm/resolve-one-facet.js +85 -84
  74. package/dist/npm/semver.d.ts +59 -0
  75. package/dist/npm/semver.d.ts.map +1 -0
  76. package/dist/npm/semver.js +226 -0
  77. package/dist/router/remote-api.js +31 -1
  78. package/dist/runtime/bash-repl.d.ts +1 -1
  79. package/dist/runtime/bash-repl.d.ts.map +1 -1
  80. package/dist/runtime/bash-repl.js +38 -4
  81. package/dist/runtime/bun-runner.js +1 -1
  82. package/dist/runtime/html-entrypoint.d.ts +7 -0
  83. package/dist/runtime/html-entrypoint.d.ts.map +1 -1
  84. package/dist/runtime/html-entrypoint.js +18 -2
  85. package/dist/runtime/node-runner.d.ts +4 -1
  86. package/dist/runtime/node-runner.d.ts.map +1 -1
  87. package/dist/runtime/node-runner.js +9 -2
  88. package/dist/runtime/node-shims.d.ts.map +1 -1
  89. package/dist/runtime/node-shims.js +305 -29
  90. package/dist/runtime/opencode-facet-runner.js +16 -16
  91. package/dist/runtime/package-manager.d.ts +35 -122
  92. package/dist/runtime/package-manager.d.ts.map +1 -1
  93. package/dist/runtime/package-manager.js +61 -596
  94. package/dist/runtime/process-logs-api.d.ts +1 -1
  95. package/dist/runtime/process-logs-api.d.ts.map +1 -1
  96. package/dist/runtime/process-logs-api.js +1 -1
  97. package/dist/runtime/python-repl.d.ts +2 -1
  98. package/dist/runtime/python-repl.d.ts.map +1 -1
  99. package/dist/runtime/python-repl.js +82 -59
  100. package/dist/runtime/repl-session.d.ts +9 -47
  101. package/dist/runtime/repl-session.d.ts.map +1 -1
  102. package/dist/runtime/repl-session.js +105 -105
  103. package/dist/runtime/runtime-catalog.d.ts +13 -0
  104. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  105. package/dist/runtime/runtime-catalog.js +88 -0
  106. package/dist/session/agent.d.ts +1 -21
  107. package/dist/session/agent.d.ts.map +1 -1
  108. package/dist/session/ai.d.ts +5 -1
  109. package/dist/session/ai.d.ts.map +1 -1
  110. package/dist/session/ai.js +7 -0
  111. package/dist/session/helpers.d.ts +38 -0
  112. package/dist/session/helpers.d.ts.map +1 -1
  113. package/dist/session/helpers.js +76 -0
  114. package/dist/session/hibernation.d.ts +2 -1
  115. package/dist/session/hibernation.d.ts.map +1 -1
  116. package/dist/session/hibernation.js +7 -4
  117. package/dist/session/init-phases.d.ts +1 -1
  118. package/dist/session/init-phases.d.ts.map +1 -1
  119. package/dist/session/init-phases.js +2 -12
  120. package/dist/session/init.d.ts +15 -36
  121. package/dist/session/init.d.ts.map +1 -1
  122. package/dist/session/init.js +50 -2327
  123. package/dist/session/loopback.d.ts +2 -29
  124. package/dist/session/loopback.d.ts.map +1 -1
  125. package/dist/session/loopback.js +3 -27
  126. package/dist/session/nimbus-session.d.ts +77 -34
  127. package/dist/session/nimbus-session.d.ts.map +1 -1
  128. package/dist/session/nimbus-session.js +126 -514
  129. package/dist/session/npm-install-port.d.ts +18 -0
  130. package/dist/session/npm-install-port.d.ts.map +1 -0
  131. package/dist/session/npm-install-port.js +37 -0
  132. package/dist/session/port-capability.d.ts +44 -0
  133. package/dist/session/port-capability.d.ts.map +1 -1
  134. package/dist/session/port-capability.js +110 -0
  135. package/dist/session/programmatic.d.ts +36 -43
  136. package/dist/session/programmatic.d.ts.map +1 -1
  137. package/dist/session/programmatic.js +109 -115
  138. package/dist/session/routes.d.ts +30 -17
  139. package/dist/session/routes.d.ts.map +1 -1
  140. package/dist/session/routes.js +42 -131
  141. package/dist/session/rpc.d.ts +15 -15
  142. package/dist/session/rpc.d.ts.map +1 -1
  143. package/dist/session/rpc.js +98 -49
  144. package/dist/session/serving-port.d.ts +27 -0
  145. package/dist/session/serving-port.d.ts.map +1 -0
  146. package/dist/session/serving-port.js +27 -0
  147. package/dist/session/start-real-vite.d.ts +12 -0
  148. package/dist/session/start-real-vite.d.ts.map +1 -1
  149. package/dist/session/start-real-vite.js +4 -4
  150. package/dist/session/supervisor-op.d.ts +4 -4
  151. package/dist/session/supervisor-op.d.ts.map +1 -1
  152. package/dist/session/supervisor-op.js +24 -10
  153. package/dist/session/supervisor-rpc.d.ts +7 -7
  154. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  155. package/dist/session/supervisor-rpc.js +4 -0
  156. package/dist/session/vite-command.d.ts +18 -0
  157. package/dist/session/vite-command.d.ts.map +1 -0
  158. package/dist/session/vite-command.js +609 -0
  159. package/dist/session/ws.d.ts +61 -0
  160. package/dist/session/ws.d.ts.map +1 -1
  161. package/dist/session/ws.js +122 -12
  162. package/dist/shell/npm-bin-entrypoints.d.ts +0 -35
  163. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  164. package/dist/shell/npm-bin-entrypoints.js +12 -85
  165. package/dist/workspace-host.d.ts +6 -0
  166. package/dist/workspace-host.d.ts.map +1 -0
  167. package/dist/workspace-host.js +5 -0
  168. package/dist/workspace.d.ts +2 -0
  169. package/dist/workspace.d.ts.map +1 -0
  170. package/dist/workspace.js +1 -0
  171. package/package.json +34 -9
  172. package/public/_assets/runtime/{node-shims-9e1380d9ee430fcc.js → node-shims-8f8c64edd2a1f0f5.js} +305 -29
  173. package/public/index.html +1 -1
  174. package/scripts/bundle-facet-workers.mjs +1 -1
  175. package/scripts/bundle-npm-cjs.mjs +2 -2
  176. package/dist/facets/on-demand-bundle-gate.d.ts +0 -65
  177. package/dist/facets/on-demand-bundle-gate.d.ts.map +0 -1
  178. package/dist/facets/on-demand-bundle-gate.js +0 -125
  179. package/dist/npm/install-args.d.ts +0 -21
  180. package/dist/npm/install-args.d.ts.map +0 -1
  181. package/dist/npm/install-args.js +0 -70
  182. package/dist/npm/npm-log.d.ts +0 -31
  183. package/dist/npm/npm-log.d.ts.map +0 -1
  184. package/dist/npm/npm-log.js +0 -51
@@ -5,23 +5,29 @@
5
5
  * Durable Object exposes a typed, programmatic sandbox surface without
6
6
  * duplicating the interactive terminal boot path.
7
7
  */
8
- import { ensureRuntimesProgrammatic, installRuntimeProgrammatic, listAvailableRuntimes, } from '../runtime/package-manager.js';
9
- import { listInstalledRuntimes, } from '@nimbus-sh/core/runtime/installed-runtimes.js';
8
+ /**
9
+ * session/programmatic.ts - public sandbox RPC helpers.
10
+ *
11
+ * These helpers are called by NimbusSession one-line delegators so the
12
+ * Durable Object exposes a typed, programmatic sandbox surface without
13
+ * duplicating the interactive terminal boot path.
14
+ */
15
+ import { ensureRuntimesProgrammatic, installRuntimeProgrammatic } from '../runtime/package-manager.js';
10
16
  import { PID_GEN_STRIDE } from '@nimbus-sh/core/runtime/process-table.js';
11
17
  import { notifyTerminalEvent } from '../runtime/process-logs-api.js';
12
18
  import { SessionProcessSupervisor } from '@nimbus-sh/core/runtime/session-process-supervisor.js';
13
19
  import { PortRegistry, createPortCapability } from '@nimbus-sh/core/runtime/port-registry.js';
14
- import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
15
- import { endProcessInput, resizeProcess, signalProcess, writeProcessInput, } from '@nimbus-sh/core/runtime/process-input-routing.js';
20
+ import { CRED_KERNEL, requireVfsCred } from '@nimbus-sh/core/runtime/os-contracts.js';
21
+ import { endProcessInput, resizeProcess, signalProcess, writeProcessInput } from '@nimbus-sh/core/runtime/process-input-routing.js';
16
22
  import { z } from 'zod/v4';
17
23
  import { SESSION_DESTROYED_KEY, SHELL_STATE_KEY_PREFIX, VITE_CONFIG_KEY } from './keys.js';
18
- import { clearPortCapability, isValidAppName, persistPortCapability, PortRecordSchema, portRecordKey, readPortReservation, readPortReservationByName, readPortReservationByOwner, reservePort, restorePortCapability, rotatePortCapability, } from './port-capability.js';
19
- import { PORT_CAPABILITY_KEY_PREFIX } from './keys.js';
24
+ import { clearPortCapability, isValidAppName, persistPortCapability, portRecordKey, readPortReservation, readPortReservationByName, readPortReservationByOwner, reservePort, restorePortCapability, rotatePortCapability } from './port-capability.js';
20
25
  import { bindPublicPortCapability, unbindPublicPortCapability } from '../router/public-directory.js';
21
- import { buildPreviewHost, buildPublicPreviewHost, isPreviewHostSafeSid, readPreviewHostSuffix, } from '../_shared/preview-host.js';
26
+ import { buildPreviewHost, buildPublicPreviewHost, isPreviewHostSafeSid, readPreviewHostSuffix } from '../_shared/preview-host.js';
22
27
  import { RESTART_POLICY_ENV } from '../facets/manager.js';
23
28
  import { GENERATION_KEY, assumeGeneration, generation } from '@nimbus-sh/fabric/generation.js';
24
29
  import { HeadlessTerminal, Shell } from '@nimbus-sh/core/substrate/lifo/index.js';
30
+ import { textSink } from '@nimbus-sh/core/_shared/bytes.js';
25
31
  const ShellIdSchema = z.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
26
32
  const ShellStateSchema = z.object({
27
33
  cwd: z.string().startsWith('/'),
@@ -113,35 +119,6 @@ const ProcessLogsOptionsSchema = z.object({
113
119
  lines: z.number().int().nonnegative().optional(),
114
120
  bytes: z.number().int().nonnegative().optional(),
115
121
  }).strict();
116
- function makeHeadlessWebSocket() {
117
- const listeners = new Map();
118
- const state = { readyState: 1 };
119
- const ws = {
120
- get readyState() { return state.readyState; },
121
- send(_data) { },
122
- close() {
123
- state.readyState = 3;
124
- for (const cb of listeners.get('close') ?? []) {
125
- try {
126
- cb();
127
- }
128
- catch { }
129
- }
130
- },
131
- accept() { },
132
- addEventListener(type, cb) {
133
- const set = listeners.get(type) ?? new Set();
134
- set.add(cb);
135
- listeners.set(type, set);
136
- },
137
- removeEventListener(type, cb) {
138
- listeners.get(type)?.delete(cb);
139
- },
140
- serializeAttachment(_value) { },
141
- deserializeAttachment() { return { kind: 'programmatic' }; },
142
- };
143
- return ws;
144
- }
145
122
  function getHome(self) {
146
123
  try {
147
124
  const envHome = self.shell?.env?.HOME;
@@ -164,23 +141,14 @@ function runtimeDeps(self) {
164
141
  if (!self._cpRegistry)
165
142
  throw new Error('Nimbus shell registry did not initialize');
166
143
  return {
167
- env: self.env,
168
- vfs: self.sqliteFs,
144
+ runtimes: self.runtimeManager,
145
+ vfs: self.sqliteFs.as(CRED_KERNEL),
169
146
  registry: self._cpRegistry,
170
147
  getHome: () => getHome(self),
171
148
  };
172
149
  }
173
150
  export async function ensureProgrammaticReady(self, options = {}) {
174
- if (!self.shell) {
175
- await self.initSession(makeHeadlessWebSocket());
176
- // A programmatic boot owns no terminal socket, which is what
177
- // 'drained' reports to the diagnostics and the recovery ring.
178
- self._b4Phase = 'drained';
179
- }
180
- else {
181
- self.ensureSqliteFs();
182
- self.ensureFacetManager();
183
- }
151
+ await self.ensureRuntimeReady();
184
152
  const preinstall = Array.from(new Set(options.preinstall ?? []))
185
153
  .map((s) => String(s).trim())
186
154
  .filter(Boolean);
@@ -218,14 +186,13 @@ function startShellJob(self, command, options, job, scoped) {
218
186
  catch { /* already settled */ }
219
187
  });
220
188
  const emit = (stream, sink) => (data) => {
221
- const text = String(data);
222
189
  if (job.background) {
223
190
  try {
224
- self.processes.appendOutput(pid, stream, text);
191
+ self.processes.appendOutputBytes(pid, stream, data);
225
192
  }
226
193
  catch { /* ring gone */ }
227
194
  }
228
- sink?.(text);
195
+ sink?.(data);
229
196
  };
230
197
  const run = shell.execute(line, {
231
198
  // A named shell already holds its own cwd and env; passing them again
@@ -261,7 +228,20 @@ function startShellJob(self, command, options, job, scoped) {
261
228
  }
262
229
  catch { } } };
263
230
  }
231
+ /**
232
+ * The shell, process table and VFS all key on absolute POSIX paths. A
233
+ * relative `cwd` arriving here means a caller skipped its own boundary
234
+ * normalization (the SDK resolves it); handed to the shell it degrades to
235
+ * silently wrong cwd semantics and ENOENT writes. Name the field and fail.
236
+ */
237
+ function assertAbsoluteExecCwd(options) {
238
+ const cwd = options.cwd;
239
+ if (cwd !== undefined && (typeof cwd !== 'string' || !cwd.startsWith('/'))) {
240
+ throw new Error(`cwd must be an absolute POSIX path starting with '/', got ${JSON.stringify(cwd)}`);
241
+ }
242
+ }
264
243
  export async function rpcExec(self, command, options = {}) {
244
+ assertAbsoluteExecCwd(options);
265
245
  await ensureProgrammaticReady(self, options);
266
246
  return withShellState(self, options, false, (scoped) => execOnShell(self, command, options, scoped));
267
247
  }
@@ -273,8 +253,8 @@ async function execOnShell(self, command, options, scoped) {
273
253
  let timedOut = false;
274
254
  const job = startShellJob(self, command, options, {
275
255
  background: false,
276
- onStdout: (d) => stdout.push(d),
277
- onStderr: (d) => stderr.push(d),
256
+ onStdout: textSink((d) => stdout.push(d)),
257
+ onStderr: textSink((d) => stderr.push(d)),
278
258
  }, scoped);
279
259
  let result;
280
260
  try {
@@ -353,6 +333,7 @@ function collectJobOutput(self, pid) {
353
333
  * process surface (`listProcesses`, `processLogs`, `killProcess`).
354
334
  */
355
335
  export async function rpcStartProcess(self, command, options = {}) {
336
+ assertAbsoluteExecCwd(options);
356
337
  await ensureProgrammaticReady(self, options);
357
338
  if (options.restart !== undefined && options.restart !== 'never' && options.restart !== 'on-failure') {
358
339
  throw new Error(`startProcess: restart must be 'never' or 'on-failure', got ${String(options.restart)}`);
@@ -408,7 +389,15 @@ async function startOnShell(self, command, options, scoped) {
408
389
  * the handoff, the facet is the live process, and it reports its own exit.
409
390
  */
410
391
  function finishBackgroundJob(self, pid, command, exitCode) {
411
- if (exitCode === 0 && self.facetManager?.hasResidentProcess(pid))
392
+ // The shell line returned, but the pid may still be serving: a resident
393
+ // facet the node runner handed off under this pid, or an in-process dev
394
+ // server a builtin (`vite`) adopted this pid for and registered a port
395
+ // under. Either way the process is alive — marking it exited would put a
396
+ // dead pid on a live port, and the app verbs identify a server by the pid
397
+ // serving its port.
398
+ const stillServing = self.facetManager?.hasResidentProcess(pid) === true
399
+ || self.portRegistry.getAll().some((live) => live.pid === pid);
400
+ if (exitCode === 0 && stillServing)
412
401
  return;
413
402
  self.processes.exit(pid, exitCode);
414
403
  if (!self.processes.getExit(pid))
@@ -447,8 +436,8 @@ export async function rpcEnsureRuntimes(self, specs, options = {}) {
447
436
  export async function rpcListRuntimes(self) {
448
437
  await ensureProgrammaticReady(self);
449
438
  return {
450
- installed: listInstalledRuntimes(self.sqliteFs, getHome(self)),
451
- available: await listAvailableRuntimes(self.env),
439
+ installed: self.runtimeManager.list(),
440
+ available: await self.runtimeManager.available(),
452
441
  };
453
442
  }
454
443
  export async function rpcListProcesses(self) {
@@ -528,12 +517,9 @@ export async function rpcProcessLogs(self, pid, options = {}) {
528
517
  }
529
518
  export async function rpcListPorts(self) {
530
519
  await ensureProgrammaticReady(self);
531
- const entries = self.portRegistry.getAll();
532
- // Persisted at the moment the embedder is told the value, not at
533
- // registration: a capability nobody has been handed does not need to
534
- // survive anything.
535
- await Promise.all(entries.map((entry) => persistPortCapability(self, entry.port, entry.capability)));
536
- return entries.map(serializePort);
520
+ // The composed manager's copy is the one implementation — persistence
521
+ // at tell-time included.
522
+ return self.ensureFacetManager().apps.listPorts();
537
523
  }
538
524
  /**
539
525
  * Browser-facing URL for an application, built inside the session: the
@@ -612,8 +598,9 @@ export async function rpcExposeApp(self, target, options = {}) {
612
598
  throw new Error(`${describeTarget(target)} has not bound a port yet — expose it once it is listening`);
613
599
  }
614
600
  if (resolved.owner === null) {
615
- throw new Error(`${describeTarget(target)} has no launch record to bind an identity to — `
616
- + 'only journalled residents carry one; use ports.expose for a bare port');
601
+ // Every running process has an identity, so this is only a port nothing
602
+ // serves and no reservation names.
603
+ throw new Error(`nothing is serving ${describeTarget(target)} — start the server, then expose it`);
617
604
  }
618
605
  const exposed = await applyExposure(self, resolved.port, resolved.owner, options);
619
606
  const live = self.portRegistry.get(resolved.port);
@@ -791,7 +778,23 @@ export async function rpcRemoveApp(self, target) {
791
778
  if (resolved.owner === null) {
792
779
  throw new Error(`${describeTarget(target)} names no application — nothing to remove`);
793
780
  }
794
- return rpcRemoveDurableApp(self, resolved.owner);
781
+ const owner = resolved.owner;
782
+ // The manager's removal kills what the journal knows. A server nothing
783
+ // journalled — the dev servers above all — is found the way every verb
784
+ // finds it, by the identity of the pid serving a port, and ended through
785
+ // the session's own kill, which is the one that also stops an in-process
786
+ // dev server rather than only marking its pid dead.
787
+ const fm = self.facetManager;
788
+ let killed = 0;
789
+ for (const live of self.portRegistry.getAll()) {
790
+ const identity = await fm.residentIdentity(live.pid);
791
+ if (identity === null || identity.ephemeral || identity.owner !== owner)
792
+ continue;
793
+ if ((await rpcKillProcess(self, live.pid)).ok)
794
+ killed += 1;
795
+ }
796
+ const durable = await rpcRemoveDurableApp(self, owner);
797
+ return { ...durable, removed: durable.removed || killed > 0 };
795
798
  }
796
799
  function describeTarget(target) {
797
800
  if (typeof target === 'number')
@@ -873,7 +876,7 @@ async function resolveAppTarget(self, target) {
873
876
  if ('pid' in target) {
874
877
  const asPid = await byPid(Number(target.pid));
875
878
  if (asPid === null) {
876
- throw new Error(`pid ${target.pid} has no launch record — only journalled residents carry one`);
879
+ throw new Error(`pid ${target.pid} is not running and has no launch record`);
877
880
  }
878
881
  return asPid;
879
882
  }
@@ -898,32 +901,10 @@ async function resolveAppTarget(self, target) {
898
901
  */
899
902
  export async function rpcEnsureDurableApp(self, input) {
900
903
  await ensureProgrammaticReady(self);
901
- const owner = input.owner;
902
- if (typeof owner !== 'string' || owner.length === 0) {
903
- throw new Error('ensureDurableApp: owner must be a non-empty string');
904
- }
905
- const occupied = new Set(self.portRegistry.getAll().map((entry) => entry.port));
906
- const port = await reservePort(self.ctx, {
907
- owner,
908
- preferredPort: input.preferredPort,
909
- occupiedPorts: occupied,
910
- // Minted here, not at boot: the URL is built from this capability, and
911
- // re-drive re-adopts it out of the same row.
912
- capability: createPortCapability(),
913
- ...(input.visibility !== undefined ? { visibility: input.visibility } : {}),
914
- });
915
- const record = await readPortReservation(self.ctx, port);
916
- if (record?.visibility === 'public' && record.capability !== null) {
917
- // The URL is built on this capability before the app has ever run:
918
- // publish it to the directory now, loudly refusing a deployment whose
919
- // binding is missing rather than minting an unroutable URL.
920
- await bindPublicPortCapability(self, record.capability, port);
921
- }
922
- return {
923
- port,
924
- capability: record?.capability ?? null,
925
- visibility: record?.visibility ?? 'scoped',
926
- };
904
+ // The composed manager owns the durable-app verbs — one implementation,
905
+ // capability minted there so a URL handed out before boot is the one the
906
+ // eventual binding re-adopts.
907
+ return self.ensureFacetManager().apps.ensureDurableApp(input);
927
908
  }
928
909
  export async function rpcUnexposePort(self, port) {
929
910
  await ensureProgrammaticReady(self);
@@ -946,26 +927,8 @@ export async function rpcUnexposePort(self, port) {
946
927
  */
947
928
  export async function rpcRemoveDurableApp(self, owner) {
948
929
  await ensureProgrammaticReady(self);
949
- if (typeof owner !== 'string' || owner.length === 0) {
950
- throw new Error('removeDurableApp: owner must be a non-empty string');
951
- }
952
- // Which durable address is being released — the reservation's row names it
953
- // before the manager retires it.
954
- const rows = await self.ctx.storage.list({ prefix: PORT_CAPABILITY_KEY_PREFIX });
955
- let port = null;
956
- for (const [key, record] of rows) {
957
- const parsed = PortRecordSchema.safeParse(record);
958
- if (!parsed.success || parsed.data.owner !== owner)
959
- continue;
960
- const n = Number(key.slice(PORT_CAPABILITY_KEY_PREFIX.length));
961
- if (Number.isInteger(n) && n > 0) {
962
- port = n;
963
- break;
964
- }
965
- }
966
- self.ensureFacetManager();
967
- const removed = await self.facetManager.removeDurableApp(owner);
968
- return { owner, removed, port };
930
+ // The composed manager owns the durable-app verbs — validation included.
931
+ return self.ensureFacetManager().apps.removeDurableApp(owner);
969
932
  }
970
933
  /**
971
934
  * Route an embedder request that carries a port capability. The embedder has
@@ -983,10 +946,34 @@ export async function rpcRouteCapabilityPort(self, port, capability, request, pa
983
946
  const routed = await self.portRegistry.routeCapabilityRequest(Number(port), String(capability), request, pathname);
984
947
  return routed ?? new Response('Not found', { status: 404 });
985
948
  }
986
- export async function rpcDeleteFile(self, path, options = {}) {
949
+ /**
950
+ * `spawnWorker` for a colocated embedder holding the DO stub: boot the
951
+ * embedder's own Worker-class program — its main module, inline modules and
952
+ * content-addressed text/wasm modules — as one of this session's resident
953
+ * processes, and answer with the pid, the runner's boot payload and the
954
+ * process's facet (`fetch`/`connect`, bound to the resident handle; no
955
+ * release — `killProcess(pid)` ends it). Deliberately absent from the remote
956
+ * HTTP dispatcher: the facet is a live handle, and a remote token holder is
957
+ * not the embedder.
958
+ */
959
+ export async function rpcSpawnWorker(self, workerCode, command, cwd, opts = {}) {
960
+ await ensureProgrammaticReady(self);
961
+ if (typeof workerCode !== 'string' || workerCode.length === 0) {
962
+ throw new Error('spawnWorker: workerCode must be a non-empty string');
963
+ }
964
+ self.ensureFacetManager();
965
+ return self.facetManager.spawnWorker(workerCode, String(command), String(cwd), opts);
966
+ }
967
+ /**
968
+ * `files.delete`. Absent a `cred` this acts as CRED_KERNEL — what it has
969
+ * always done, and the embedder's trusted surface: only a caller holding the
970
+ * DO binding reaches it. A `cred` binds the removal to that identity instead,
971
+ * the same view `SqliteVFS.as(cred)` gives in-process.
972
+ */
973
+ export async function rpcDeleteFile(self, path, options = {}, cred) {
987
974
  await ensureProgrammaticReady(self);
988
975
  const p = String(path).replace(/^\/+/, '');
989
- const vfs = self.sqliteFs.as(CRED_KERNEL);
976
+ const vfs = self.sqliteFs.as(cred === undefined ? CRED_KERNEL : requireVfsCred(cred, 'files.delete'));
990
977
  if (!vfs.exists(p))
991
978
  return;
992
979
  if (vfs.isDirectory(p)) {
@@ -1212,10 +1199,17 @@ function resetInMemorySessionState(self) {
1212
1199
  self.shell = null;
1213
1200
  self.shellProcessPid = null;
1214
1201
  self.terminal = null;
1215
- self.facetManager = null;
1202
+ self.facetManagerComposed = null;
1216
1203
  self.facetProcessManager = null;
1217
1204
  self.esbuildService = null;
1218
1205
  self.viteDevServer = null;
1206
+ // The installer's pre-bundler and the dev server's on-demand path share
1207
+ // this pool; it goes when both of them go.
1208
+ try {
1209
+ self.bundlePool?.dispose();
1210
+ }
1211
+ catch { }
1212
+ self.bundlePool = null;
1219
1213
  self.cirrusReal = null;
1220
1214
  self._cirrusHmrWsClients = null;
1221
1215
  self.nimbusWrangler = null;
@@ -25,24 +25,22 @@
25
25
  */
26
26
  type RoutesHost = any;
27
27
  /**
28
- * Route a request to whatever is listening on a session port.
29
- *
30
- * The one implementation behind every port-addressed surface: `/port/<n>/`,
31
- * `/preview/?port=N`, and the `<port>--<sid>` preview hostname, which the
32
- * router forwards as `/port/<n>/`. They differ only in how the port and the
33
- * inner path are spelled, so they must not differ in what answers.
34
- *
35
- * `mountBase` is the public URL prefix the served app is mounted at for THIS
36
- * request — '' for a root-mounted `<port>--<sid>` host, '/s/<sid>/preview' for
37
- * the preview path. The in-process Cirrus dev server rewrites base-relative
38
- * URLs (module URLs, <base href>, BASE_URL, router basename), so it is handed
39
- * the base directly: the generic port proxy strips the Nimbus base header at
40
- * the untrusted-code boundary and cannot carry it, and a plain user server on
41
- * any other port is mounted at root and needs no rewriting.
28
+ * Restore the dev server a previous isolate left behind.
29
+ *
30
+ * Hibernation takes the ViteDevServer and the whole port registry with it;
31
+ * only the `vite-config` blob survives in DO storage. Restoring it is the
32
+ * first thing every route that can reach that server does, so a woken session
33
+ * serves on all of them rather than on whichever one happened to carry the
34
+ * restore.
35
+ *
36
+ * `onlyPort` scopes the restore to a config that would listen there: a
37
+ * request for a port nothing ever persisted stays an honest 502, and a port
38
+ * something else already holds is left alone.
39
+ *
40
+ * Idempotent, and silent on failure — a session with no dev server to restore
41
+ * is the normal case, not an error.
42
42
  */
43
- export declare function routeToSessionPort(self: RoutesHost, port: number, request: Request, innerPath: string, mountBase: string, capability?: string): Promise<Response>;
44
- /** Route a capability-authenticated embedder request to a guest HTTP server. */
45
- export declare function routeCapabilityPort(self: RoutesHost, port: number, capability: string, request: Request, innerPath: string): Promise<Response>;
43
+ export declare function restorePersistedDevServer(self: RoutesHost, onlyPort?: number): Promise<void>;
46
44
  /**
47
45
  * The name-addressed door: `/app/<name>/…` — what the scoped `<name>--<sid>`
48
46
  * host is forwarded as, and reachable in path form as `/s/<sid>/app/<name>/`.
@@ -52,6 +50,21 @@ export declare function routeCapabilityPort(self: RoutesHost, port: number, capa
52
50
  * nothing holds is a 404, never a guess.
53
51
  */
54
52
  export declare function routeToSessionApp(self: RoutesHost, name: string, request: Request, innerPath: string, capability?: string): Promise<Response>;
53
+ /**
54
+ * Accept a Vite HMR WebSocket for the running cirrus-real facet and wire it
55
+ * into the facet's HMR bridge.
56
+ *
57
+ * Handled in the DO, never through the port-registry proxy: that proxy moves a
58
+ * Request/Response over RPC and drops the `webSocket` handshake, so a HMR
59
+ * upgrade routed through it hangs. This is the ONE place a preview HMR socket
60
+ * is accepted — shared by the `/preview/` path and the `<port>--<sid>` host's
61
+ * `/port/N` route, so HMR works the same on both.
62
+ *
63
+ * ctx.acceptWebSocket (hibernatable) is required because HMR messages arrive
64
+ * from a DIFFERENT request context (the facet's long-poll RPC), and workerd
65
+ * forbids cross-request I/O on a `server.accept()`'d socket.
66
+ */
67
+ export declare function acceptCirrusHmrWs(self: RoutesHost, request: Request): Response;
55
68
  export declare function handleFetch(self: RoutesHost, request: Request): Promise<Response>;
56
69
  export {};
57
70
  //# sourceMappingURL=routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/session/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAyDH,KAAK,UAAU,GAAG,GAAG,CAAC;AA2HtB;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC,CAyDnB;AA2BD,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,CAAC,CAEnB;AAeD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC,CAMnB;AA4FD,wBAAsB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmhCvF"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/session/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAkEH,KAAK,UAAU,GAAG,GAAG,CAAC;AAyBtB;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFlG;AAeD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC,CAMnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,CAiB9E;AAmDD,wBAAsB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0hCvF"}