@nimbus-sh/worker 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -43
- package/dist/_shared/preview-host.d.ts +33 -2
- package/dist/_shared/preview-host.d.ts.map +1 -1
- package/dist/_shared/preview-host.js +73 -8
- package/dist/_shared/session-router.d.ts +43 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +59 -12
- package/dist/facets/durable-images.d.ts +59 -0
- package/dist/facets/durable-images.d.ts.map +1 -0
- package/dist/facets/durable-images.js +94 -0
- package/dist/facets/durable-slots.d.ts +45 -0
- package/dist/facets/durable-slots.d.ts.map +1 -0
- package/dist/facets/durable-slots.js +95 -0
- package/dist/facets/esbuild-transform.d.ts +10 -0
- package/dist/facets/esbuild-transform.d.ts.map +1 -0
- package/dist/facets/esbuild-transform.js +36 -0
- package/dist/facets/manager.d.ts +244 -6
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +901 -117
- package/dist/facets/resident-identity.d.ts +23 -0
- package/dist/facets/resident-identity.d.ts.map +1 -0
- package/dist/facets/resident-identity.js +31 -0
- package/dist/git/commands.d.ts +22 -1
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +429 -425
- package/dist/git-bundle.generated.d.ts +2 -2
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +97 -3
- package/dist/router/public-directory-do.d.ts +17 -0
- package/dist/router/public-directory-do.d.ts.map +1 -0
- package/dist/router/public-directory-do.js +16 -0
- package/dist/router/public-directory.d.ts +79 -0
- package/dist/router/public-directory.d.ts.map +1 -0
- package/dist/router/public-directory.js +122 -0
- package/dist/router/remote-api.d.ts +27 -1
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +48 -2
- package/dist/runtime/cpython-resident.d.ts.map +1 -1
- package/dist/runtime/cpython-resident.js +3 -1
- package/dist/runtime/node-shims.d.ts +4 -1
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +324 -6
- package/dist/runtime/package-manager.d.ts +44 -1
- package/dist/runtime/package-manager.d.ts.map +1 -1
- package/dist/runtime/package-manager.js +168 -3
- package/dist/runtime/ruby-resident.d.ts.map +1 -1
- package/dist/runtime/ruby-resident.js +2 -0
- package/dist/session/agent.d.ts +11 -0
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/agent.js +28 -1
- package/dist/session/helpers.js +1 -1
- package/dist/session/init-phases.d.ts +17 -23
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +17 -41
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +28 -15
- package/dist/session/keys.d.ts +12 -0
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +12 -0
- package/dist/session/nimbus-session.d.ts +63 -5
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +131 -17
- package/dist/session/port-capability.d.ts +141 -8
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +265 -8
- package/dist/session/programmatic.d.ts +145 -3
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +434 -12
- package/dist/session/routes.d.ts +26 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +117 -25
- package/dist/session/rpc.d.ts +24 -1
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +65 -98
- package/dist/session/shell-socket.d.ts +76 -0
- package/dist/session/shell-socket.d.ts.map +1 -0
- package/dist/session/shell-socket.js +172 -0
- package/dist/session/start-real-vite.js +1 -1
- package/dist/session/supervisor-op.d.ts +42 -0
- package/dist/session/supervisor-op.d.ts.map +1 -0
- package/dist/session/supervisor-op.js +96 -0
- package/dist/session/supervisor-rpc.d.ts +6 -10
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +91 -71
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +19 -4
- package/package.json +4 -4
- package/public/_assets/runtime/{node-shims-fea906843f6d594e.js → node-shims-9e1380d9ee430fcc.js} +320 -5
- package/public/s/index.html +101 -5
package/dist/session/rpc.js
CHANGED
|
@@ -21,13 +21,12 @@
|
|
|
21
21
|
* these ~3 sites would each need ctx threaded through; cast at boundary
|
|
22
22
|
* is acceptable per plan §IX recommendation 1.
|
|
23
23
|
*/
|
|
24
|
-
import { enc
|
|
24
|
+
import { enc } from '@nimbus-sh/core/_shared/bytes.js';
|
|
25
25
|
import { normalizeTerminalNewlines } from '@nimbus-sh/core/_shared/terminal.js';
|
|
26
26
|
import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
27
27
|
import { getInnerDoClass } from '@nimbus-sh/fabric/inner-do-registry.js';
|
|
28
28
|
import { NpmCache } from '../npm/cache.js';
|
|
29
29
|
import { EsbuildService } from '@nimbus-sh/core/runtime/esbuild-service.js';
|
|
30
|
-
import { SqliteRuntimeFsBridge } from '@nimbus-sh/core/runtime/sqlite-runtime-fs-bridge.js';
|
|
31
30
|
import { notifyTerminalEvent } from '../runtime/process-logs-api.js';
|
|
32
31
|
import { IsolatePool } from '@nimbus-sh/fabric/isolate-pool.js';
|
|
33
32
|
import { residentBootSpecSchema, } from '@nimbus-sh/fabric/process-fabric.js';
|
|
@@ -40,7 +39,6 @@ import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
|
|
|
40
39
|
import { acquireSupervisorReadAllocation, } from '@nimbus-sh/platform/heavy-alloc-coord.js';
|
|
41
40
|
import { rpcPayloadEnd, rpcPayloadStart, } from '@nimbus-sh/platform/diag-counters.js';
|
|
42
41
|
import { CRED_KERNEL, CRED_SESSION_USER, } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
43
|
-
import { getSymlinkRegistry } from '@nimbus-sh/core/vfs/symlink-registry.js';
|
|
44
42
|
import { MAX_RPC_SAFE_PAYLOAD_BYTES } from '@nimbus-sh/platform/limits.js';
|
|
45
43
|
import { FS_LIST_PAGE_LIMIT, FS_READ_BATCH_PATH_LIMIT, FS_READ_BATCH_REQUEST_BYTES, } from '@nimbus-sh/core/constants.js';
|
|
46
44
|
import { routeSessionLoopback } from './loopback.js';
|
|
@@ -68,11 +66,6 @@ const WriteBatchPayloadSchema = z.object({
|
|
|
68
66
|
chunks: z.array(WriteBatchChunkSchema).default([]),
|
|
69
67
|
deletePaths: z.array(z.string()).optional(),
|
|
70
68
|
}).passthrough();
|
|
71
|
-
/**
|
|
72
|
-
* Bridge key for the pid-less (host caller) filesystem view. ProcessTable
|
|
73
|
-
* allocates pids from 1 upwards, so 0 can never collide with a process.
|
|
74
|
-
*/
|
|
75
|
-
const HOST_CALLER_KEY = 0;
|
|
76
69
|
function processPid(pid) {
|
|
77
70
|
if (!Number.isInteger(pid) || typeof pid !== 'number' || pid <= 0) {
|
|
78
71
|
throw new Error('filesystem RPC requires a valid process pid');
|
|
@@ -92,26 +85,7 @@ function processPid(pid) {
|
|
|
92
85
|
function callerCred(self, pid) {
|
|
93
86
|
return pid === undefined ? CRED_SESSION_USER : self.processes.cred(processPid(pid));
|
|
94
87
|
}
|
|
95
|
-
function
|
|
96
|
-
self.ensureSqliteFs();
|
|
97
|
-
return self.sqliteFs.as(callerCred(self, pid));
|
|
98
|
-
}
|
|
99
|
-
function runtimeFs(self, pid) {
|
|
100
|
-
const key = pid === undefined ? HOST_CALLER_KEY : processPid(pid);
|
|
101
|
-
const vfs = processVfs(self, pid);
|
|
102
|
-
if (!self.runtimeFsBridges)
|
|
103
|
-
self.runtimeFsBridges = new Map();
|
|
104
|
-
let bridge = self.runtimeFsBridges.get(key);
|
|
105
|
-
if (!bridge) {
|
|
106
|
-
bridge = new SqliteRuntimeFsBridge(vfs, self.sqliteFs);
|
|
107
|
-
self.runtimeFsBridges.set(key, bridge);
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
bridge.updateCredential(vfs);
|
|
111
|
-
}
|
|
112
|
-
return bridge;
|
|
113
|
-
}
|
|
114
|
-
function checkedReadPayloadBytes(bytes) {
|
|
88
|
+
export function checkedReadPayloadBytes(bytes) {
|
|
115
89
|
if (!Number.isSafeInteger(bytes) || bytes < 0) {
|
|
116
90
|
throw new RangeError(`filesystem RPC read size must be a non-negative safe integer: ${bytes}`);
|
|
117
91
|
}
|
|
@@ -129,13 +103,13 @@ function checkedReadPayloadBytes(bytes) {
|
|
|
129
103
|
* `stat` here is a local SQLite lookup inside the DO — the same one
|
|
130
104
|
* `_rpcReadFile` makes for the same reason — not a second round trip.
|
|
131
105
|
*/
|
|
132
|
-
async function rangeReadBytes(fs, path, offset, length) {
|
|
106
|
+
export async function rangeReadBytes(fs, path, offset, length) {
|
|
133
107
|
const stat = await fs.stat(path);
|
|
134
108
|
if (!stat)
|
|
135
109
|
return 0;
|
|
136
110
|
return Math.max(0, Math.min(length, stat.size - offset));
|
|
137
111
|
}
|
|
138
|
-
async function withReadAllocation(bytes, read) {
|
|
112
|
+
export async function withReadAllocation(bytes, read) {
|
|
139
113
|
const payloadBytes = checkedReadPayloadBytes(bytes);
|
|
140
114
|
if (payloadBytes === 0)
|
|
141
115
|
return read();
|
|
@@ -149,15 +123,13 @@ async function withReadAllocation(bytes, read) {
|
|
|
149
123
|
lease.release();
|
|
150
124
|
}
|
|
151
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* `files.readFile` — the read with the supervisor's allocation lease.
|
|
128
|
+
* The body lives in `buildSessionSupervisorOps`'s `readFile` override so
|
|
129
|
+
* direct `_rpc*` calls and the supervisor envelope share it.
|
|
130
|
+
*/
|
|
152
131
|
export async function _rpcReadFile(self, path, pid) {
|
|
153
|
-
|
|
154
|
-
const stat = await fs.stat(path);
|
|
155
|
-
if (!stat)
|
|
156
|
-
return null;
|
|
157
|
-
return withReadAllocation(stat.size, async () => {
|
|
158
|
-
const bytes = await fs.readFile(path);
|
|
159
|
-
return bytes ? dec.decode(bytes) : null;
|
|
160
|
-
});
|
|
132
|
+
return self.supervisorOp({ op: 'readFile', args: [path], pid });
|
|
161
133
|
}
|
|
162
134
|
/**
|
|
163
135
|
* Read a file as raw bytes (Uint8Array). Used by git network facet for
|
|
@@ -165,11 +137,7 @@ export async function _rpcReadFile(self, path, pid) {
|
|
|
165
137
|
* round-tripping through readFile (string) would corrupt bytes.
|
|
166
138
|
*/
|
|
167
139
|
export async function _rpcReadFileBytes(self, path, pid) {
|
|
168
|
-
|
|
169
|
-
const stat = await fs.stat(path);
|
|
170
|
-
if (!stat)
|
|
171
|
-
return null;
|
|
172
|
-
return withReadAllocation(stat.size, () => fs.readFile(path));
|
|
140
|
+
return self.supervisorOp({ op: 'readFileBytes', args: [path], pid });
|
|
173
141
|
}
|
|
174
142
|
/**
|
|
175
143
|
* Phase-3 inner-DO fetch dispatcher. Called by NimbusDOStub.fetch()
|
|
@@ -235,13 +203,10 @@ export async function _rpcInnerDoFetch(self, req) {
|
|
|
235
203
|
}
|
|
236
204
|
}
|
|
237
205
|
export async function _rpcWriteFile(self, path, content, pid) {
|
|
238
|
-
// binary-fs wave:
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
// a Buffer flows through node-shims.ts:writeFileSync which stores it as
|
|
243
|
-
// a plain Uint8Array on the cell — the shape that arrives here.
|
|
244
|
-
return runtimeFs(self, pid).writeFile(path, content);
|
|
206
|
+
// binary-fs wave: the bridge's writeFile already accepts
|
|
207
|
+
// string | Uint8Array — the shape arrives unchanged across structured
|
|
208
|
+
// clone.
|
|
209
|
+
return self.supervisorOp({ op: 'writeFile', args: [path, content], pid });
|
|
245
210
|
}
|
|
246
211
|
/**
|
|
247
212
|
* Write one host-governed file at a session root and let ordinary Unix
|
|
@@ -275,53 +240,52 @@ export async function _rpcWriteProtectedRootFile(self, rootPath, path, content)
|
|
|
275
240
|
fs.chmod(protectedPath, 0o444);
|
|
276
241
|
}
|
|
277
242
|
export async function _rpcStat(self, path, pid) {
|
|
278
|
-
return
|
|
243
|
+
return self.supervisorOp({ op: 'stat', args: [path], pid });
|
|
279
244
|
}
|
|
280
245
|
export async function _rpcLstat(self, path, pid) {
|
|
281
|
-
return
|
|
246
|
+
return self.supervisorOp({ op: 'lstat', args: [path], pid });
|
|
282
247
|
}
|
|
283
248
|
export async function _rpcHasLegacySymlinkUnder(self, path, pid) {
|
|
284
|
-
|
|
285
|
-
return getSymlinkRegistry(self.sqliteFs).hasAtOrBelow(path);
|
|
249
|
+
return self.supervisorOp({ op: 'hasLegacySymlinkUnder', args: [path], pid });
|
|
286
250
|
}
|
|
287
251
|
export async function _rpcUtimes(self, path, atimeMs, mtimeMs, pid) {
|
|
288
|
-
await
|
|
252
|
+
await self.supervisorOp({ op: 'utimes', args: [path, atimeMs, mtimeMs], pid });
|
|
289
253
|
}
|
|
290
254
|
export async function _rpcChmod(self, path, mode, pid) {
|
|
291
|
-
await
|
|
255
|
+
await self.supervisorOp({ op: 'chmod', args: [path, mode], pid });
|
|
292
256
|
}
|
|
293
257
|
export async function _rpcAccess(self, path, mode, pid) {
|
|
294
|
-
await
|
|
258
|
+
await self.supervisorBridge(pid).access(path, mode);
|
|
295
259
|
}
|
|
296
260
|
export async function _rpcChown(self, path, uid, gid, pid, options) {
|
|
297
|
-
await
|
|
261
|
+
await self.supervisorBridge(pid).chown(path, uid, gid, options);
|
|
298
262
|
}
|
|
299
263
|
export async function _rpcSetUmask(self, mask, pid) {
|
|
300
264
|
return self.processes.setUmask(processPid(pid), mask);
|
|
301
265
|
}
|
|
302
266
|
export async function _rpcReaddir(self, path, pid) {
|
|
303
|
-
return
|
|
267
|
+
return self.supervisorOp({ op: 'readdir', args: [path], pid });
|
|
304
268
|
}
|
|
305
269
|
export async function _rpcExists(self, path, pid) {
|
|
306
|
-
return (
|
|
270
|
+
return self.supervisorOp({ op: 'exists', args: [path], pid });
|
|
307
271
|
}
|
|
308
272
|
export async function _rpcMkdir(self, path, pid) {
|
|
309
|
-
await
|
|
273
|
+
await self.supervisorOp({ op: 'mkdir', args: [path], pid });
|
|
310
274
|
}
|
|
311
275
|
export async function _rpcRmdir(self, path, pid) {
|
|
312
|
-
await
|
|
276
|
+
await self.supervisorOp({ op: 'rmdir', args: [path], pid });
|
|
313
277
|
}
|
|
314
278
|
export async function _rpcRename(self, from, to, pid) {
|
|
315
|
-
await
|
|
279
|
+
await self.supervisorOp({ op: 'rename', args: [from, to], pid });
|
|
316
280
|
}
|
|
317
281
|
export async function _rpcReadlink(self, path, pid) {
|
|
318
|
-
return
|
|
282
|
+
return self.supervisorOp({ op: 'readlink', args: [path], pid });
|
|
319
283
|
}
|
|
320
284
|
export async function _rpcSymlink(self, target, path, pid) {
|
|
321
|
-
await
|
|
285
|
+
await self.supervisorOp({ op: 'symlink', args: [target, path], pid });
|
|
322
286
|
}
|
|
323
287
|
const FsRangeOffsetSchema = z.number().int().min(0).finite();
|
|
324
|
-
const FsReadRangeArgsSchema = z.object({
|
|
288
|
+
export const FsReadRangeArgsSchema = z.object({
|
|
325
289
|
path: z.string(),
|
|
326
290
|
offset: FsRangeOffsetSchema,
|
|
327
291
|
length: FsRangeOffsetSchema,
|
|
@@ -366,7 +330,7 @@ const FsListArgsSchema = z.object({
|
|
|
366
330
|
limit: z.number().int().min(1).max(FS_LIST_PAGE_LIMIT).nullable(),
|
|
367
331
|
});
|
|
368
332
|
export async function _rpcFsRevision(self, path, pid) {
|
|
369
|
-
return
|
|
333
|
+
return self.supervisorOp({ op: 'fsRevision', args: [path], pid });
|
|
370
334
|
}
|
|
371
335
|
/**
|
|
372
336
|
* The facet's WebSocket relay. A facet does not open its own sockets: the
|
|
@@ -416,23 +380,21 @@ export async function _rpcWsClose(self, id, code, reason, pid) {
|
|
|
416
380
|
*/
|
|
417
381
|
export async function _rpcFsAcquire(self, epoch, cursor, pid) {
|
|
418
382
|
const args = FsAcquireArgsSchema.parse({ epoch, cursor });
|
|
419
|
-
return
|
|
383
|
+
return self.supervisorBridge(pid).acquire(args.epoch, args.cursor);
|
|
420
384
|
}
|
|
421
385
|
/**
|
|
422
386
|
* Enumerate the session filesystem for a process, one bounded page at a time.
|
|
423
387
|
*
|
|
424
|
-
* Goes through `
|
|
388
|
+
* Goes through `self.supervisorBridge(pid)` like every other fs RPC, so the listing
|
|
425
389
|
* is filtered by the calling process's own credential rather than the kernel's
|
|
426
390
|
* — a process must not learn of a path it could not stat.
|
|
427
391
|
*/
|
|
428
392
|
export async function _rpcFsList(self, after, limit, pid) {
|
|
429
393
|
const args = FsListArgsSchema.parse({ after: after ?? null, limit: limit ?? null });
|
|
430
|
-
return
|
|
394
|
+
return self.supervisorBridge(pid).list(args.after, args.limit ?? undefined);
|
|
431
395
|
}
|
|
432
396
|
export async function _rpcFsReadRange(self, path, offset, length, pid) {
|
|
433
|
-
|
|
434
|
-
const fs = runtimeFs(self, pid);
|
|
435
|
-
return withReadAllocation(await rangeReadBytes(fs, args.path, args.offset, args.length), () => fs.readRange(args.path, args.offset, args.length));
|
|
397
|
+
return self.supervisorOp({ op: 'fsReadRange', args: [path, offset, length], pid });
|
|
436
398
|
}
|
|
437
399
|
/**
|
|
438
400
|
* The same read, through the same process credential and the same bridge, with
|
|
@@ -449,9 +411,7 @@ export async function _rpcFsReadRange(self, path, offset, length, pid) {
|
|
|
449
411
|
* substrate that was supposed to relieve the coordinator damages it instead.
|
|
450
412
|
*/
|
|
451
413
|
export async function _rpcFsReadRangeUncached(self, path, offset, length, pid) {
|
|
452
|
-
|
|
453
|
-
const fs = runtimeFs(self, pid);
|
|
454
|
-
return withReadAllocation(await rangeReadBytes(fs, args.path, args.offset, args.length), () => fs.readRange(args.path, args.offset, args.length, { cached: false }));
|
|
414
|
+
return self.supervisorOp({ op: 'fsReadRangeUncached', args: [path, offset, length], pid });
|
|
455
415
|
}
|
|
456
416
|
/**
|
|
457
417
|
* Read many ranges in ONE round trip.
|
|
@@ -481,7 +441,7 @@ export async function _rpcFsReadBatch(self, requests, pid) {
|
|
|
481
441
|
// Sizing pass. A path this process may not stat contributes nothing and
|
|
482
442
|
// still gets its own entry below — denying one path must not deny the
|
|
483
443
|
// batch, which is what N separate reads would have done.
|
|
484
|
-
const fs =
|
|
444
|
+
const fs = self.supervisorBridge(pid);
|
|
485
445
|
let residentBytes = 0;
|
|
486
446
|
for (const request of args) {
|
|
487
447
|
try {
|
|
@@ -520,7 +480,7 @@ function readBatchEntryError(error) {
|
|
|
520
480
|
}
|
|
521
481
|
export async function _rpcFsWriteRange(self, path, offset, bytes, pid) {
|
|
522
482
|
const args = FsWriteRangeArgsSchema.parse({ path, offset });
|
|
523
|
-
return
|
|
483
|
+
return self.supervisorBridge(pid).writeRange(args.path, args.offset, normalizeWriteBatchChunkData(bytes));
|
|
524
484
|
}
|
|
525
485
|
export async function _rpcFsAppend(self, path, writerId, moduleId, operationId, bytes, pid) {
|
|
526
486
|
const args = FsAppendArgsSchema.parse({ path, writerId, moduleId, operationId });
|
|
@@ -532,7 +492,7 @@ export async function _rpcFsAppend(self, path, writerId, moduleId, operationId,
|
|
|
532
492
|
const data = normalizeWriteBatchChunkData(bytes);
|
|
533
493
|
const digestBytes = new Uint8Array(await crypto.subtle.digest('SHA-256', data));
|
|
534
494
|
const digest = Array.from(digestBytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
535
|
-
return
|
|
495
|
+
return self.supervisorBridge(pid).appendOnce(args.path, processId, args.writerId, args.moduleId, sequence, digest, data);
|
|
536
496
|
}
|
|
537
497
|
export async function _rpcFsAppendAck(self, writerId, moduleId, operationId, pid) {
|
|
538
498
|
const args = FsAppendAckArgsSchema.parse({ writerId, moduleId, operationId });
|
|
@@ -541,17 +501,16 @@ export async function _rpcFsAppendAck(self, writerId, moduleId, operationId, pid
|
|
|
541
501
|
throw new Error('filesystem append operation exceeds the safe integer range');
|
|
542
502
|
}
|
|
543
503
|
const processId = processPid(pid);
|
|
544
|
-
await
|
|
504
|
+
await self.supervisorBridge(processId).acknowledgeAppend(processId, args.writerId, args.moduleId, sequence);
|
|
545
505
|
}
|
|
546
506
|
export async function _rpcFsTruncate(self, path, size, pid) {
|
|
547
|
-
|
|
548
|
-
await runtimeFs(self, pid).truncate(args.path, args.size);
|
|
507
|
+
await self.supervisorOp({ op: 'fsTruncate', args: [path, size], pid });
|
|
549
508
|
}
|
|
550
509
|
export async function _rpcFsOpen(self, path, flags, pid) {
|
|
551
|
-
return
|
|
510
|
+
return self.supervisorBridge(pid).open(path, flags || {});
|
|
552
511
|
}
|
|
553
512
|
export async function _rpcFsRead(self, handleId, offset, length, pid) {
|
|
554
|
-
return withReadAllocation(length, () =>
|
|
513
|
+
return withReadAllocation(length, () => self.supervisorBridge(pid).read(handleId, offset, length));
|
|
555
514
|
}
|
|
556
515
|
export async function _rpcFsWrite(self, handleId, offset, bytes, pid) {
|
|
557
516
|
let data;
|
|
@@ -561,10 +520,10 @@ export async function _rpcFsWrite(self, handleId, offset, bytes, pid) {
|
|
|
561
520
|
data = new Uint8Array(bytes);
|
|
562
521
|
else
|
|
563
522
|
data = new Uint8Array(bytes || []);
|
|
564
|
-
return
|
|
523
|
+
return self.supervisorBridge(pid).write(handleId, offset, data);
|
|
565
524
|
}
|
|
566
525
|
export async function _rpcFsClose(self, handleId, pid) {
|
|
567
|
-
await
|
|
526
|
+
await self.supervisorBridge(pid).close(handleId);
|
|
568
527
|
}
|
|
569
528
|
/**
|
|
570
529
|
* Called by CirrusHmrRPC.hmrSend. Runs in the DO's own context so
|
|
@@ -577,7 +536,7 @@ export async function _rpcHmrRelay(self, clientId, msg) {
|
|
|
577
536
|
self.cirrusReal.hmr.relayToBrowser(clientId, msg);
|
|
578
537
|
}
|
|
579
538
|
export async function _rpcUnlink(self, path, pid) {
|
|
580
|
-
await
|
|
539
|
+
await self.supervisorOp({ op: 'unlink', args: [path], pid });
|
|
581
540
|
}
|
|
582
541
|
/**
|
|
583
542
|
* Bulk-write files and directories via one transactionSync().
|
|
@@ -601,7 +560,7 @@ export async function _rpcWriteBatch(self, payload, pid) {
|
|
|
601
560
|
chunkId: c.chunkId,
|
|
602
561
|
data: normalizeWriteBatchChunkData(c.data),
|
|
603
562
|
}));
|
|
604
|
-
return
|
|
563
|
+
return self.sqliteFs.as(callerCred(self, pid)).writeBatch({
|
|
605
564
|
inodes,
|
|
606
565
|
chunks,
|
|
607
566
|
deletePaths,
|
|
@@ -660,10 +619,8 @@ export async function _rpcWriteBatchStream(self, stream, mutationOwner, pid) {
|
|
|
660
619
|
// total writeBatchStream RPCs at the coordinator (vs 620+ pre-fix).
|
|
661
620
|
// Workerd's input-gate queue depth on the coordinator stays well
|
|
662
621
|
// under the queue-age threshold without any user-space semaphore.
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
decodeDrainStartedAt,
|
|
666
|
-
mutationOwner,
|
|
622
|
+
return self.supervisorOp({
|
|
623
|
+
op: 'writeBatchStream', stream, mutationOwner, pid,
|
|
667
624
|
});
|
|
668
625
|
}
|
|
669
626
|
/**
|
|
@@ -789,7 +746,7 @@ export async function _rpcReportExit(self, pid, code, tail) {
|
|
|
789
746
|
self.webSocketRelay?.closeForPid(pid);
|
|
790
747
|
}
|
|
791
748
|
catch { }
|
|
792
|
-
self.
|
|
749
|
+
self.supervisorForgetBridge?.(pid);
|
|
793
750
|
if (tail)
|
|
794
751
|
self.processes.appendOutput(pid, 'stderr', tail);
|
|
795
752
|
// Guard against double-reporting: if we've already recorded exit
|
|
@@ -951,7 +908,7 @@ export function _reportExternalExit(self, pid, code, reason) {
|
|
|
951
908
|
self.webSocketRelay?.closeForPid(pid);
|
|
952
909
|
}
|
|
953
910
|
catch { }
|
|
954
|
-
self.
|
|
911
|
+
self.supervisorForgetBridge?.(pid);
|
|
955
912
|
if (reason) {
|
|
956
913
|
self.processes.appendOutput(pid, 'stderr', `[process killed: ${reason}]\n`);
|
|
957
914
|
}
|
|
@@ -1009,9 +966,17 @@ export async function _rpcPrefetch(self, cwd, entryCode) {
|
|
|
1009
966
|
return prefetchForRequire(self.sqliteFs, entryCode, cwd).bundle;
|
|
1010
967
|
}
|
|
1011
968
|
export async function _rpcRegisterPort(self, pid, port) {
|
|
1012
|
-
// Port registration stores the facet association
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
969
|
+
// Port registration stores the facet association — the actual facet stub
|
|
970
|
+
// is stored by FacetManager separately. Registration goes through the
|
|
971
|
+
// manager so a pid binding a reserved port inherits the reservation's
|
|
972
|
+
// durable contract: its journal row is stamped with the port's owner and
|
|
973
|
+
// the stored capability is re-adopted rather than retired.
|
|
974
|
+
if (self.facetManager) {
|
|
975
|
+
await self.facetManager.registerPort(pid, port);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
// No facet manager yet means nothing resident is running — a bare
|
|
979
|
+
// registration retires the previous occupant's preview capability.
|
|
1015
980
|
await clearPortCapability(self, port);
|
|
1016
981
|
self.portRegistry.register(port, pid);
|
|
1017
982
|
}
|
|
@@ -1027,7 +992,9 @@ export async function _rpcRouteLoopback(self, port, request) {
|
|
|
1027
992
|
export async function _rpcTransform(self, code, loader) {
|
|
1028
993
|
if (!self.esbuildService) {
|
|
1029
994
|
self.ensureSqliteFs();
|
|
1030
|
-
|
|
995
|
+
if (!self.sqliteFs)
|
|
996
|
+
throw new Error('Session VFS is not initialized');
|
|
997
|
+
self.esbuildService = new EsbuildService(self.sqliteFs.as(CRED_KERNEL));
|
|
1031
998
|
}
|
|
1032
999
|
try {
|
|
1033
1000
|
const result = await self.esbuildService.transform(code, {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/session/shell-socket.ts — who owns the session's terminal.
|
|
3
|
+
*
|
|
4
|
+
* The /ws upgrade refuses a second browser terminal on one session, so it
|
|
5
|
+
* has to know whether the terminal already has an owner. `readyState ===
|
|
6
|
+
* OPEN` does not answer that. It describes this end of the socket. A tab
|
|
7
|
+
* that dies without a close frame leaves a socket that still reads OPEN
|
|
8
|
+
* with nobody on the other end, and an upgrade that fails after accepting
|
|
9
|
+
* its socket leaves one whose client half was never handed to anyone. No
|
|
10
|
+
* close frame can ever arrive for either. Refusing every reconnect
|
|
11
|
+
* against such a socket locks the session out for good.
|
|
12
|
+
*
|
|
13
|
+
* Ownership needs peer-originated evidence, and that evidence has to
|
|
14
|
+
* outlive a hibernation cycle, because isolate memory does not. Two
|
|
15
|
+
* sources qualify:
|
|
16
|
+
*
|
|
17
|
+
* 1. `seenAt` in the socket's attachment. The attachment survives
|
|
18
|
+
* hibernation. Every inbound frame refreshes it, rate-limited to one
|
|
19
|
+
* write per STAMP_INTERVAL_MS, so a fast typist costs one attachment
|
|
20
|
+
* write per 15 s.
|
|
21
|
+
* 2. `ctx.getWebSocketAutoResponseTimestamp(ws)`. The runtime answers
|
|
22
|
+
* the configured `ping` itself without waking the object, so a ping
|
|
23
|
+
* never reaches case 1 and leaves no other trace. The browser
|
|
24
|
+
* terminal does not ping — it probes with an ordinary frame, for
|
|
25
|
+
* the reasons public/s/index.html gives — so this covers the other
|
|
26
|
+
* clients rather than that one.
|
|
27
|
+
*
|
|
28
|
+
* A close or an error is better than either: the runtime is telling the
|
|
29
|
+
* object outright that this socket is finished, so `clearShellSocketStamp`
|
|
30
|
+
* drops the evidence there and the next upgrade needs no inference at all.
|
|
31
|
+
*
|
|
32
|
+
* A socket owns the terminal while the later of the two falls inside
|
|
33
|
+
* SHELL_OWNER_LIVENESS_MS. The window has to cover the longest silence a
|
|
34
|
+
* live tab can produce. Browsers throttle a background tab's timers to
|
|
35
|
+
* about one tick per minute, so 120 s clears that with margin. A tab
|
|
36
|
+
* quiet for longer has stopped running its own code, and the upgrade that
|
|
37
|
+
* takes the terminal also closes its socket — so it redials cleanly
|
|
38
|
+
* instead of holding a terminal it can no longer use.
|
|
39
|
+
*/
|
|
40
|
+
/** How long a shell socket owns the terminal after its peer was heard. */
|
|
41
|
+
export declare const SHELL_OWNER_LIVENESS_MS = 120000;
|
|
42
|
+
/** The part of `DurableObjectState` this module reads. */
|
|
43
|
+
export interface AutoResponseHost {
|
|
44
|
+
getWebSocketAutoResponseTimestamp?(ws: WebSocket): Date | null;
|
|
45
|
+
}
|
|
46
|
+
/** Tag a freshly accepted terminal socket as this session's shell owner. */
|
|
47
|
+
export declare function tagShellSocket(ws: WebSocket, now?: number): void;
|
|
48
|
+
/**
|
|
49
|
+
* Drop the evidence that a peer was on this socket.
|
|
50
|
+
*
|
|
51
|
+
* Called when the runtime reports the socket closed or errored. Both say
|
|
52
|
+
* this socket is finished, so the next upgrade must not read a stamp the
|
|
53
|
+
* dying handler wrote moments earlier and refuse the reconnect. Keeps the
|
|
54
|
+
* kind, so the socket still classifies as the terminal's while it drains.
|
|
55
|
+
* A peer that turns out to be alive re-stamps on its next frame.
|
|
56
|
+
*/
|
|
57
|
+
export declare function clearShellSocketStamp(ws: WebSocket): void;
|
|
58
|
+
/**
|
|
59
|
+
* Record that the peer on this socket is still there.
|
|
60
|
+
*
|
|
61
|
+
* Called on every inbound shell frame. Rewrites the attachment only once
|
|
62
|
+
* the recorded stamp has aged past STAMP_INTERVAL_MS. No-op on sockets
|
|
63
|
+
* that carry another kind.
|
|
64
|
+
*/
|
|
65
|
+
export declare function noteShellSocketActivity(ws: WebSocket, now?: number): void;
|
|
66
|
+
/** True when another browser terminal currently holds this session. */
|
|
67
|
+
export declare function hasLiveShellOwner(ctx: AutoResponseHost | undefined, sockets: readonly WebSocket[], now?: number): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Close the shell sockets no peer holds any more.
|
|
70
|
+
*
|
|
71
|
+
* Called on an upgrade that is taking the terminal. Without it, a socket
|
|
72
|
+
* whose tab is gone stays accepted for the life of the object, and every
|
|
73
|
+
* warm rejoin adds another one.
|
|
74
|
+
*/
|
|
75
|
+
export declare function closeStaleShellSockets(ctx: AutoResponseHost | undefined, sockets: readonly WebSocket[], now?: number): void;
|
|
76
|
+
//# sourceMappingURL=shell-socket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-socket.d.ts","sourceRoot":"","sources":["../../src/session/shell-socket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAU/C,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC,CAAC,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;CAChE;AAwFD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI,CAE5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAGzD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI,CAKrF;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,gBAAgB,GAAG,SAAS,EACjC,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,GAAG,GAAE,MAAmB,GACvB,OAAO,CAET;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,gBAAgB,GAAG,SAAS,EACjC,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,GAAG,GAAE,MAAmB,GACvB,IAAI,CAKN"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/session/shell-socket.ts — who owns the session's terminal.
|
|
3
|
+
*
|
|
4
|
+
* The /ws upgrade refuses a second browser terminal on one session, so it
|
|
5
|
+
* has to know whether the terminal already has an owner. `readyState ===
|
|
6
|
+
* OPEN` does not answer that. It describes this end of the socket. A tab
|
|
7
|
+
* that dies without a close frame leaves a socket that still reads OPEN
|
|
8
|
+
* with nobody on the other end, and an upgrade that fails after accepting
|
|
9
|
+
* its socket leaves one whose client half was never handed to anyone. No
|
|
10
|
+
* close frame can ever arrive for either. Refusing every reconnect
|
|
11
|
+
* against such a socket locks the session out for good.
|
|
12
|
+
*
|
|
13
|
+
* Ownership needs peer-originated evidence, and that evidence has to
|
|
14
|
+
* outlive a hibernation cycle, because isolate memory does not. Two
|
|
15
|
+
* sources qualify:
|
|
16
|
+
*
|
|
17
|
+
* 1. `seenAt` in the socket's attachment. The attachment survives
|
|
18
|
+
* hibernation. Every inbound frame refreshes it, rate-limited to one
|
|
19
|
+
* write per STAMP_INTERVAL_MS, so a fast typist costs one attachment
|
|
20
|
+
* write per 15 s.
|
|
21
|
+
* 2. `ctx.getWebSocketAutoResponseTimestamp(ws)`. The runtime answers
|
|
22
|
+
* the configured `ping` itself without waking the object, so a ping
|
|
23
|
+
* never reaches case 1 and leaves no other trace. The browser
|
|
24
|
+
* terminal does not ping — it probes with an ordinary frame, for
|
|
25
|
+
* the reasons public/s/index.html gives — so this covers the other
|
|
26
|
+
* clients rather than that one.
|
|
27
|
+
*
|
|
28
|
+
* A close or an error is better than either: the runtime is telling the
|
|
29
|
+
* object outright that this socket is finished, so `clearShellSocketStamp`
|
|
30
|
+
* drops the evidence there and the next upgrade needs no inference at all.
|
|
31
|
+
*
|
|
32
|
+
* A socket owns the terminal while the later of the two falls inside
|
|
33
|
+
* SHELL_OWNER_LIVENESS_MS. The window has to cover the longest silence a
|
|
34
|
+
* live tab can produce. Browsers throttle a background tab's timers to
|
|
35
|
+
* about one tick per minute, so 120 s clears that with margin. A tab
|
|
36
|
+
* quiet for longer has stopped running its own code, and the upgrade that
|
|
37
|
+
* takes the terminal also closes its socket — so it redials cleanly
|
|
38
|
+
* instead of holding a terminal it can no longer use.
|
|
39
|
+
*/
|
|
40
|
+
/** How long a shell socket owns the terminal after its peer was heard. */
|
|
41
|
+
export const SHELL_OWNER_LIVENESS_MS = 120_000;
|
|
42
|
+
/** Smallest gap between two attachment writes on one socket. */
|
|
43
|
+
const STAMP_INTERVAL_MS = 15_000;
|
|
44
|
+
/** Tolerance for a `seenAt` that reads ahead of the current clock. */
|
|
45
|
+
const CLOCK_SKEW_MS = 60_000;
|
|
46
|
+
const SHELL_KIND = 'shell';
|
|
47
|
+
function readAttachment(ws) {
|
|
48
|
+
try {
|
|
49
|
+
const read = Reflect.get(ws, 'deserializeAttachment');
|
|
50
|
+
if (typeof read !== 'function')
|
|
51
|
+
return null;
|
|
52
|
+
return Reflect.apply(read, ws, []);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function writeAttachment(ws, attachment) {
|
|
59
|
+
try {
|
|
60
|
+
const write = Reflect.get(ws, 'serializeAttachment');
|
|
61
|
+
if (typeof write === 'function')
|
|
62
|
+
Reflect.apply(write, ws, [attachment]);
|
|
63
|
+
}
|
|
64
|
+
catch { /* the socket is closing, or the runtime refused the write */ }
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Read a shell socket's stamp in one attachment pass.
|
|
68
|
+
*
|
|
69
|
+
* Returns null when the socket carries another kind, and a stamp of null
|
|
70
|
+
* when it is a shell socket nothing has stamped yet. An attachment
|
|
71
|
+
* outlives the deploy that wrote it and is untrusted on the way back, so
|
|
72
|
+
* a stamp that is not a plain timestamp, or that reads further ahead than
|
|
73
|
+
* the clocks can differ, counts as no stamp at all.
|
|
74
|
+
*/
|
|
75
|
+
function readShellStamp(ws, now) {
|
|
76
|
+
const attachment = readAttachment(ws);
|
|
77
|
+
if (typeof attachment !== 'object' || attachment === null)
|
|
78
|
+
return null;
|
|
79
|
+
const record = attachment;
|
|
80
|
+
if (record.kind !== SHELL_KIND)
|
|
81
|
+
return null;
|
|
82
|
+
const seenAt = record.seenAt;
|
|
83
|
+
if (typeof seenAt !== 'number' || !Number.isFinite(seenAt) || seenAt > now + CLOCK_SKEW_MS) {
|
|
84
|
+
return { seenAt: null };
|
|
85
|
+
}
|
|
86
|
+
return { seenAt };
|
|
87
|
+
}
|
|
88
|
+
/** When the runtime last answered a ping on this socket, if it ever did. */
|
|
89
|
+
function autoResponseAt(ctx, ws, now) {
|
|
90
|
+
// Null whenever the runtime has never auto-answered on this socket.
|
|
91
|
+
const at = ctx?.getWebSocketAutoResponseTimestamp?.(ws) ?? null;
|
|
92
|
+
if (!at)
|
|
93
|
+
return null;
|
|
94
|
+
const ms = at.getTime();
|
|
95
|
+
if (!Number.isFinite(ms) || ms > now + CLOCK_SKEW_MS)
|
|
96
|
+
return null;
|
|
97
|
+
return ms;
|
|
98
|
+
}
|
|
99
|
+
/** True while a peer is still proven to be on this shell socket. */
|
|
100
|
+
function ownsTerminal(ctx, ws, stamp, now) {
|
|
101
|
+
const answered = autoResponseAt(ctx, ws, now);
|
|
102
|
+
const heard = stamp.seenAt === null
|
|
103
|
+
? answered
|
|
104
|
+
: (answered === null ? stamp.seenAt : Math.max(stamp.seenAt, answered));
|
|
105
|
+
return heard !== null && now - heard < SHELL_OWNER_LIVENESS_MS;
|
|
106
|
+
}
|
|
107
|
+
/** The open shell sockets, each with the stamp it carries. */
|
|
108
|
+
function openShellSockets(sockets, now) {
|
|
109
|
+
const found = [];
|
|
110
|
+
for (const ws of sockets) {
|
|
111
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
112
|
+
continue;
|
|
113
|
+
const stamp = readShellStamp(ws, now);
|
|
114
|
+
if (stamp)
|
|
115
|
+
found.push({ ws, stamp });
|
|
116
|
+
}
|
|
117
|
+
return found;
|
|
118
|
+
}
|
|
119
|
+
/** Tag a freshly accepted terminal socket as this session's shell owner. */
|
|
120
|
+
export function tagShellSocket(ws, now = Date.now()) {
|
|
121
|
+
writeAttachment(ws, { kind: SHELL_KIND, seenAt: now });
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Drop the evidence that a peer was on this socket.
|
|
125
|
+
*
|
|
126
|
+
* Called when the runtime reports the socket closed or errored. Both say
|
|
127
|
+
* this socket is finished, so the next upgrade must not read a stamp the
|
|
128
|
+
* dying handler wrote moments earlier and refuse the reconnect. Keeps the
|
|
129
|
+
* kind, so the socket still classifies as the terminal's while it drains.
|
|
130
|
+
* A peer that turns out to be alive re-stamps on its next frame.
|
|
131
|
+
*/
|
|
132
|
+
export function clearShellSocketStamp(ws) {
|
|
133
|
+
if (readShellStamp(ws, Date.now()) === null)
|
|
134
|
+
return;
|
|
135
|
+
writeAttachment(ws, { kind: SHELL_KIND });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Record that the peer on this socket is still there.
|
|
139
|
+
*
|
|
140
|
+
* Called on every inbound shell frame. Rewrites the attachment only once
|
|
141
|
+
* the recorded stamp has aged past STAMP_INTERVAL_MS. No-op on sockets
|
|
142
|
+
* that carry another kind.
|
|
143
|
+
*/
|
|
144
|
+
export function noteShellSocketActivity(ws, now = Date.now()) {
|
|
145
|
+
const stamp = readShellStamp(ws, now);
|
|
146
|
+
if (!stamp)
|
|
147
|
+
return;
|
|
148
|
+
if (stamp.seenAt !== null && now - stamp.seenAt < STAMP_INTERVAL_MS)
|
|
149
|
+
return;
|
|
150
|
+
writeAttachment(ws, { kind: SHELL_KIND, seenAt: now });
|
|
151
|
+
}
|
|
152
|
+
/** True when another browser terminal currently holds this session. */
|
|
153
|
+
export function hasLiveShellOwner(ctx, sockets, now = Date.now()) {
|
|
154
|
+
return openShellSockets(sockets, now).some(({ ws, stamp }) => ownsTerminal(ctx, ws, stamp, now));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Close the shell sockets no peer holds any more.
|
|
158
|
+
*
|
|
159
|
+
* Called on an upgrade that is taking the terminal. Without it, a socket
|
|
160
|
+
* whose tab is gone stays accepted for the life of the object, and every
|
|
161
|
+
* warm rejoin adds another one.
|
|
162
|
+
*/
|
|
163
|
+
export function closeStaleShellSockets(ctx, sockets, now = Date.now()) {
|
|
164
|
+
for (const { ws, stamp } of openShellSockets(sockets, now)) {
|
|
165
|
+
if (ownsTerminal(ctx, ws, stamp, now))
|
|
166
|
+
continue;
|
|
167
|
+
try {
|
|
168
|
+
ws.close(1001, 'terminal taken over');
|
|
169
|
+
}
|
|
170
|
+
catch { /* already closing */ }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -53,7 +53,7 @@ export async function startRealVite(self, opts) {
|
|
|
53
53
|
if (cfgPath) {
|
|
54
54
|
try {
|
|
55
55
|
if (!self.esbuildService)
|
|
56
|
-
self.esbuildService = new EsbuildService(
|
|
56
|
+
self.esbuildService = new EsbuildService(kernelFs);
|
|
57
57
|
const bundleResult = await self.esbuildService.build([cfgPath], {
|
|
58
58
|
bundle: true,
|
|
59
59
|
format: 'esm',
|