@nimbus-sh/worker 0.3.0 → 0.5.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 (136) hide show
  1. package/README.md +46 -43
  2. package/dist/_shared/preview-host.d.ts +14 -1
  3. package/dist/_shared/preview-host.d.ts.map +1 -1
  4. package/dist/_shared/preview-host.js +28 -3
  5. package/dist/_shared/session-router.d.ts +43 -0
  6. package/dist/_shared/session-router.d.ts.map +1 -1
  7. package/dist/_shared/session-router.js +59 -12
  8. package/dist/esbuild-wasm-bundle.generated.d.ts +1 -1
  9. package/dist/esbuild-wasm-bundle.generated.js +1 -1
  10. package/dist/facets/cirrus-real.js +1 -1
  11. package/dist/facets/durable-images.d.ts +50 -0
  12. package/dist/facets/durable-images.d.ts.map +1 -0
  13. package/dist/facets/durable-images.js +75 -0
  14. package/dist/facets/durable-slots.d.ts +45 -0
  15. package/dist/facets/durable-slots.d.ts.map +1 -0
  16. package/dist/facets/durable-slots.js +94 -0
  17. package/dist/facets/esbuild-transform.d.ts +10 -0
  18. package/dist/facets/esbuild-transform.d.ts.map +1 -0
  19. package/dist/facets/esbuild-transform.js +36 -0
  20. package/dist/facets/manager.d.ts +167 -12
  21. package/dist/facets/manager.d.ts.map +1 -1
  22. package/dist/facets/manager.js +575 -113
  23. package/dist/facets/opencode-staging.d.ts.map +1 -1
  24. package/dist/facets/opencode-staging.js +3 -4
  25. package/dist/facets/real-vite-hmr.js +1 -1
  26. package/dist/facets/vite-dev-server.d.ts +4 -4
  27. package/dist/facets/vite-dev-server.js +5 -5
  28. package/dist/git/commands.d.ts +22 -0
  29. package/dist/git/commands.d.ts.map +1 -1
  30. package/dist/git/commands.js +431 -424
  31. package/dist/git/network-facet.d.ts.map +1 -1
  32. package/dist/git/network-facet.js +6 -5
  33. package/dist/git-bundle.generated.d.ts +2 -2
  34. package/dist/git-bundle.generated.d.ts.map +1 -1
  35. package/dist/git-bundle.generated.js +3 -3
  36. package/dist/index.d.ts +2 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +11 -2
  39. package/dist/loaders/child-process/spawn-facet.js +1 -1
  40. package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
  41. package/dist/loaders/child-process/spawn-pool.js +2 -2
  42. package/dist/loaders/generated-workers.d.ts +2 -2
  43. package/dist/loaders/generated-workers.d.ts.map +1 -1
  44. package/dist/loaders/generated-workers.js +3 -3
  45. package/dist/loaders/npm-resolve-preamble.d.ts +3 -3
  46. package/dist/loaders/npm-resolve-preamble.js +3 -3
  47. package/dist/loaders/pre-bundle-preamble.d.ts +5 -5
  48. package/dist/loaders/pre-bundle-preamble.js +6 -6
  49. package/dist/node-shims-artifact.generated.js +3 -3
  50. package/dist/npm/install-batch-facet.d.ts +1 -1
  51. package/dist/npm/install-batch-facet.js +1 -1
  52. package/dist/npm/installer.d.ts +6 -6
  53. package/dist/npm/installer.d.ts.map +1 -1
  54. package/dist/npm/installer.js +28 -27
  55. package/dist/npm/pre-bundle-facet.d.ts +2 -2
  56. package/dist/npm/pre-bundle-facet.js +5 -5
  57. package/dist/npm/r2-cache.d.ts +1 -1
  58. package/dist/npm/resolve-facet.d.ts +1 -1
  59. package/dist/npm/resolve-facet.js +1 -1
  60. package/dist/npm/resolve-one-facet.d.ts +5 -5
  61. package/dist/npm/resolve-one-facet.js +5 -5
  62. package/dist/router/index.d.ts.map +1 -1
  63. package/dist/router/index.js +61 -4
  64. package/dist/router/public-directory-do.d.ts +17 -0
  65. package/dist/router/public-directory-do.d.ts.map +1 -0
  66. package/dist/router/public-directory-do.js +16 -0
  67. package/dist/router/public-directory.d.ts +75 -0
  68. package/dist/router/public-directory.d.ts.map +1 -0
  69. package/dist/router/public-directory.js +115 -0
  70. package/dist/router/remote-api.d.ts +9 -1
  71. package/dist/router/remote-api.d.ts.map +1 -1
  72. package/dist/router/remote-api.js +20 -3
  73. package/dist/runtime/bun-repl.d.ts +1 -1
  74. package/dist/runtime/bun-repl.js +3 -3
  75. package/dist/runtime/esbuild-wasm-bytes.js +1 -1
  76. package/dist/runtime/facet-loader-host.d.ts +2 -2
  77. package/dist/runtime/facet-loader-host.js +3 -3
  78. package/dist/runtime/node-repl.js +2 -2
  79. package/dist/runtime/node-shims-artifact.js +1 -1
  80. package/dist/runtime/node-shims.d.ts +4 -1
  81. package/dist/runtime/node-shims.d.ts.map +1 -1
  82. package/dist/runtime/node-shims.js +326 -7
  83. package/dist/runtime/opencode-artifact.js +1 -1
  84. package/dist/runtime/opentui-wasm-bytes.js +1 -1
  85. package/dist/runtime/python-repl.js +3 -3
  86. package/dist/runtime/ruby-repl.js +2 -2
  87. package/dist/runtime/sqlite-wasm-bytes.js +1 -1
  88. package/dist/session/agent.d.ts +11 -0
  89. package/dist/session/agent.d.ts.map +1 -1
  90. package/dist/session/diag.js +1 -1
  91. package/dist/session/helpers.js +1 -1
  92. package/dist/session/hibernation.d.ts +8 -8
  93. package/dist/session/hibernation.d.ts.map +1 -1
  94. package/dist/session/hibernation.js +15 -15
  95. package/dist/session/init-phases.d.ts +19 -26
  96. package/dist/session/init-phases.d.ts.map +1 -1
  97. package/dist/session/init-phases.js +20 -43
  98. package/dist/session/init.d.ts.map +1 -1
  99. package/dist/session/init.js +24 -19
  100. package/dist/session/keys.d.ts +8 -0
  101. package/dist/session/keys.d.ts.map +1 -1
  102. package/dist/session/keys.js +12 -3
  103. package/dist/session/nimbus-session.d.ts +33 -19
  104. package/dist/session/nimbus-session.d.ts.map +1 -1
  105. package/dist/session/nimbus-session.js +78 -37
  106. package/dist/session/port-capability.d.ts +95 -8
  107. package/dist/session/port-capability.d.ts.map +1 -1
  108. package/dist/session/port-capability.js +165 -8
  109. package/dist/session/programmatic.d.ts +50 -4
  110. package/dist/session/programmatic.d.ts.map +1 -1
  111. package/dist/session/programmatic.js +112 -15
  112. package/dist/session/routes.d.ts +17 -0
  113. package/dist/session/routes.d.ts.map +1 -1
  114. package/dist/session/routes.js +111 -43
  115. package/dist/session/rpc.d.ts +7 -7
  116. package/dist/session/rpc.d.ts.map +1 -1
  117. package/dist/session/rpc.js +34 -23
  118. package/dist/session/shell-socket.d.ts +76 -0
  119. package/dist/session/shell-socket.d.ts.map +1 -0
  120. package/dist/session/shell-socket.js +172 -0
  121. package/dist/session/start-real-vite.js +2 -2
  122. package/dist/session/supervisor-op.d.ts +5 -0
  123. package/dist/session/supervisor-op.d.ts.map +1 -0
  124. package/dist/session/supervisor-op.js +74 -0
  125. package/dist/session/supervisor-rpc.d.ts +6 -10
  126. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  127. package/dist/session/supervisor-rpc.js +98 -79
  128. package/dist/session/ws.d.ts +1 -3
  129. package/dist/session/ws.d.ts.map +1 -1
  130. package/dist/session/ws.js +23 -7
  131. package/package.json +4 -3
  132. package/public/_assets/runtime/{node-shims-fea906843f6d594e.js → node-shims-9e1380d9ee430fcc.js} +320 -5
  133. package/public/s/index.html +101 -5
  134. package/scripts/bundle-esbuild-wasm.mjs +2 -2
  135. package/scripts/bundle-facet-workers.mjs +5 -7
  136. package/scripts/bundle-sqlite-wasm.mjs +1 -1
@@ -29,15 +29,15 @@
29
29
  * prefetch(cwd, entryCode) → Record<string, string>
30
30
  */
31
31
  import { WorkerEntrypoint } from 'cloudflare:workers';
32
- import { setSupervisorEntrypointName } from '@nimbus-sh/fabric/ctx-exports.js';
32
+ import { hostNamespace, hostDispatchMethod } from '@nimbus-sh/platform/composition.js';
33
33
  // W5: OOM discriminator — record last-known RPC frame on writeBatch entry
34
- import { setLastRpcFrame } from '@nimbus-sh/core/observability/oom-discriminator.js';
34
+ import { setLastRpcFrame } from '@nimbus-sh/platform/oom-discriminator.js';
35
35
  // Phase 2 A'.2 — supervisor in-flight RPC payload byte tracking.
36
- import { rpcPayloadStart, rpcPayloadEnd } from '@nimbus-sh/core/observability/diag-counters.js';
36
+ import { rpcPayloadStart, rpcPayloadEnd } from '@nimbus-sh/platform/diag-counters.js';
37
37
  // W4: R2 cross-tenant npm cache (tarballs + packuments)
38
38
  import { R2CacheClient, MAX_R2_TARBALL_BYTES } from '../npm/r2-cache.js';
39
- import { useRpcResource } from '@nimbus-sh/core/_shared/rpc-dispose.js';
40
- import { W7_MAX_RECORD_BYTES } from '@nimbus-sh/core/_shared/w7-frame.js';
39
+ import { useRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
40
+ import { W7_MAX_RECORD_BYTES } from '@nimbus-sh/platform/w7-frame.js';
41
41
  /**
42
42
  * Drain the per-call event list captured by R2CacheClient during this
43
43
  * SupervisorRPC call.
@@ -75,26 +75,45 @@ function _estimateWriteBatchBytes(payload) {
75
75
  n += 80 + (i?.path?.length ?? 0);
76
76
  return n;
77
77
  }
78
- // The fabric mints `env.SUPERVISOR` bindings for the programs it hosts;
79
- // this names the ctx.exports entrypoint those bindings resolve to. Module
80
- // scope so every isolate that can reach the fabric has it before first use.
81
- setSupervisorEntrypointName('SupervisorRPC');
78
+ // The fabric mints `env.SUPERVISOR` bindings for the programs it hosts; the
79
+ // worker's composition root (src/index.ts) names this class to the fabric
80
+ // with composeFabric.
82
81
  export class SupervisorRPC extends WorkerEntrypoint {
83
- /**
84
- * The supervisor DO stub for RPC routing, found from the doId in ctx.props.
85
- *
86
- * Minted per call, because that is the only lifetime an instance of this
87
- * class has: workerd constructs a NEW WorkerEntrypoint for every RPC call,
88
- * not one per facet invocation, so a field cached here is written and
89
- * discarded by the same call and never read by another (measured: two calls
90
- * on one stub produced instances #3 and #4, each having served one call).
91
- */
92
- _getStub() {
82
+ /** Resolve the composed host anew for each WorkerEntrypoint invocation. */
83
+ _dispatch() {
93
84
  const doId = this.ctx.props?.doId;
94
- if (!doId)
85
+ if (typeof doId !== 'string' || doId.length === 0) {
95
86
  throw new Error('SupervisorRPC: missing doId in props');
96
- const id = this.env.NIMBUS_SESSION.idFromString(doId);
97
- return this.env.NIMBUS_SESSION.get(id);
87
+ }
88
+ const binding = hostNamespace();
89
+ const namespace = Reflect.get(this.env, binding);
90
+ if (!namespace || typeof namespace.idFromString !== 'function') {
91
+ throw new Error(`SupervisorRPC: env.${binding} is not a Durable Object namespace; `
92
+ + "a workspace host names its own with composeFabric({ hostNamespace: '<binding>' })");
93
+ }
94
+ const stub = namespace.get(namespace.idFromString(doId));
95
+ const method = hostDispatchMethod();
96
+ const dispatch = Reflect.get(stub, method);
97
+ if (typeof dispatch !== 'function') {
98
+ throw new Error(`SupervisorRPC: the workspace host mounts no ${method}(); `
99
+ + 'a host forwards one method to workspace.supervisorOp(op)');
100
+ }
101
+ // Never `.call(stub, …)` on an RpcStub: the proxy treats `call` as a
102
+ // property get, which becomes an RPC to a method literally named "call".
103
+ // `Reflect.apply` reaches the proxied member's apply trap instead, and on
104
+ // a plain-object host it still invokes the method with the stub as `this`.
105
+ return (envelope) => Reflect.apply(dispatch, stub, [envelope]);
106
+ }
107
+ _op(op, args = [], extra = {}) {
108
+ return this._dispatch()({ op, args, ...extra });
109
+ }
110
+ /** Stamp filesystem credentials from the binding, not the supplied arguments. */
111
+ _fsOp(op, args = []) {
112
+ return this._op(op, args, { pid: this._pid() });
113
+ }
114
+ _reportingPid() {
115
+ const pid = this.ctx.props?.pid;
116
+ return typeof pid === 'number' && Number.isInteger(pid) && pid > 0 ? pid : 0;
98
117
  }
99
118
  _call(promise) {
100
119
  return useRpcResource(promise, (value) => value);
@@ -115,14 +134,14 @@ export class SupervisorRPC extends WorkerEntrypoint {
115
134
  }
116
135
  // ── Filesystem RPC ────────────────────────────────────────────────────
117
136
  async readFile(path) {
118
- return this._call(this._getStub()._rpcReadFile(path, this._pid()));
137
+ return this._call(this._fsOp('readFile', [path]));
119
138
  }
120
139
  /**
121
140
  * Read a file as raw bytes. Used by the git network facet for binary
122
141
  * object/pack files where the text readFile would corrupt content.
123
142
  */
124
143
  async readFileBytes(path) {
125
- return this._call(this._getStub()._rpcReadFileBytes(path, this._pid()));
144
+ return this._call(this._fsOp('readFileBytes', [path]));
126
145
  }
127
146
  async writeFile(path, content) {
128
147
  // binary-fs wave: accept Uint8Array natively. Pre-fix this RPC was
@@ -130,55 +149,55 @@ export class SupervisorRPC extends WorkerEntrypoint {
130
149
  // decode every Uint8Array write — mangling bytes ≥ 0x80 to U+FFFD
131
150
  // and corrupting binary content. RPC structured-clone handles
132
151
  // Uint8Array transparently; downstream _rpcWriteFile also accepts
133
- return this._call(this._getStub()._rpcWriteFile(path, content, this._pid()));
152
+ return this._call(this._fsOp('writeFile', [path, content]));
134
153
  }
135
154
  async stat(path) {
136
- return this._call(this._getStub()._rpcStat(path, this._pid()));
155
+ return this._call(this._fsOp('stat', [path]));
137
156
  }
138
157
  async lstat(path) {
139
- return this._call(this._getStub()._rpcLstat(path, this._pid()));
158
+ return this._call(this._fsOp('lstat', [path]));
140
159
  }
141
160
  async hasLegacySymlinkUnder(path) {
142
- return this._call(this._getStub()._rpcHasLegacySymlinkUnder(path, this._pid()));
161
+ return this._call(this._fsOp('hasLegacySymlinkUnder', [path]));
143
162
  }
144
163
  async utimes(path, atimeMs, mtimeMs) {
145
- return this._call(this._getStub()._rpcUtimes(path, atimeMs, mtimeMs, this._pid()));
164
+ return this._call(this._fsOp('utimes', [path, atimeMs, mtimeMs]));
146
165
  }
147
166
  async chmod(path, mode) {
148
- return this._call(this._getStub()._rpcChmod(path, mode, this._pid()));
167
+ return this._call(this._fsOp('chmod', [path, mode]));
149
168
  }
150
169
  async access(path, mode) {
151
- return this._call(this._getStub()._rpcAccess(path, mode, this._pid()));
170
+ return this._call(this._fsOp('access', [path, mode]));
152
171
  }
153
172
  async chown(path, uid, gid, options) {
154
- return this._call(this._getStub()._rpcChown(path, uid, gid, this._pid(), options));
173
+ return this._call(this._fsOp('chown', [path, uid, gid, options]));
155
174
  }
156
175
  async setUmask(mask) {
157
- return this._call(this._getStub()._rpcSetUmask(mask, this._pid()));
176
+ return this._call(this._fsOp('setUmask', [mask]));
158
177
  }
159
178
  async readdir(path) {
160
- return this._call(this._getStub()._rpcReaddir(path, this._pid()));
179
+ return this._call(this._fsOp('readdir', [path]));
161
180
  }
162
181
  async exists(path) {
163
- return this._call(this._getStub()._rpcExists(path, this._pid()));
182
+ return this._call(this._fsOp('exists', [path]));
164
183
  }
165
184
  async mkdir(path) {
166
- return this._call(this._getStub()._rpcMkdir(path, this._pid()));
185
+ return this._call(this._fsOp('mkdir', [path]));
167
186
  }
168
187
  async rmdir(path) {
169
- return this._call(this._getStub()._rpcRmdir(path, this._pid()));
188
+ return this._call(this._fsOp('rmdir', [path]));
170
189
  }
171
190
  async rename(from, to) {
172
- return this._call(this._getStub()._rpcRename(from, to, this._pid()));
191
+ return this._call(this._fsOp('rename', [from, to]));
173
192
  }
174
193
  async unlink(path) {
175
- return this._call(this._getStub()._rpcUnlink(path, this._pid()));
194
+ return this._call(this._fsOp('unlink', [path]));
176
195
  }
177
196
  async readlink(path) {
178
- return this._call(this._getStub()._rpcReadlink(path, this._pid()));
197
+ return this._call(this._fsOp('readlink', [path]));
179
198
  }
180
199
  async symlink(target, path) {
181
- return this._call(this._getStub()._rpcSymlink(target, path, this._pid()));
200
+ return this._call(this._fsOp('symlink', [target, path]));
182
201
  }
183
202
  /**
184
203
  * ACQUIRE: the paths mutated since the facet's cursor, plus a fresh
@@ -192,10 +211,10 @@ export class SupervisorRPC extends WorkerEntrypoint {
192
211
  * disposal, which targets the returned value.
193
212
  */
194
213
  async fsAcquire(epoch, cursor) {
195
- return this._call(this._getStub()._rpcFsAcquire(epoch, cursor, this._pid()));
214
+ return this._call(this._fsOp('fsAcquire', [epoch, cursor]));
196
215
  }
197
216
  async fsRevision(path) {
198
- return this._call(this._getStub()._rpcFsRevision(path, this._pid()));
217
+ return this._call(this._fsOp('fsRevision', [path]));
199
218
  }
200
219
  /**
201
220
  * Enumerate the session filesystem, one bounded page at a time.
@@ -215,7 +234,7 @@ export class SupervisorRPC extends WorkerEntrypoint {
215
234
  * never mistaken for a complete listing.
216
235
  */
217
236
  async fsList(after, limit) {
218
- return this._call(this._getStub()._rpcFsList(after ?? null, limit ?? null, this._pid()));
237
+ return this._call(this._fsOp('fsList', [after ?? null, limit ?? null]));
219
238
  }
220
239
  /**
221
240
  * WebSocket relay. A facet does not open its own sockets: the supervisor
@@ -226,28 +245,28 @@ export class SupervisorRPC extends WorkerEntrypoint {
226
245
  * facet's next synchronous read serves bytes the authority has replaced.
227
246
  */
228
247
  async wsOpen(url, protocols) {
229
- return this._call(this._getStub()._rpcWsOpen(url, protocols, this._pid()));
248
+ return this._call(this._fsOp('wsOpen', [url, protocols]));
230
249
  }
231
250
  async wsPoll(id, waitMs) {
232
- return this._call(this._getStub()._rpcWsPoll(id, waitMs, this._pid()));
251
+ return this._call(this._fsOp('wsPoll', [id, waitMs]));
233
252
  }
234
253
  async wsSend(id, text, bytes) {
235
- return this._call(this._getStub()._rpcWsSend(id, text, bytes, this._pid()));
254
+ return this._call(this._fsOp('wsSend', [id, text, bytes]));
236
255
  }
237
256
  async wsClose(id, code, reason) {
238
- return this._call(this._getStub()._rpcWsClose(id, code, reason, this._pid()));
257
+ return this._call(this._fsOp('wsClose', [id, code, reason]));
239
258
  }
240
259
  async fsOpen(path, flags) {
241
- return this._call(this._getStub()._rpcFsOpen(path, flags, this._pid()));
260
+ return this._call(this._fsOp('fsOpen', [path, flags]));
242
261
  }
243
262
  async fsRead(handleId, offset, length) {
244
- return this._call(this._getStub()._rpcFsRead(handleId, offset, length, this._pid()));
263
+ return this._call(this._fsOp('fsRead', [handleId, offset, length]));
245
264
  }
246
265
  async fsWrite(handleId, offset, bytes) {
247
- return this._call(this._getStub()._rpcFsWrite(handleId, offset, bytes, this._pid()));
266
+ return this._call(this._fsOp('fsWrite', [handleId, offset, bytes]));
248
267
  }
249
268
  async fsClose(handleId) {
250
- return this._call(this._getStub()._rpcFsClose(handleId, this._pid()));
269
+ return this._call(this._fsOp('fsClose', [handleId]));
251
270
  }
252
271
  /**
253
272
  * Stateless ranged ops. Unlike fsOpen/fsRead/fsWrite they carry no
@@ -255,7 +274,7 @@ export class SupervisorRPC extends WorkerEntrypoint {
255
274
  * hibernation and never rewrite whole files for partial updates.
256
275
  */
257
276
  async fsReadRange(path, offset, length) {
258
- return this._call(this._getStub()._rpcFsReadRange(path, offset, length, this._pid()));
277
+ return this._call(this._fsOp('fsReadRange', [path, offset, length]));
259
278
  }
260
279
  /**
261
280
  * The same read with the session's content cache bypassed, for a boot spec's
@@ -264,7 +283,7 @@ export class SupervisorRPC extends WorkerEntrypoint {
264
283
  * session's heap for the rest of its life.
265
284
  */
266
285
  async fsReadRangeUncached(path, offset, length) {
267
- return this._call(this._getStub()._rpcFsReadRangeUncached(path, offset, length, this._pid()));
286
+ return this._call(this._fsOp('fsReadRangeUncached', [path, offset, length]));
268
287
  }
269
288
  /**
270
289
  * Read many ranges in ONE round trip — the read-side counterpart to
@@ -288,23 +307,23 @@ export class SupervisorRPC extends WorkerEntrypoint {
288
307
  setLastRpcFrame('fsReadBatch', payloadBytes);
289
308
  rpcPayloadStart(payloadBytes);
290
309
  try {
291
- return await this._call(this._getStub()._rpcFsReadBatch(requests, this._pid()));
310
+ return await this._call(this._fsOp('fsReadBatch', [requests]));
292
311
  }
293
312
  finally {
294
313
  rpcPayloadEnd(payloadBytes);
295
314
  }
296
315
  }
297
316
  async fsWriteRange(path, offset, bytes) {
298
- return this._call(this._getStub()._rpcFsWriteRange(path, offset, bytes, this._pid()));
317
+ return this._call(this._fsOp('fsWriteRange', [path, offset, bytes]));
299
318
  }
300
319
  async fsAppend(path, moduleId, operationId, bytes) {
301
- return this._call(this._getStub()._rpcFsAppend(path, this._writerId(), moduleId, operationId, bytes, this._pid()));
320
+ return this._call(this._op('fsAppend', [path, moduleId, operationId, bytes], { pid: this._pid(), writerId: this._writerId() }));
302
321
  }
303
322
  async fsAppendAck(moduleId, operationId) {
304
- return this._call(this._getStub()._rpcFsAppendAck(this._writerId(), moduleId, operationId, this._pid()));
323
+ return this._call(this._fsOp('fsAppendAck', [this._writerId(), moduleId, operationId]));
305
324
  }
306
325
  async fsTruncate(path, size) {
307
- return this._call(this._getStub()._rpcFsTruncate(path, size, this._pid()));
326
+ return this._call(this._fsOp('fsTruncate', [path, size]));
308
327
  }
309
328
  /**
310
329
  * Bulk-write all inodes + chunks in ONE transactionSync on the supervisor.
@@ -327,7 +346,7 @@ export class SupervisorRPC extends WorkerEntrypoint {
327
346
  setLastRpcFrame('writeBatch', payloadBytes);
328
347
  rpcPayloadStart(payloadBytes);
329
348
  try {
330
- return await this._call(this._getStub()._rpcWriteBatch(payload, this._pid()));
349
+ return await this._call(this._fsOp('writeBatch', [payload]));
331
350
  }
332
351
  finally {
333
352
  rpcPayloadEnd(payloadBytes);
@@ -358,7 +377,7 @@ export class SupervisorRPC extends WorkerEntrypoint {
358
377
  rpcPayloadStart(STREAM_RESIDENT_BYTES);
359
378
  try {
360
379
  const mutationOwner = this.ctx.props?.mutationOwner;
361
- return await this._call(this._getStub()._rpcWriteBatchStream(stream, typeof mutationOwner === 'string' ? mutationOwner : undefined, this._pid()));
380
+ return await this._call(this._op('writeBatchStream', [], { pid: this._pid(), mutationOwner: typeof mutationOwner === 'string' ? mutationOwner : undefined, stream }));
362
381
  }
363
382
  finally {
364
383
  rpcPayloadEnd(STREAM_RESIDENT_BYTES);
@@ -386,7 +405,7 @@ export class SupervisorRPC extends WorkerEntrypoint {
386
405
  const payloadBytes = (Array.isArray(entries) ? entries.length : 0) * REGISTRY_ENTRY_BYTES;
387
406
  rpcPayloadStart(payloadBytes);
388
407
  try {
389
- return await this._call(this._getStub()._rpcPutRegistryEntries(entries));
408
+ return await this._call(this._op('putRegistryEntries', [entries]));
390
409
  }
391
410
  finally {
392
411
  rpcPayloadEnd(payloadBytes);
@@ -479,10 +498,10 @@ export class SupervisorRPC extends WorkerEntrypoint {
479
498
  }
480
499
  // ── Process I/O ───────────────────────────────────────────────────────
481
500
  async stdout(data) {
482
- return this._call(this._getStub()._rpcStdout(this.ctx.props?.pid || 0, data));
501
+ return this._call(this._op('stdout', [data], { pid: this._reportingPid() }));
483
502
  }
484
503
  async stderr(data) {
485
- return this._call(this._getStub()._rpcStderr(this.ctx.props?.pid || 0, data));
504
+ return this._call(this._op('stderr', [data], { pid: this._reportingPid() }));
486
505
  }
487
506
  /**
488
507
  * Report process exit to the supervisor. Called from the facet's own
@@ -495,18 +514,18 @@ export class SupervisorRPC extends WorkerEntrypoint {
495
514
  */
496
515
  async reportExit(code, tail) {
497
516
  const pid = this.ctx.props?.pid || 0;
498
- return this._call(this._getStub()._rpcReportExit(pid, code, tail || ''));
517
+ return this._call(this._op('reportExit', [code, tail || ''], { pid }));
499
518
  }
500
519
  // ── Prefetch ──────────────────────────────────────────────────────────
501
520
  async prefetch(cwd, entryCode) {
502
- return this._call(this._getStub()._rpcPrefetch(cwd, entryCode));
521
+ return this._call(this._op('prefetch', [cwd, entryCode]));
503
522
  }
504
523
  // ── Port registration ─────────────────────────────────────────────────
505
524
  async registerPort(port) {
506
- return this._call(this._getStub()._rpcRegisterPort(this.ctx.props?.pid || 0, port));
525
+ return this._call(this._op('registerPort', [port], { pid: this._reportingPid() }));
507
526
  }
508
527
  async unregisterPort(port) {
509
- return this._call(this._getStub()._rpcUnregisterPort(port));
528
+ return this._call(this._op('unregisterPort', [port]));
510
529
  }
511
530
  /**
512
531
  * Route an in-session loopback HTTP request (a facet's fetch to
@@ -521,11 +540,11 @@ export class SupervisorRPC extends WorkerEntrypoint {
521
540
  * exactly how PortRegistry.routeRequest returns the facet's Response as-is.
522
541
  */
523
542
  async routeLoopback(port, request) {
524
- return this._getStub()._rpcRouteLoopback(port, request);
543
+ return this._op('routeLoopback', [port, request]);
525
544
  }
526
545
  // ── Esbuild transform ─────────────────────────────────────────────────
527
546
  async transform(code, loader) {
528
- return this._call(this._getStub()._rpcTransform(code, loader));
547
+ return this._call(this._op('transform', [code, loader]));
529
548
  }
530
549
  // ── child_process [W8 Phase 1] ────────────────────────────────────────
531
550
  //
@@ -534,28 +553,28 @@ export class SupervisorRPC extends WorkerEntrypoint {
534
553
  // route through the shared FacetProcessManager.
535
554
  //
536
555
  async cpSpawn(req) {
537
- return this._call(this._getStub()._rpcCpSpawn({ ...req, parentPid: this._pid() }));
556
+ return this._call(this._op('cpSpawn', [{ ...req, parentPid: this._pid() }]));
538
557
  }
539
558
  async cpStdinWrite(childPid, data) {
540
- return this._call(this._getStub()._rpcCpStdinWrite(childPid, data));
559
+ return this._call(this._op('cpStdinWrite', [childPid, data]));
541
560
  }
542
561
  async cpStdinEnd(childPid) {
543
- return this._call(this._getStub()._rpcCpStdinEnd(childPid));
562
+ return this._call(this._op('cpStdinEnd', [childPid]));
544
563
  }
545
564
  async cpReadStdin(childPid, waitMs) {
546
- return this._call(this._getStub()._rpcCpReadStdin(childPid, waitMs));
565
+ return this._call(this._op('cpReadStdin', [childPid, waitMs]));
547
566
  }
548
567
  async cpReadOutput(childPid, fd, sinceSeq, waitMs) {
549
- return this._call(this._getStub()._rpcCpReadOutput(childPid, fd, sinceSeq, waitMs));
568
+ return this._call(this._op('cpReadOutput', [childPid, fd, sinceSeq, waitMs]));
550
569
  }
551
570
  async cpDrainOutput(childPid) {
552
- return this._call(this._getStub()._rpcCpDrainOutput(childPid));
571
+ return this._call(this._op('cpDrainOutput', [childPid]));
553
572
  }
554
573
  async cpKill(childPid, signal) {
555
- return this._call(this._getStub()._rpcCpKill(childPid, signal));
574
+ return this._call(this._op('cpKill', [childPid, signal]));
556
575
  }
557
576
  async cpWait(childPid, waitMs) {
558
- return this._call(this._getStub()._rpcCpWait(childPid, waitMs));
577
+ return this._call(this._op('cpWait', [childPid, waitMs]));
559
578
  }
560
579
  /**
561
580
  * child-process isolation gap #1: dispatch a single cp.spawn request inline using
@@ -566,6 +585,6 @@ export class SupervisorRPC extends WorkerEntrypoint {
566
585
  * supervisor while keeping the dispatch envelope in a fresh isolate.
567
586
  */
568
587
  async cpDispatchInline(req, kind) {
569
- return this._call(this._getStub()._rpcCpDispatchInline(req, kind));
588
+ return this._call(this._op('cpDispatchInline', [req, kind]));
570
589
  }
571
590
  }
@@ -60,15 +60,13 @@ export interface WsHost {
60
60
  _w9PersistWired: boolean;
61
61
  _w9FlushTimer: any;
62
62
  _w9SchemaInit: boolean;
63
- _isolateGen: number;
64
- _isolateGenPersisted: boolean;
65
63
  _w9WsConfig: any;
66
64
  _diagPeakRss: number;
67
65
  _diagPeakHeapUsed: number;
68
66
  _w5LastPersistAt: number;
69
67
  _w5LastPersistRingSize: number;
70
68
  /** [B'.4] live phase indicator — see nimbus-session-internal.d.ts */
71
- _b4Phase: import('@nimbus-sh/core/observability/oom-discriminator.js').SessionState | null;
69
+ _b4Phase: import('@nimbus-sh/platform/oom-discriminator.js').SessionState | null;
72
70
  _w5PersistRing(): Promise<void> | null;
73
71
  _w9FlushOnClose(): void;
74
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/session/ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,eAAe,CAAC;AAIvB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uDAAuD,CAAC;AACtG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,yCAAyC,CAAC;AAG7E;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,mBAAmB,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACnD;;;;8DAI0D;IAC1D,YAAY,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5C,SAAS,EAAE,wBAAwB,CAAC;IACpC,wBAAwB,EAAE,OAAO,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,GAAG,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,WAAW,EAAE,GAAG,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,EAAE,OAAO,oDAAoD,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3F,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvC,eAAe,IAAI,IAAI,CAAC;CACzB;AAgDD;;;;;;;GAOG;AACH,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA0CD,wBAAgB,MAAM,CAAC,EAAE,EAAE,SAAS,GAAG,YAAY,CAElD;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEzG;AA+CD,wBAAsB,OAAO,CAC3B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,EACb,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,IAAI,CAAC,CA4Ff;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEtF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAUlD"}
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/session/ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAOH,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,eAAe,CAAC;AAIvB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uDAAuD,CAAC;AACtG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,yCAAyC,CAAC;AAG7E;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,mBAAmB,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACnD;;;;8DAI0D;IAC1D,YAAY,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5C,SAAS,EAAE,wBAAwB,CAAC;IACpC,wBAAwB,EAAE,OAAO,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,GAAG,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,GAAG,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,EAAE,OAAO,0CAA0C,EAAE,YAAY,GAAG,IAAI,CAAC;IACjF,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvC,eAAe,IAAI,IAAI,CAAC;CACzB;AAgDD;;;;;;;GAOG;AACH,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA0CD,wBAAgB,MAAM,CAAC,EAAE,EAAE,SAAS,GAAG,YAAY,CAElD;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA4EzG;AA+CD,wBAAsB,OAAO,CAC3B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,EACb,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,IAAI,CAAC,CAiGf;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CA4EtF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAUlD"}
@@ -33,12 +33,14 @@
33
33
  * touches ctx.waitUntil; uses `(host.ctx as any)` cast).
34
34
  */
35
35
  import { dec } from '@nimbus-sh/core/_shared/bytes.js';
36
- import { recordFailure, getLastRpcFrame, getLastFacetId, recordRecoveryEvent } from '@nimbus-sh/core/observability/oom-discriminator.js';
36
+ import { recordFailure, getLastRpcFrame, getLastFacetId, recordRecoveryEvent } from '@nimbus-sh/platform/oom-discriminator.js';
37
+ import { generation } from '@nimbus-sh/fabric/generation.js';
37
38
  import { persistShellState } from './state-store.js';
38
39
  import { handleFsWatchSubscribe, handleFsWatchUnsubscribe, cleanupFsWatchOnClose, } from './fs-watch.js';
39
40
  import { parseProcessLogClientFrame } from '@nimbus-sh/core/runtime/process-io-protocol.js';
40
41
  import { applyProcessClientFrame } from '@nimbus-sh/core/runtime/process-input-routing.js';
41
42
  import { z } from 'zod/v4';
43
+ import { clearShellSocketStamp, noteShellSocketActivity } from './shell-socket.js';
42
44
  import { PRIOR_GENERATION_EXIT_REASON } from './rpc.js';
43
45
  /**
44
46
  * Snapshot the live Shell state and write it through to DO SQLite
@@ -150,6 +152,12 @@ export async function wsMessage(self, ws, message) {
150
152
  await routeProcessLogClientMessage(self, ws, attach, message);
151
153
  return;
152
154
  }
155
+ // A frame arriving is the peer proving it is still on this socket.
156
+ // The stamp goes in the attachment because isolate memory does not
157
+ // survive hibernation and the /ws upgrade has to read it afterwards.
158
+ // Rate-limited inside; see src/session/shell-socket.ts.
159
+ if (attach?.kind === 'shell')
160
+ noteShellSocketActivity(ws);
153
161
  const data = typeof message === 'string' ? message : dec.decode(message);
154
162
  const value = JSON.parse(data);
155
163
  // file-tree-watch (2026-05-15): handle fs-watch-* on this WS BEFORE
@@ -303,6 +311,11 @@ export async function wsClose(self, ws, _code, _reason, _wasClean) {
303
311
  // wrangler dev) + long-running facets must still survive the
304
312
  // terminal reconnect (see 607e472 — do NOT kill running processes
305
313
  // here). Only reap per-tab state.
314
+ // The runtime says this socket is finished, so it no longer holds the
315
+ // terminal. Drop its liveness stamp before anything else: the frame
316
+ // that triggered this close stamped it moments ago, and leaving that
317
+ // behind would make the reconnect look like a second tab.
318
+ clearShellSocketStamp(ws);
306
319
  // ── Phase 3 B'.1: transitionTo('drained') ──────────────────────────
307
320
  // The Track B' state-machine transition. Persist final shell
308
321
  // state + record a recovery_event BEFORE we null the in-memory
@@ -320,7 +333,7 @@ export async function wsClose(self, ws, _code, _reason, _wasClean) {
320
333
  fromState: 'active',
321
334
  toState: 'drained',
322
335
  trigger: 'ws-close',
323
- isolateGen: self._isolateGen,
336
+ isolateGen: generation(self.ctx),
324
337
  dataLoss: false,
325
338
  snapshotKeysRehydrated: 0,
326
339
  });
@@ -351,10 +364,9 @@ export async function wsClose(self, ws, _code, _reason, _wasClean) {
351
364
  // upgrade calls terminal.attach(newWs) to swap in the new socket.
352
365
  //
353
366
  // Pre-B'.5 we nulled these three fields to avoid two-tab cross-
354
- // wiring (the 409 in nimbus-session-routes.ts:97 protected against
355
- // overwriting an active shell). With phase=drained surfaced on the
356
- // host, the /ws handler can disambiguate "warm session waiting for
357
- // rejoin" (warmJoin path) from "active session busy" (still 409).
367
+ // wiring, which cost every reconnect a full rebuild. The upgrade now
368
+ // reads the sockets instead: a shell socket a peer still holds is the
369
+ // busy session it refuses, and this one is closing.
358
370
  // Reset the one-shot "wrangler alias" banner so a reconnecting user
359
371
  // sees it again — terminal-lifetime state, not session-lifetime.
360
372
  self.wranglerAliasBannerShown = false;
@@ -386,6 +398,10 @@ export async function wsError(self, ws, _error) {
386
398
  catch { /* best-effort */ }
387
399
  return;
388
400
  }
401
+ // Same reasoning as wsClose: this socket stops holding the terminal
402
+ // here. It matters more on this path, because the handler that just
403
+ // blew the 5-s cap is the one that stamped the socket.
404
+ clearShellSocketStamp(ws);
389
405
  // ── Phase 3 B'.1: transitionTo('drained') ──────────────────────────
390
406
  // Same architectural step as wsClose: persist shell state + record
391
407
  // a drained event before nulling. wsError is a different physical
@@ -400,7 +416,7 @@ export async function wsError(self, ws, _error) {
400
416
  fromState: 'active',
401
417
  toState: 'drained',
402
418
  trigger: 'ws-error',
403
- isolateGen: self._isolateGen,
419
+ isolateGen: generation(self.ctx),
404
420
  dataLoss: false,
405
421
  snapshotKeysRehydrated: 0,
406
422
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-sh/worker",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Nimbus runtime package for Cloudflare Workers, re-exported publicly through @nimbus-sh/sdk/worker.",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -84,8 +84,9 @@
84
84
  },
85
85
  "dependencies": {
86
86
  "@ai-sdk/openai-compatible": "^2.0.41",
87
- "@nimbus-sh/core": "^0.5.0",
88
- "@nimbus-sh/fabric": "^0.1.0",
87
+ "@nimbus-sh/core": "^0.7.0",
88
+ "@nimbus-sh/fabric": "^0.3.0",
89
+ "@nimbus-sh/platform": "^0.2.0",
89
90
  "@renovatebot/pep440": "^5.0.0",
90
91
  "ai": "^6.0.182",
91
92
  "clean-git-ref": "^2.0.1",