@nimbus-sh/core 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 (96) hide show
  1. package/README.md +47 -26
  2. package/dist/_shared/byte-stream.d.ts +5 -4
  3. package/dist/_shared/byte-stream.d.ts.map +1 -1
  4. package/dist/_shared/real-node-imports.d.ts +7 -0
  5. package/dist/_shared/real-node-imports.d.ts.map +1 -1
  6. package/dist/_shared/real-node-imports.js +8 -0
  7. package/dist/_shared/tarball.d.ts +24 -5
  8. package/dist/_shared/tarball.d.ts.map +1 -1
  9. package/dist/_shared/tarball.js +42 -5
  10. package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
  11. package/dist/_shared/vfs-write-ledger.js +22 -16
  12. package/dist/runtime/comment-strip.d.ts +32 -29
  13. package/dist/runtime/comment-strip.d.ts.map +1 -1
  14. package/dist/runtime/comment-strip.js +273 -51
  15. package/dist/runtime/cpython-runner.d.ts +1 -0
  16. package/dist/runtime/cpython-runner.d.ts.map +1 -1
  17. package/dist/runtime/cpython-runner.js +1 -1
  18. package/dist/runtime/esbuild-service.d.ts +38 -42
  19. package/dist/runtime/esbuild-service.d.ts.map +1 -1
  20. package/dist/runtime/esbuild-service.js +500 -388
  21. package/dist/runtime/javascript-ast.d.ts.map +1 -1
  22. package/dist/runtime/javascript-ast.js +45 -7
  23. package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
  24. package/dist/runtime/opentui-wasm-backend.js +18 -16
  25. package/dist/runtime/port-registry.d.ts +4 -2
  26. package/dist/runtime/port-registry.d.ts.map +1 -1
  27. package/dist/runtime/port-registry.js +60 -8
  28. package/dist/runtime/ruby-runner.d.ts +1 -0
  29. package/dist/runtime/ruby-runner.d.ts.map +1 -1
  30. package/dist/runtime/ruby-runner.js +1 -0
  31. package/dist/shell/stdin-adapter.d.ts +11 -0
  32. package/dist/shell/stdin-adapter.d.ts.map +1 -0
  33. package/dist/shell/stdin-adapter.js +59 -0
  34. package/dist/shell/unix-commands.d.ts +2 -1
  35. package/dist/shell/unix-commands.d.ts.map +1 -1
  36. package/dist/shell/unix-commands.js +1078 -50
  37. package/dist/substrate/lifo/commands/io/dd.js +1 -1
  38. package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
  39. package/dist/substrate/lifo/commands/net/curl.js +270 -54
  40. package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
  41. package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
  42. package/dist/substrate/lifo/commands/system/npm.js +25 -43
  43. package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
  44. package/dist/substrate/lifo/commands/text/head.js +2 -1
  45. package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
  46. package/dist/substrate/lifo/commands/text/sed.js +499 -117
  47. package/dist/substrate/lifo/commands/types.d.ts +8 -4
  48. package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
  49. package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
  50. package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
  51. package/dist/substrate/lifo/node-compat/zlib.js +122 -23
  52. package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
  53. package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
  54. package/dist/substrate/lifo/shell/interpreter.js +79 -29
  55. package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
  56. package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
  57. package/dist/substrate/lifo/shell/pipe.js +62 -18
  58. package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
  59. package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
  60. package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
  61. package/dist/vfs/sqlite-vfs.d.ts +19 -0
  62. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  63. package/dist/vfs/sqlite-vfs.js +106 -23
  64. package/dist/workspace/nimbus-workspace.d.ts +22 -1
  65. package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
  66. package/dist/workspace/nimbus-workspace.js +19 -3
  67. package/dist/workspace/supervisor-op.d.ts +107 -0
  68. package/dist/workspace/supervisor-op.d.ts.map +1 -0
  69. package/dist/workspace/supervisor-op.js +224 -0
  70. package/package.json +2 -2
  71. package/src/_shared/byte-stream.ts +5 -4
  72. package/src/_shared/real-node-imports.ts +8 -0
  73. package/src/_shared/tarball.ts +60 -5
  74. package/src/_shared/vfs-write-ledger.ts +22 -16
  75. package/src/runtime/comment-strip.ts +218 -52
  76. package/src/runtime/cpython-runner.ts +2 -1
  77. package/src/runtime/esbuild-service.ts +572 -372
  78. package/src/runtime/javascript-ast.ts +40 -9
  79. package/src/runtime/opentui-wasm-backend.ts +19 -17
  80. package/src/runtime/port-registry.ts +66 -8
  81. package/src/runtime/ruby-runner.ts +2 -0
  82. package/src/shell/stdin-adapter.ts +58 -0
  83. package/src/shell/unix-commands.ts +1054 -50
  84. package/src/substrate/lifo/commands/io/dd.ts +2 -2
  85. package/src/substrate/lifo/commands/net/curl.ts +310 -58
  86. package/src/substrate/lifo/commands/system/npm.ts +55 -38
  87. package/src/substrate/lifo/commands/text/head.ts +2 -1
  88. package/src/substrate/lifo/commands/text/sed.ts +519 -109
  89. package/src/substrate/lifo/commands/types.ts +8 -5
  90. package/src/substrate/lifo/node-compat/zlib.ts +155 -26
  91. package/src/substrate/lifo/shell/interpreter.ts +74 -27
  92. package/src/substrate/lifo/shell/pipe.ts +65 -24
  93. package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
  94. package/src/vfs/sqlite-vfs.ts +104 -23
  95. package/src/workspace/nimbus-workspace.ts +30 -3
  96. package/src/workspace/supervisor-op.ts +309 -0
@@ -27,8 +27,11 @@ import type { CommandResult, RunOptions, SandboxFs } from '../substrate/lifo/san
27
27
  import type { ITerminal } from '../substrate/lifo/terminal/ITerminal.js';
28
28
  import { SqliteVFS } from '../vfs/sqlite-vfs.js';
29
29
  import type { SqlDatabase, TransactionHost } from '../runtime/os-contracts.js';
30
+ import { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
30
31
  import type { FacetHost } from '../runtime/facet-host.js';
31
32
  import { type RuntimePackage } from '../runtime/runtime-package.js';
33
+ import { type CtxExports, type FabricComposition } from '@nimbus-sh/platform/composition.js';
34
+ import { type SupervisorOpEnvelope, type SupervisorOpHandler } from './supervisor-op.js';
32
35
  export interface NimbusWorkspaceOptions {
33
36
  /** The host's SQLite. In a Durable Object: `ctx.storage.sql`. */
34
37
  readonly sql: SqlDatabase;
@@ -38,8 +41,14 @@ export interface NimbusWorkspaceOptions {
38
41
  * Every atomic write in the filesystem rests on this being a real
39
42
  * transaction. An implementation that merely calls the callback converts
40
43
  * each one into a torn write that reports success.
44
+ *
45
+ * `NimbusWorkspace.create` is a first-write-wins composition root beside
46
+ * `worker/index.ts` and `loom/actor.ts`: when `ctxExports` is absent the
47
+ * host's ctx `exports` bag is adopted as the isolate's.
41
48
  */
42
- readonly transactions?: TransactionHost;
49
+ readonly transactions?: TransactionHost & {
50
+ readonly exports?: CtxExports;
51
+ };
43
52
  /**
44
53
  * The filesystem already open over `sql`, for a host that has one.
45
54
  *
@@ -106,6 +115,15 @@ export interface NimbusWorkspaceOptions {
106
115
  * carry REPLs and a resident-process substrate this cannot reach.
107
116
  */
108
117
  readonly facets?: FacetHost;
118
+ /** Isolate-wide fabric composition, including the hosting namespace. */
119
+ readonly fabric?: FabricComposition;
120
+ /** Explicit exports override the bag on the transaction host. */
121
+ readonly ctxExports?: CtxExports;
122
+ /** The process table that allocated supervisor-binding pids. */
123
+ readonly processes?: SessionProcessSupervisor;
124
+ readonly processOutput?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
125
+ /** Host operations, including overrides for host-specific accounting. */
126
+ readonly supervisorOps?: Readonly<Record<string, SupervisorOpHandler>>;
109
127
  }
110
128
  /**
111
129
  * A durable filesystem and a shell over it.
@@ -120,6 +138,7 @@ export interface NimbusWorkspaceOptions {
120
138
  */
121
139
  export declare class NimbusWorkspace {
122
140
  private readonly sql;
141
+ private readonly supervisorOps;
123
142
  /**
124
143
  * Credentialed and mount-aware. Acts as the session user, never as the
125
144
  * kernel: a pid-less caller must not gain more authority than the shell it
@@ -142,6 +161,8 @@ export declare class NimbusWorkspace {
142
161
  private constructor();
143
162
  static create(options: NimbusWorkspaceOptions): Promise<NimbusWorkspace>;
144
163
  exec(command: string, options?: RunOptions): Promise<CommandResult>;
164
+ /** The hosting object forwards its supervisorOp RPC to this method. */
165
+ supervisorOp(envelope: SupervisorOpEnvelope): Promise<unknown>;
145
166
  /**
146
167
  * Apply the login files, and begin reading the terminal when there is one.
147
168
  *
@@ -1 +1 @@
1
- {"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,8 @@ import { rehydrateInstalledRuntimesView, } from '../runtime/installed-runtimes.j
34
34
  import { seedRuntimePackage } from '../runtime/runtime-package.js';
35
35
  import { registerUnixCommands } from '../shell/unix-commands.js';
36
36
  import { installPathExecResolver } from '../shell/exec-dispatch.js';
37
+ import { adoptCtxExports, composeFabric } from '@nimbus-sh/platform/composition.js';
38
+ import { createSupervisorOpHandler } from './supervisor-op.js';
37
39
  /**
38
40
  * A durable filesystem and a shell over it.
39
41
  *
@@ -47,6 +49,7 @@ import { installPathExecResolver } from '../shell/exec-dispatch.js';
47
49
  */
48
50
  export class NimbusWorkspace {
49
51
  sql;
52
+ supervisorOps;
50
53
  /**
51
54
  * Credentialed and mount-aware. Acts as the session user, never as the
52
55
  * kernel: a pid-less caller must not gain more authority than the shell it
@@ -66,8 +69,9 @@ export class NimbusWorkspace {
66
69
  */
67
70
  env;
68
71
  commands;
69
- constructor(vfs, kernel, shell, registry, env, sql) {
72
+ constructor(vfs, kernel, shell, registry, env, sql, supervisorOps) {
70
73
  this.sql = sql;
74
+ this.supervisorOps = supervisorOps;
71
75
  this.vfs = vfs;
72
76
  this.kernel = kernel;
73
77
  this.shell = shell;
@@ -80,6 +84,11 @@ export class NimbusWorkspace {
80
84
  this.fs = new SandboxFsImpl(kernel.vfs.as(CRED_SESSION_USER), () => shell.getCwd());
81
85
  }
82
86
  static async create(options) {
87
+ if (options.fabric)
88
+ composeFabric(options.fabric);
89
+ const exports = options.ctxExports ?? options.transactions?.exports;
90
+ if (exports)
91
+ adoptCtxExports(exports);
83
92
  const vfs = options.vfs ?? openFilesystem(options);
84
93
  const mounts = options.mounts ?? DEFAULT_MOUNT_POINTS;
85
94
  seedBaseFilesystem(vfs, mounts);
@@ -118,11 +127,18 @@ export class NimbusWorkspace {
118
127
  home,
119
128
  });
120
129
  }
121
- return new NimbusWorkspace(vfs, kernel, shell, registry, env, options.sql);
130
+ const supervisorOps = createSupervisorOpHandler({
131
+ vfs, processes: options.processes, output: options.processOutput, extend: options.supervisorOps,
132
+ });
133
+ return new NimbusWorkspace(vfs, kernel, shell, registry, env, options.sql, supervisorOps);
122
134
  }
123
135
  exec(command, options) {
124
136
  return this.commands.run(command, options);
125
137
  }
138
+ /** The hosting object forwards its supervisorOp RPC to this method. */
139
+ supervisorOp(envelope) {
140
+ return this.supervisorOps(envelope);
141
+ }
126
142
  /**
127
143
  * Apply the login files, and begin reading the terminal when there is one.
128
144
  *
@@ -243,7 +259,7 @@ async function registerWasmRuntimes(deps) {
243
259
  getEsbuild: () => {
244
260
  if (!esbuild) {
245
261
  esbuild = import('../runtime/esbuild-service.js')
246
- .then((module) => new module.EsbuildService(deps.vfs));
262
+ .then((module) => new module.EsbuildService(deps.vfs.as(CRED_KERNEL)));
247
263
  }
248
264
  return esbuild;
249
265
  },
@@ -0,0 +1,107 @@
1
+ import type { SqliteVFS } from '../vfs/sqlite-vfs.js';
2
+ import { type VfsCred } from '../runtime/os-contracts.js';
3
+ import { SqliteRuntimeFsBridge } from '../runtime/sqlite-runtime-fs-bridge.js';
4
+ import type { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
5
+ /** Identity comes from the supervisor binding, never from facet arguments. */
6
+ export interface SupervisorOpEnvelope {
7
+ readonly op: SupervisorOpName;
8
+ readonly args?: readonly unknown[];
9
+ readonly pid?: number;
10
+ readonly writerId?: string;
11
+ readonly mutationOwner?: string;
12
+ readonly stream?: ReadableStream<Uint8Array>;
13
+ }
14
+ export type SupervisorOpHandler = (envelope: SupervisorOpEnvelope, tools: SupervisorOpTools) => unknown;
15
+ export interface SupervisorOpDeps {
16
+ readonly vfs: SqliteVFS;
17
+ /** Absent a process table, operations use the unprivileged session user. */
18
+ readonly processes?: SessionProcessSupervisor;
19
+ readonly output?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
20
+ /**
21
+ * The host's `_rpc*` surface for ops beyond the native set — an in-process
22
+ * workspace's dispatch record, or the session itself for
23
+ * `sessionSupervisorOps`. A native op never consults it.
24
+ */
25
+ readonly host?: SupervisorOpHost;
26
+ /**
27
+ * A pid-keyed bridge cache to serve the native ops from. Supplied by the
28
+ * session so `supervisorBridge` hands callers the same bridges the handler
29
+ * uses; in-process workspaces let the handler build its own.
30
+ */
31
+ readonly bridge?: SupervisorOpBridgeStore;
32
+ readonly extend?: Partial<Record<SupervisorOpName, SupervisorOpHandler>>;
33
+ }
34
+ /**
35
+ * One slot in an op's argument plan: a number takes `envelope.args[n]`, a
36
+ * name takes the envelope's identity field (`pid`, `writerId`, `stream`,
37
+ * `mutationOwner`). The envelope is always the shape — a host never
38
+ * re-parses it.
39
+ */
40
+ export type SupervisorOpArg = number | 'pid' | 'writerId' | 'stream' | 'mutationOwner';
41
+ export interface SupervisorOpRoute {
42
+ /** The host method this op dispatches to. */
43
+ readonly method: string;
44
+ /** Positional plan for the host call — envelope fields, not raw args. */
45
+ readonly args: readonly SupervisorOpArg[];
46
+ }
47
+ /**
48
+ * The embedder's dispatch surface — the `_rpc*` methods SUPERVISOR_OP_ROUTES
49
+ * names. The session satisfies it with its own class; an in-process
50
+ * workspace supplies its host object.
51
+ */
52
+ export interface SupervisorOpHost {
53
+ readonly [method: string]: unknown;
54
+ }
55
+ /**
56
+ * The canonical supervisor op set — every operation the supervisor RPC
57
+ * serves. Three consumers key on these names:
58
+ *
59
+ * - `sessionSupervisorOp` (worker): the DO's host — `extend` overrides for
60
+ * hosted accounting plus the non-filesystem ops it answers itself.
61
+ * - `createSupervisorOpHandler`: an in-process workspace — filesystem ops
62
+ * run against the VFS directly; every other op dispatches to
63
+ * `deps.host` through SUPERVISOR_OP_ROUTES, the embedder's `_rpc*`
64
+ * surface.
65
+ * - `supervisor-host-dispatch`: the test — derives every case's delegate
66
+ * and expected arguments from SUPERVISOR_OP_ROUTES, not a copied list.
67
+ *
68
+ * An op absent here is not served, on any host.
69
+ */
70
+ export declare const SUPERVISOR_OPS: readonly ["readFile", "readFileBytes", "writeFile", "stat", "lstat", "hasLegacySymlinkUnder", "utimes", "chmod", "access", "chown", "setUmask", "readdir", "exists", "mkdir", "rmdir", "rename", "unlink", "readlink", "symlink", "fsAcquire", "fsRevision", "fsList", "wsOpen", "wsPoll", "wsSend", "wsClose", "fsOpen", "fsRead", "fsWrite", "fsClose", "fsReadRange", "fsReadRangeUncached", "fsReadBatch", "fsWriteRange", "fsAppend", "fsAppendAck", "fsTruncate", "writeBatch", "writeBatchStream", "putRegistryEntries", "stdout", "stderr", "prefetch", "registerPort", "unregisterPort", "reportExit", "routeLoopback", "transform", "cpSpawn", "cpStdinWrite", "cpStdinEnd", "cpReadStdin", "cpReadOutput", "cpDrainOutput", "cpKill", "cpWait", "cpDispatchInline"];
71
+ export type SupervisorOpName = (typeof SUPERVISOR_OPS)[number];
72
+ /**
73
+ * What the shared handler hands a host override: the pid-keyed bridge and
74
+ * the deps it was built with, so an override that wraps a filesystem op
75
+ * (read-allocation accounting, stream-drain timing) reuses the same bridge
76
+ * the default handler would have used instead of caching its own.
77
+ */
78
+ export interface SupervisorOpTools {
79
+ readonly bridge: (pid?: number) => SqliteRuntimeFsBridge;
80
+ readonly vfs: SqliteVFS;
81
+ readonly cred: (pid?: number) => VfsCred;
82
+ readonly output?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
83
+ }
84
+ /** The host-side argument plan per op — how an envelope becomes an _rpc* call. */
85
+ export declare const SUPERVISOR_OP_ROUTES: Readonly<Record<SupervisorOpName, SupervisorOpRoute>>;
86
+ /**
87
+ * The ops `createSupervisorOpHandler` serves natively — one pid-keyed
88
+ * filesystem bridge, plus the output stream. A session's `extend` overrides
89
+ * never cover these by accident: `sessionSupervisorOps` builds its delegate
90
+ * set from this name list, not a hand-copied table.
91
+ */
92
+ export declare const SUPERVISOR_NATIVE_OPS: ReadonlySet<string>;
93
+ /** The pid-keyed bridge cache behind the native filesystem ops. */
94
+ export interface SupervisorOpBridgeStore {
95
+ readonly bridge: (pid?: number) => SqliteRuntimeFsBridge;
96
+ /** Drop a pid's bridge — a process exit ends its credential's validity. */
97
+ readonly forget: (pid: number) => void;
98
+ }
99
+ /**
100
+ * Exported so the session's `supervisorBridge` — used by RPC bodies the
101
+ * envelope delegates back to (fsOpen, fsAppend, writeBatch, …) — is the
102
+ * same cache the handler's native ops serve from, never a second one.
103
+ */
104
+ export declare function createSupervisorBridgeStore(deps: Pick<SupervisorOpDeps, 'vfs' | 'processes'>): SupervisorOpBridgeStore;
105
+ /** One dispatch method lets any host serve its workspace to process facets. */
106
+ export declare function createSupervisorOpHandler(deps: SupervisorOpDeps): (envelope: SupervisorOpEnvelope) => Promise<unknown>;
107
+ //# sourceMappingURL=supervisor-op.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-op.d.ts","sourceRoot":"","sources":["../../src/workspace/supervisor-op.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,224 @@
1
+ import { CRED_SESSION_USER } from '../runtime/os-contracts.js';
2
+ import { SqliteRuntimeFsBridge } from '../runtime/sqlite-runtime-fs-bridge.js';
3
+ import { getSymlinkRegistry } from '../vfs/symlink-registry.js';
4
+ function stringArg(envelope, index) {
5
+ const value = envelope.args?.[index];
6
+ if (typeof value !== 'string') {
7
+ throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a string`);
8
+ }
9
+ return value;
10
+ }
11
+ function numberArg(envelope, index) {
12
+ const value = envelope.args?.[index];
13
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
14
+ throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a number`);
15
+ }
16
+ return value;
17
+ }
18
+ function contentArg(envelope, index) {
19
+ const value = envelope.args?.[index];
20
+ if (typeof value === 'string' || value instanceof Uint8Array)
21
+ return value;
22
+ if (value instanceof ArrayBuffer)
23
+ return new Uint8Array(value);
24
+ throw new Error(`supervisor op ${envelope.op}: argument ${index} must be bytes or text`);
25
+ }
26
+ function credFor(deps, pid) {
27
+ if (pid === undefined)
28
+ return CRED_SESSION_USER;
29
+ if (!Number.isInteger(pid) || pid <= 0) {
30
+ throw new Error('supervisor op: filesystem operation requires a valid process pid');
31
+ }
32
+ return deps.processes ? deps.processes.cred(pid) : CRED_SESSION_USER;
33
+ }
34
+ /**
35
+ * The canonical supervisor op set — every operation the supervisor RPC
36
+ * serves. Three consumers key on these names:
37
+ *
38
+ * - `sessionSupervisorOp` (worker): the DO's host — `extend` overrides for
39
+ * hosted accounting plus the non-filesystem ops it answers itself.
40
+ * - `createSupervisorOpHandler`: an in-process workspace — filesystem ops
41
+ * run against the VFS directly; every other op dispatches to
42
+ * `deps.host` through SUPERVISOR_OP_ROUTES, the embedder's `_rpc*`
43
+ * surface.
44
+ * - `supervisor-host-dispatch`: the test — derives every case's delegate
45
+ * and expected arguments from SUPERVISOR_OP_ROUTES, not a copied list.
46
+ *
47
+ * An op absent here is not served, on any host.
48
+ */
49
+ export const SUPERVISOR_OPS = [
50
+ 'readFile', 'readFileBytes', 'writeFile', 'stat', 'lstat',
51
+ 'hasLegacySymlinkUnder', 'utimes', 'chmod', 'access', 'chown', 'setUmask',
52
+ 'readdir', 'exists', 'mkdir', 'rmdir', 'rename', 'unlink', 'readlink',
53
+ 'symlink', 'fsAcquire', 'fsRevision', 'fsList', 'wsOpen', 'wsPoll',
54
+ 'wsSend', 'wsClose', 'fsOpen', 'fsRead', 'fsWrite', 'fsClose',
55
+ 'fsReadRange', 'fsReadRangeUncached', 'fsReadBatch', 'fsWriteRange',
56
+ 'fsAppend', 'fsAppendAck', 'fsTruncate', 'writeBatch', 'writeBatchStream',
57
+ 'putRegistryEntries', 'stdout', 'stderr', 'prefetch', 'registerPort',
58
+ 'unregisterPort', 'reportExit', 'routeLoopback', 'transform', 'cpSpawn',
59
+ 'cpStdinWrite', 'cpStdinEnd', 'cpReadStdin', 'cpReadOutput',
60
+ 'cpDrainOutput', 'cpKill', 'cpWait', 'cpDispatchInline',
61
+ ];
62
+ /** The host-side argument plan per op — how an envelope becomes an _rpc* call. */
63
+ export const SUPERVISOR_OP_ROUTES = {
64
+ readFile: { method: '_rpcReadFile', args: [0, 'pid'] },
65
+ readFileBytes: { method: '_rpcReadFileBytes', args: [0, 'pid'] },
66
+ writeFile: { method: '_rpcWriteFile', args: [0, 1, 'pid'] },
67
+ stat: { method: '_rpcStat', args: [0, 'pid'] },
68
+ lstat: { method: '_rpcLstat', args: [0, 'pid'] },
69
+ hasLegacySymlinkUnder: { method: '_rpcHasLegacySymlinkUnder', args: [0, 'pid'] },
70
+ utimes: { method: '_rpcUtimes', args: [0, 1, 2, 'pid'] },
71
+ chmod: { method: '_rpcChmod', args: [0, 1, 'pid'] },
72
+ access: { method: '_rpcAccess', args: [0, 1, 'pid'] },
73
+ chown: { method: '_rpcChown', args: [0, 1, 2, 'pid', 3] },
74
+ setUmask: { method: '_rpcSetUmask', args: [0, 'pid'] },
75
+ readdir: { method: '_rpcReaddir', args: [0, 'pid'] },
76
+ exists: { method: '_rpcExists', args: [0, 'pid'] },
77
+ mkdir: { method: '_rpcMkdir', args: [0, 'pid'] },
78
+ rmdir: { method: '_rpcRmdir', args: [0, 'pid'] },
79
+ rename: { method: '_rpcRename', args: [0, 1, 'pid'] },
80
+ unlink: { method: '_rpcUnlink', args: [0, 'pid'] },
81
+ readlink: { method: '_rpcReadlink', args: [0, 'pid'] },
82
+ symlink: { method: '_rpcSymlink', args: [0, 1, 'pid'] },
83
+ fsAcquire: { method: '_rpcFsAcquire', args: [0, 1, 'pid'] },
84
+ fsRevision: { method: '_rpcFsRevision', args: [0, 'pid'] },
85
+ fsList: { method: '_rpcFsList', args: [0, 1, 'pid'] },
86
+ wsOpen: { method: '_rpcWsOpen', args: [0, 1, 'pid'] },
87
+ wsPoll: { method: '_rpcWsPoll', args: [0, 1, 'pid'] },
88
+ wsSend: { method: '_rpcWsSend', args: [0, 1, 2, 'pid'] },
89
+ wsClose: { method: '_rpcWsClose', args: [0, 1, 2, 'pid'] },
90
+ fsOpen: { method: '_rpcFsOpen', args: [0, 1, 'pid'] },
91
+ fsRead: { method: '_rpcFsRead', args: [0, 1, 2, 'pid'] },
92
+ fsWrite: { method: '_rpcFsWrite', args: [0, 1, 2, 'pid'] },
93
+ fsClose: { method: '_rpcFsClose', args: [0, 'pid'] },
94
+ fsReadRange: { method: '_rpcFsReadRange', args: [0, 1, 2, 'pid'] },
95
+ fsReadRangeUncached: { method: '_rpcFsReadRangeUncached', args: [0, 1, 2, 'pid'] },
96
+ fsReadBatch: { method: '_rpcFsReadBatch', args: [0, 'pid'] },
97
+ fsWriteRange: { method: '_rpcFsWriteRange', args: [0, 1, 2, 'pid'] },
98
+ fsAppend: { method: '_rpcFsAppend', args: [0, 'writerId', 1, 2, 3, 'pid'] },
99
+ fsAppendAck: { method: '_rpcFsAppendAck', args: ['writerId', 0, 1, 'pid'] },
100
+ fsTruncate: { method: '_rpcFsTruncate', args: [0, 1, 'pid'] },
101
+ writeBatch: { method: '_rpcWriteBatch', args: [0, 'pid'] },
102
+ writeBatchStream: { method: '_rpcWriteBatchStream', args: ['stream', 'mutationOwner', 'pid'] },
103
+ putRegistryEntries: { method: '_rpcPutRegistryEntries', args: [0] },
104
+ stdout: { method: '_rpcStdout', args: ['pid', 0] },
105
+ stderr: { method: '_rpcStderr', args: ['pid', 0] },
106
+ prefetch: { method: '_rpcPrefetch', args: [0, 1] },
107
+ registerPort: { method: '_rpcRegisterPort', args: ['pid', 0] },
108
+ unregisterPort: { method: '_rpcUnregisterPort', args: [0] },
109
+ reportExit: { method: '_rpcReportExit', args: ['pid', 0, 1] },
110
+ routeLoopback: { method: '_rpcRouteLoopback', args: [0, 1] },
111
+ transform: { method: '_rpcTransform', args: [0, 1] },
112
+ cpSpawn: { method: '_rpcCpSpawn', args: [0] },
113
+ cpStdinWrite: { method: '_rpcCpStdinWrite', args: [0, 1] },
114
+ cpStdinEnd: { method: '_rpcCpStdinEnd', args: [0] },
115
+ cpReadStdin: { method: '_rpcCpReadStdin', args: [0, 1] },
116
+ cpReadOutput: { method: '_rpcCpReadOutput', args: [0, 1, 2, 3] },
117
+ cpDrainOutput: { method: '_rpcCpDrainOutput', args: [0] },
118
+ cpKill: { method: '_rpcCpKill', args: [0, 1] },
119
+ cpWait: { method: '_rpcCpWait', args: [0, 1] },
120
+ cpDispatchInline: { method: '_rpcCpDispatchInline', args: [0, 1] },
121
+ };
122
+ /**
123
+ * The ops `createSupervisorOpHandler` serves natively — one pid-keyed
124
+ * filesystem bridge, plus the output stream. A session's `extend` overrides
125
+ * never cover these by accident: `sessionSupervisorOps` builds its delegate
126
+ * set from this name list, not a hand-copied table.
127
+ */
128
+ export const SUPERVISOR_NATIVE_OPS = new Set([
129
+ 'readFile', 'readFileBytes', 'stat', 'lstat', 'exists', 'readdir',
130
+ 'readlink', 'fsReadRange', 'fsReadRangeUncached', 'fsRevision',
131
+ 'hasLegacySymlinkUnder', 'writeFile', 'mkdir', 'rmdir', 'unlink',
132
+ 'rename', 'symlink', 'chmod', 'utimes', 'fsTruncate',
133
+ 'writeBatchStream', 'stdout', 'stderr',
134
+ ]);
135
+ /**
136
+ * Exported so the session's `supervisorBridge` — used by RPC bodies the
137
+ * envelope delegates back to (fsOpen, fsAppend, writeBatch, …) — is the
138
+ * same cache the handler's native ops serve from, never a second one.
139
+ */
140
+ export function createSupervisorBridgeStore(deps) {
141
+ const bridges = new Map();
142
+ return {
143
+ bridge: (pid) => {
144
+ const key = pid ?? 0;
145
+ const credentialed = deps.vfs.as(credFor(deps, pid));
146
+ const held = bridges.get(key);
147
+ if (held) {
148
+ held.updateCredential(credentialed);
149
+ return held;
150
+ }
151
+ const built = new SqliteRuntimeFsBridge(credentialed, deps.vfs);
152
+ bridges.set(key, built);
153
+ return built;
154
+ },
155
+ forget: (pid) => { bridges.delete(pid); },
156
+ };
157
+ }
158
+ /** One dispatch method lets any host serve its workspace to process facets. */
159
+ export function createSupervisorOpHandler(deps) {
160
+ const bridgeFor = deps.bridge?.bridge ?? createSupervisorBridgeStore(deps).bridge;
161
+ const tools = {
162
+ bridge: bridgeFor,
163
+ vfs: deps.vfs,
164
+ cred: (pid) => credFor(deps, pid),
165
+ output: deps.output,
166
+ };
167
+ const ops = {
168
+ readFile: async (e) => {
169
+ const bytes = await bridgeFor(e.pid).readFile(stringArg(e, 0));
170
+ return bytes === null ? null : new TextDecoder().decode(bytes);
171
+ },
172
+ readFileBytes: (e) => bridgeFor(e.pid).readFile(stringArg(e, 0)),
173
+ stat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0)),
174
+ lstat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0), { followSymlinks: false }),
175
+ exists: async (e) => (await bridgeFor(e.pid).stat(stringArg(e, 0))) !== null,
176
+ readdir: (e) => bridgeFor(e.pid).readdir(stringArg(e, 0)),
177
+ readlink: (e) => bridgeFor(e.pid).readlink(stringArg(e, 0)),
178
+ fsReadRange: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
179
+ fsReadRangeUncached: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2), { cached: false }),
180
+ fsRevision: (e) => bridgeFor(e.pid).revision(e.args?.[0] === undefined ? undefined : stringArg(e, 0)),
181
+ hasLegacySymlinkUnder: (e) => getSymlinkRegistry(deps.vfs).hasAtOrBelow(stringArg(e, 0)),
182
+ writeFile: (e) => bridgeFor(e.pid).writeFile(stringArg(e, 0), contentArg(e, 1)),
183
+ mkdir: (e) => bridgeFor(e.pid).mkdir(stringArg(e, 0), { recursive: true }),
184
+ rmdir: (e) => bridgeFor(e.pid).rmdir(stringArg(e, 0)),
185
+ unlink: (e) => bridgeFor(e.pid).unlink(stringArg(e, 0)),
186
+ rename: (e) => bridgeFor(e.pid).rename(stringArg(e, 0), stringArg(e, 1)),
187
+ symlink: (e) => bridgeFor(e.pid).symlink(stringArg(e, 0), stringArg(e, 1)),
188
+ chmod: (e) => bridgeFor(e.pid).chmod(stringArg(e, 0), numberArg(e, 1)),
189
+ utimes: (e) => bridgeFor(e.pid).utimes(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
190
+ fsTruncate: (e) => bridgeFor(e.pid).truncate(stringArg(e, 0), numberArg(e, 1)),
191
+ writeBatchStream: (e) => {
192
+ if (!e.stream)
193
+ throw new Error('supervisor op writeBatchStream: no stream');
194
+ return deps.vfs.as(credFor(deps, e.pid)).writeStream(e.stream, { mutationOwner: e.mutationOwner });
195
+ },
196
+ stdout: (e) => { deps.output?.('stdout', e.pid ?? 0, stringArg(e, 0)); },
197
+ stderr: (e) => { deps.output?.('stderr', e.pid ?? 0, stringArg(e, 0)); },
198
+ };
199
+ const extend = deps.extend ?? {};
200
+ return async (envelope) => {
201
+ if (!envelope || typeof envelope.op !== 'string') {
202
+ throw new Error('supervisor op: envelope names no operation');
203
+ }
204
+ // Priority: the embedder's own handler → the native filesystem op → the
205
+ // canonical route table onto the host's _rpc* methods. An op in none of
206
+ // these is not served by this host.
207
+ const handler = Object.hasOwn(extend, envelope.op) ? extend[envelope.op]
208
+ : Object.hasOwn(ops, envelope.op) ? ops[envelope.op] : undefined;
209
+ if (handler)
210
+ return handler(envelope, tools);
211
+ const route = Object.hasOwn(SUPERVISOR_OP_ROUTES, envelope.op)
212
+ ? SUPERVISOR_OP_ROUTES[envelope.op] : undefined;
213
+ if (!route)
214
+ throw new Error(`supervisor op: '${envelope.op}' is not served by this host`);
215
+ const host = deps.host;
216
+ if (!host)
217
+ throw new Error(`supervisor op: '${envelope.op}' needs a host that this workspace does not have`);
218
+ const method = host[route.method];
219
+ if (typeof method !== 'function')
220
+ throw new Error(`supervisor op: missing host method ${route.method}`);
221
+ const args = route.args.map((slot) => typeof slot === 'number' ? envelope.args?.[slot] : envelope[slot]);
222
+ return Reflect.apply(method, host, args);
223
+ };
224
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-sh/core",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Backend-agnostic half of Nimbus — the durable filesystem, the shell, and the OS/process contracts, over a host-supplied SQL port.",
5
5
  "keywords": [
6
6
  "filesystem",
@@ -55,7 +55,7 @@
55
55
  "typecheck": "tsc --noEmit"
56
56
  },
57
57
  "dependencies": {
58
- "@nimbus-sh/platform": "^0.1.0",
58
+ "@nimbus-sh/platform": "^0.3.0",
59
59
  "@renovatebot/pep440": "^5.0.0",
60
60
  "acorn": "^8.15.0",
61
61
  "acorn-walk": "^8.3.5",
@@ -18,10 +18,11 @@
18
18
  /**
19
19
  * A command's output destination.
20
20
  *
21
- * `writeBytes` is present on sinks that store bytes verbatim (files). Sinks
22
- * that are inherently textual — the terminal, shell pipes omit it and
23
- * receive decoded text instead. This is a real capability difference, not a
24
- * fallback: a terminal has no way to hold a byte that is not text.
21
+ * `writeBytes` is present on sinks that store bytes verbatim files,
22
+ * `/dev/null`, and shell pipes, which carry the producer's exact bytes.
23
+ * Sinks without it take decoded text instead. This is a real capability
24
+ * difference, not a fallback: a terminal has no way to hold a byte that is
25
+ * not text.
25
26
  */
26
27
  export interface ByteSink {
27
28
  write(text: string): void;
@@ -38,6 +38,13 @@
38
38
  * constructed Session's connect/post are inert.
39
39
  * Tools (e.g. nuxi) that open a Session purely
40
40
  * for optional profiling degrade cleanly.
41
+ * - node:zlib — full surface: every *Sync variant, brotli/zstd,
42
+ * crc32, constants, and streaming create* factories
43
+ * (probe-verified 2026-08-23 at compat date
44
+ * 2026-04-01). Forwarded verbatim by the zlib
45
+ * block in node-shims.ts; results are the host
46
+ * realm's own Buffers, which the widened
47
+ * __BufferMod.isBuffer recognizes.
41
48
  */
42
49
 
43
50
  export function getRealNodeImportsCode(): string {
@@ -49,5 +56,6 @@ import * as __real_diagnostics_channel from 'node:diagnostics_channel';
49
56
  import * as __real_repl from 'node:repl';
50
57
  import * as __real_vm from 'node:vm';
51
58
  import * as __real_inspector from 'node:inspector';
59
+ import * as __real_zlib from 'node:zlib';
52
60
  `.trim();
53
61
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tarball.ts a whole tarball, in memory, as a path→bytes map.
2
+ * Tarball extraction for streaming installers and buffered archive consumers.
3
3
  *
4
4
  * The streaming primitives it walks with (`parseTarHeader`, `streamTarEntries`,
5
5
  * `readableStreamToAsyncIterable`) live in `./tarball-stream.ts` — a
@@ -7,10 +7,8 @@
7
7
  * into a string the loader pool injects into dynamic workers, where an import
8
8
  * would not resolve.
9
9
  *
10
- * This half is for a caller that already holds the bytes: `gem install`, which
11
- * fetches a `.gem` (a tar holding a gzipped tar) and needs both layers open at
12
- * once. An installer streaming a tarball it is still downloading should drive
13
- * `streamTarEntries` itself and never hold the whole thing.
10
+ * Installers use writeTarballStream. extractTarball retains a map for callers
11
+ * such as gem install, which must open an archive nested inside another one.
14
12
  */
15
13
 
16
14
  import {
@@ -18,6 +16,63 @@ import {
18
16
  readableStreamToAsyncIterable,
19
17
  } from './tarball-stream.js';
20
18
 
19
+ export interface TarballWriteTarget {
20
+ exists(path: string): boolean;
21
+ mkdir(path: string, options?: { recursive?: boolean }): void;
22
+ writeFile(path: string, data: Uint8Array | string): unknown;
23
+ }
24
+
25
+ export interface TarballWriteResult {
26
+ /** Regular files written, including the manifest. */
27
+ files: number;
28
+ /** Total decompressed bytes written. */
29
+ bytes: number;
30
+ }
31
+
32
+ const PACKAGE_MANIFEST = 'package.json';
33
+
34
+ /**
35
+ * Stream a gzipped npm archive into a package directory. Entry names are
36
+ * already canonical and prefix-stripped by streamTarEntries. Hold only the
37
+ * current entry and the manifest; write the manifest last so a failed install
38
+ * is not mistaken for a complete package on retry. A second manifest in one
39
+ * archive is a malformed package, not an overwrite. Filesystem failures reject.
40
+ */
41
+ export async function writeTarballStream(
42
+ body: ReadableStream<Uint8Array>,
43
+ targetDir: string,
44
+ vfs: TarballWriteTarget,
45
+ ): Promise<TarballWriteResult> {
46
+ const ensureDir = (path: string): void => {
47
+ if (!vfs.exists(path)) vfs.mkdir(path, { recursive: true });
48
+ };
49
+ ensureDir(targetDir);
50
+ let files = 0;
51
+ let bytes = 0;
52
+ let manifest: Uint8Array | null = null;
53
+ const entries = streamTarEntries(
54
+ readableStreamToAsyncIterable(body.pipeThrough(new DecompressionStream('gzip'))),
55
+ );
56
+ for await (const entry of entries) {
57
+ if (entry.name === PACKAGE_MANIFEST) {
58
+ if (manifest !== null) {
59
+ throw new Error(`tarball for ${targetDir} carried two ${PACKAGE_MANIFEST} entries`);
60
+ }
61
+ manifest = entry.data;
62
+ continue;
63
+ }
64
+ const fullPath = `${targetDir}/${entry.name}`;
65
+ const cut = fullPath.lastIndexOf('/');
66
+ if (cut > 0) ensureDir(fullPath.slice(0, cut));
67
+ await vfs.writeFile(fullPath, entry.data);
68
+ files++;
69
+ bytes += entry.data.length;
70
+ }
71
+ if (!manifest) throw new Error(`tarball for ${targetDir} carried no ${PACKAGE_MANIFEST}`);
72
+ await vfs.writeFile(`${targetDir}/${PACKAGE_MANIFEST}`, manifest);
73
+ return { files: files + 1, bytes: bytes + manifest.length };
74
+ }
75
+
21
76
  /** Extract every regular file. Gzipped input is decompressed first. */
22
77
  export async function extractTarball(
23
78
  tarball: ArrayBuffer,