@ai-ecoverse/slicc-kernel 1.8.12 → 1.9.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/dist/node.js CHANGED
@@ -1,5 +1,496 @@
1
1
  // src/node.ts
2
- import { Worker } from "node:worker_threads";
2
+ import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
3
+
4
+ // src/fs/types.ts
5
+ function fsError(code, path) {
6
+ return Object.assign(new Error(`${code}: ${path}`), { code });
7
+ }
8
+ function normalizePath(path) {
9
+ const parts = [];
10
+ for (const part of path.split("/")) {
11
+ if (part === "" || part === ".") continue;
12
+ if (part === "..") parts.pop();
13
+ else parts.push(part);
14
+ }
15
+ return `/${parts.join("/")}`;
16
+ }
17
+ function resolvePath(base, path) {
18
+ return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
19
+ }
20
+ function inodeOf(path) {
21
+ let h1 = 3735928559;
22
+ let h2 = 1103547991;
23
+ for (let i = 0; i < path.length; i++) {
24
+ const c = path.charCodeAt(i);
25
+ h1 = Math.imul(h1 ^ c, 2654435761);
26
+ h2 = Math.imul(h2 ^ c, 1597334677);
27
+ }
28
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
29
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
30
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
31
+ }
32
+
33
+ // src/kernel/signals.ts
34
+ var SIG = {
35
+ HUP: 1,
36
+ INT: 2,
37
+ QUIT: 3,
38
+ ILL: 4,
39
+ TRAP: 5,
40
+ ABRT: 6,
41
+ BUS: 7,
42
+ FPE: 8,
43
+ KILL: 9,
44
+ USR1: 10,
45
+ SEGV: 11,
46
+ USR2: 12,
47
+ PIPE: 13,
48
+ ALRM: 14,
49
+ TERM: 15,
50
+ STKFLT: 16,
51
+ CHLD: 17,
52
+ CONT: 18,
53
+ STOP: 19,
54
+ TSTP: 20,
55
+ TTIN: 21,
56
+ TTOU: 22,
57
+ URG: 23,
58
+ XCPU: 24,
59
+ XFSZ: 25,
60
+ VTALRM: 26,
61
+ PROF: 27,
62
+ WINCH: 28,
63
+ POLL: 29,
64
+ PWR: 30,
65
+ SYS: 31
66
+ };
67
+ var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
68
+ var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
69
+ function defaultAction(sig) {
70
+ if (sig === SIG.CONT) return "continue";
71
+ if (STOPPING.has(sig)) return "stop";
72
+ return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
73
+ }
74
+ function isSignal(sig) {
75
+ return Number.isInteger(sig) && sig >= 1 && sig <= 31;
76
+ }
77
+ function sigbit(sig) {
78
+ return 1 << sig;
79
+ }
80
+ var SIGNAL_BY_NAME = {
81
+ SIGINT: SIG.INT,
82
+ SIGTERM: SIG.TERM,
83
+ SIGKILL: SIG.KILL,
84
+ SIGSTOP: SIG.STOP,
85
+ SIGCONT: SIG.CONT
86
+ };
87
+
88
+ // src/body-idle.ts
89
+ var BODY_IDLE_MS = 5 * 60 * 1e3;
90
+ function idleFailure(ms) {
91
+ return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
92
+ code: "ETIMEDOUT"
93
+ });
94
+ }
95
+ async function readWithin(reader, ms) {
96
+ if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
97
+ let timer;
98
+ const idle = new Promise((_, reject) => {
99
+ timer = setTimeout(() => reject(idleFailure(ms)), ms);
100
+ });
101
+ try {
102
+ return await Promise.race([reader.read(), idle]);
103
+ } finally {
104
+ clearTimeout(timer);
105
+ }
106
+ }
107
+
108
+ // src/transport.ts
109
+ var MAX_REQUEST_BODY = 64 * 1024 * 1024;
110
+ function joinHeaders(headers) {
111
+ const out = new Headers();
112
+ for (const [name2, value] of headers) {
113
+ try {
114
+ out.append(name2, value);
115
+ } catch {
116
+ }
117
+ }
118
+ return out;
119
+ }
120
+ async function* chunks(body, idleMs) {
121
+ if (!body) return;
122
+ const reader = body.getReader();
123
+ try {
124
+ for (let next = await readWithin(reader, idleMs); !next.done; next = await readWithin(reader, idleMs)) {
125
+ if (next.value.byteLength > 0) yield next.value;
126
+ }
127
+ } finally {
128
+ await reader.cancel().catch(() => void 0);
129
+ }
130
+ }
131
+ function fetchTransport(options = {}) {
132
+ const send = options.fetch ?? globalThis.fetch.bind(globalThis);
133
+ return {
134
+ traits: {
135
+ manualRedirects: false,
136
+ encodedBodies: false,
137
+ maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
138
+ crossOrigin: "cors"
139
+ },
140
+ async fetch(request) {
141
+ let response;
142
+ try {
143
+ response = await send(request.url, {
144
+ method: request.method,
145
+ headers: joinHeaders(request.headers),
146
+ ...request.body ? { body: request.body } : {},
147
+ signal: request.signal,
148
+ redirect: "follow",
149
+ credentials: "omit",
150
+ mode: "cors"
151
+ });
152
+ } catch (e) {
153
+ if (request.signal.aborted) throw e;
154
+ throw Object.assign(
155
+ new Error(
156
+ `fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
157
+ ),
158
+ { status: 502 }
159
+ );
160
+ }
161
+ const headers = [];
162
+ response.headers.forEach((value, name2) => {
163
+ headers.push([name2, value]);
164
+ });
165
+ const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
166
+ return {
167
+ status: response.status,
168
+ statusText: response.statusText,
169
+ headers,
170
+ body,
171
+ cancel: async () => {
172
+ await body.return(void 0);
173
+ await response.body?.cancel().catch(() => void 0);
174
+ }
175
+ };
176
+ }
177
+ };
178
+ }
179
+ function serveTransport(peer, transport) {
180
+ const open = /* @__PURE__ */ new Map();
181
+ const fail2 = (nid, e) => {
182
+ open.delete(nid);
183
+ const { status, code } = e;
184
+ peer.postMessage({
185
+ net: "error",
186
+ nid,
187
+ message: e instanceof Error ? e.message : String(e),
188
+ ...typeof status === "number" ? { status } : {},
189
+ ...typeof code === "string" ? { code } : {}
190
+ });
191
+ };
192
+ const start = async (call) => {
193
+ const abort = new AbortController();
194
+ const entry = { abort };
195
+ open.set(call.nid, entry);
196
+ try {
197
+ const response = await transport.fetch({
198
+ url: call.url,
199
+ method: call.method,
200
+ headers: call.headers,
201
+ ...call.body ? { body: call.body } : {},
202
+ signal: abort.signal
203
+ });
204
+ if (!open.has(call.nid)) {
205
+ await response.cancel();
206
+ return;
207
+ }
208
+ open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
209
+ peer.postMessage({
210
+ net: "head",
211
+ nid: call.nid,
212
+ status: response.status,
213
+ statusText: response.statusText,
214
+ headers: response.headers
215
+ });
216
+ } catch (e) {
217
+ fail2(call.nid, e);
218
+ }
219
+ };
220
+ const read = async (nid) => {
221
+ const body = open.get(nid)?.body;
222
+ if (!body) return;
223
+ try {
224
+ const next = await body.next();
225
+ if (next.done) {
226
+ open.delete(nid);
227
+ peer.postMessage({ net: "end", nid });
228
+ } else {
229
+ const bytes = next.value.slice();
230
+ peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
231
+ }
232
+ } catch (e) {
233
+ fail2(nid, e);
234
+ }
235
+ };
236
+ const drop = (nid) => {
237
+ const entry = open.get(nid);
238
+ open.delete(nid);
239
+ entry?.abort.abort();
240
+ void entry?.response?.cancel().catch(() => void 0);
241
+ };
242
+ return {
243
+ answer(call) {
244
+ if (call.net === "fetch") void start(call);
245
+ else if (call.net === "read") void read(call.nid);
246
+ else drop(call.nid);
247
+ },
248
+ close() {
249
+ for (const nid of [...open.keys()]) drop(nid);
250
+ }
251
+ };
252
+ }
253
+
254
+ // src/client/protocol.ts
255
+ var PROTOCOL = [1, 0];
256
+ var FS_METHODS = [
257
+ "readFile",
258
+ "writeFile",
259
+ "stat",
260
+ "lstat",
261
+ "readdir",
262
+ "mkdir",
263
+ "rm",
264
+ "rename",
265
+ "realpath",
266
+ "symlink",
267
+ "readlink",
268
+ "exists"
269
+ ];
270
+ function locksOf(scope = globalThis) {
271
+ const locks = scope.navigator?.locks;
272
+ return typeof locks?.request === "function" ? locks : void 0;
273
+ }
274
+ function versionError(theirs) {
275
+ const major = Array.isArray(theirs) ? theirs[0] : void 0;
276
+ if (major === PROTOCOL[0]) return void 0;
277
+ const named = typeof major === "number" ? `${major}.x` : "without a version";
278
+ return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
279
+ }
280
+
281
+ // src/client/serve-client.ts
282
+ var MAX_LINKS = 40;
283
+ function act(session, req, signal) {
284
+ if (req.action === "write") session.write(req.bytes);
285
+ else if (req.action === "resize") session.resize(req.cols, req.rows);
286
+ else if (req.action === "signal") session.signal(signal(req.signal));
287
+ else session.close();
288
+ }
289
+ async function realpath(fs, path) {
290
+ let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
291
+ let resolved = "";
292
+ for (let hops = 0; parts.length > 0; ) {
293
+ const [name2, ...rest] = parts;
294
+ const next = `${resolved}/${name2}`;
295
+ if (!(await fs.lstat(next)).isSymbolicLink) {
296
+ resolved = next;
297
+ parts = rest;
298
+ continue;
299
+ }
300
+ if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
301
+ const target = await fs.readlink(next);
302
+ const base = target.startsWith("/") ? "/" : resolved || "/";
303
+ parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
304
+ resolved = "";
305
+ }
306
+ return resolved || "/";
307
+ }
308
+ async function fsCall(fs, method, args) {
309
+ const path = (i) => fs.resolvePath("/", String(args[i]));
310
+ switch (method) {
311
+ case "readFile": {
312
+ const bytes = (await fs.readFileBuffer(path(0))).slice();
313
+ return { result: bytes, transfer: [bytes.buffer] };
314
+ }
315
+ case "writeFile":
316
+ await fs.writeFile(path(0), args[1]);
317
+ return { result: true };
318
+ case "stat":
319
+ return { result: await fs.stat(path(0)) };
320
+ case "lstat":
321
+ return { result: await fs.lstat(path(0)) };
322
+ case "readdir":
323
+ return { result: await fs.readdir(path(0)) };
324
+ case "mkdir":
325
+ await fs.mkdir(path(0), { recursive: true });
326
+ return { result: true };
327
+ case "rm":
328
+ await fs.rm(path(0), { recursive: true, force: args[1] === true });
329
+ return { result: true };
330
+ case "rename":
331
+ await fs.rename(path(0), path(1));
332
+ return { result: true };
333
+ case "realpath":
334
+ return { result: await realpath(fs, String(args[0])) };
335
+ case "symlink":
336
+ await fs.symlink(String(args[0]), path(1));
337
+ return { result: true };
338
+ case "readlink":
339
+ return { result: await fs.readlink(path(0)) };
340
+ default:
341
+ return { result: await fs.exists(path(0)) };
342
+ }
343
+ }
344
+ function serveClient(port, host) {
345
+ let state = "new";
346
+ const terminals = /* @__PURE__ */ new Map();
347
+ const groups = /* @__PURE__ */ new Set();
348
+ let net;
349
+ const ended = Promise.withResolvers();
350
+ const send = (message2, transfer = []) => {
351
+ if (state !== "closed") port.postMessage(message2, transfer);
352
+ };
353
+ const reply = (id, body, transfer) => send({ id, ...body }, transfer);
354
+ function detach(kill = false, bye = "the slicc-kernel detached this client") {
355
+ if (state === "closed") return;
356
+ send({ bye });
357
+ state = "closed";
358
+ net?.close();
359
+ for (const session of terminals.values()) session.close();
360
+ terminals.clear();
361
+ const pids = [...groups];
362
+ groups.clear();
363
+ if (kill && pids.length > 0) {
364
+ void host.launcher().then((launcher) => {
365
+ for (const pid of pids) launcher.kill(-pid, SIG.KILL);
366
+ });
367
+ }
368
+ port.close?.();
369
+ ended.resolve();
370
+ }
371
+ async function hello(message2) {
372
+ const refused = versionError(message2.protocol);
373
+ if (refused) {
374
+ send({ hello: { protocol: PROTOCOL, error: refused } });
375
+ detach();
376
+ return;
377
+ }
378
+ const launcher = await host.launcher();
379
+ const transport = host.transport?.() ?? launcher.transport;
380
+ net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
381
+ state = "open";
382
+ if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
383
+ send({
384
+ hello: {
385
+ protocol: PROTOCOL,
386
+ traits: transport.traits,
387
+ ...host.lock ? { lock: host.lock } : {}
388
+ }
389
+ });
390
+ }
391
+ async function terminal(req, l) {
392
+ const session = await l.openTerminal(req.argv, {
393
+ ...req.options,
394
+ onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
395
+ });
396
+ if (state === "closed") {
397
+ session.close();
398
+ } else {
399
+ terminals.set(req.id, session);
400
+ reply(req.id, { started: session.pid });
401
+ }
402
+ try {
403
+ return { result: await session.exited };
404
+ } finally {
405
+ terminals.delete(req.id);
406
+ }
407
+ }
408
+ async function spawn(req, l) {
409
+ const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
410
+ let leader;
411
+ const result = await l.run(req.argv, {
412
+ ...req.options,
413
+ collect: false,
414
+ onStdout: out(1),
415
+ onStderr: out(2),
416
+ onStarted: (pid) => {
417
+ leader = pid;
418
+ if (state === "closed") return;
419
+ groups.add(pid);
420
+ reply(req.id, { started: pid });
421
+ }
422
+ });
423
+ if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
424
+ if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
425
+ return { result: result.status };
426
+ }
427
+ async function handle(req) {
428
+ const l = await host.launcher();
429
+ switch (req.op) {
430
+ case "spawn":
431
+ return spawn(req, l);
432
+ case "open-terminal":
433
+ return terminal(req, l);
434
+ case "terminal": {
435
+ const session = terminals.get(req.terminal);
436
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
437
+ act(session, req, host.signal);
438
+ return { result: true };
439
+ }
440
+ case "kill":
441
+ if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
442
+ return { result: true };
443
+ case "ps":
444
+ return {
445
+ result: l.list().map(({ tid: _, ...entry }) => entry)
446
+ };
447
+ case "fs":
448
+ if (!FS_METHODS.includes(req.method)) {
449
+ throw new Error(`unknown file system call ${req.method}`);
450
+ }
451
+ return fsCall(l.fs, req.method, req.args);
452
+ case "detach":
453
+ return { result: true };
454
+ default:
455
+ throw new Error(`unknown op ${req.op}`);
456
+ }
457
+ }
458
+ port.addEventListener("message", (event) => {
459
+ const data = event.data;
460
+ if (state === "closed") return;
461
+ if (data.hello) {
462
+ if (state === "new") {
463
+ hello(data.hello).catch((err) => {
464
+ send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
465
+ detach();
466
+ });
467
+ }
468
+ return;
469
+ }
470
+ if (state !== "open") return;
471
+ if (data.net !== void 0) {
472
+ net?.answer(data);
473
+ return;
474
+ }
475
+ const req = data;
476
+ handle(req).then(
477
+ ({ result, transfer }) => {
478
+ reply(req.id, { result }, transfer);
479
+ if (req.op === "detach") detach(req.kill === true);
480
+ },
481
+ (err) => {
482
+ const code = err?.code;
483
+ reply(req.id, {
484
+ error: err instanceof Error ? err.message : String(err),
485
+ ...typeof code === "string" ? { code } : {}
486
+ });
487
+ }
488
+ );
489
+ });
490
+ port.addEventListener("close", () => detach());
491
+ port.start?.();
492
+ return { detach, closed: ended.promise };
493
+ }
3
494
 
4
495
  // src/fs/meta.ts
5
496
  function parentOf(path) {
@@ -34,49 +525,20 @@ var MemoryMeta = class {
34
525
  this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
35
526
  }
36
527
  async remove(paths) {
37
- for (const key of [...this.entries.keys()]) {
38
- if (paths.some((path) => under(key, path))) this.entries.delete(key);
39
- }
40
- }
41
- async links(dir) {
42
- return [...this.entries.values()].filter((entry) => entry.dir === dir);
43
- }
44
- async under(path) {
45
- return [...this.entries.values()].filter((entry) => under(entry.path, path));
46
- }
47
- async all() {
48
- return [...this.entries.values()];
49
- }
50
- };
51
-
52
- // src/fs/types.ts
53
- function fsError(code, path) {
54
- return Object.assign(new Error(`${code}: ${path}`), { code });
55
- }
56
- function normalizePath(path) {
57
- const parts = [];
58
- for (const part of path.split("/")) {
59
- if (part === "" || part === ".") continue;
60
- if (part === "..") parts.pop();
61
- else parts.push(part);
528
+ for (const key of [...this.entries.keys()]) {
529
+ if (paths.some((path) => under(key, path))) this.entries.delete(key);
530
+ }
62
531
  }
63
- return `/${parts.join("/")}`;
64
- }
65
- function resolvePath(base, path) {
66
- return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
67
- }
68
- function inodeOf(path) {
69
- let h1 = 3735928559;
70
- let h2 = 1103547991;
71
- for (let i = 0; i < path.length; i++) {
72
- const c = path.charCodeAt(i);
73
- h1 = Math.imul(h1 ^ c, 2654435761);
74
- h2 = Math.imul(h2 ^ c, 1597334677);
532
+ async links(dir) {
533
+ return [...this.entries.values()].filter((entry) => entry.dir === dir);
75
534
  }
76
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
77
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
78
- return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
79
- }
535
+ async under(path) {
536
+ return [...this.entries.values()].filter((entry) => under(entry.path, path));
537
+ }
538
+ async all() {
539
+ return [...this.entries.values()];
540
+ }
541
+ };
80
542
 
81
543
  // src/fs/opfs.ts
82
544
  var S_IFDIR = 16384;
@@ -84,7 +546,7 @@ var S_IFREG = 32768;
84
546
  var S_IFLNK = 40960;
85
547
  var PERM_MASK = 4095;
86
548
  var READ_ATTEMPTS = 3;
87
- var MAX_LINKS = 40;
549
+ var MAX_LINKS2 = 40;
88
550
  var CHECK_MS = 1e3;
89
551
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
90
552
  var ERRNO_BY_DOM_ERROR = {
@@ -235,7 +697,7 @@ var OpfsFs = class {
235
697
  }
236
698
  async locate(path, follow) {
237
699
  let current = path;
238
- for (let hops = 0; hops <= MAX_LINKS; hops++) {
700
+ for (let hops = 0; hops <= MAX_LINKS2; hops++) {
239
701
  try {
240
702
  return { path: current, handle: await this.handle(current) };
241
703
  } catch (err) {
@@ -699,7 +1161,7 @@ async function scanCommands(fs, modules) {
699
1161
  var S_IFDIR2 = 16384;
700
1162
  var S_IFREG2 = 32768;
701
1163
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
702
- var MAX_LINKS2 = 40;
1164
+ var MAX_LINKS3 = 40;
703
1165
  function missing2(err) {
704
1166
  return err?.code === "ENOENT";
705
1167
  }
@@ -719,7 +1181,7 @@ function synthetic(directory, path) {
719
1181
  }
720
1182
  async function followLinks(fs, path) {
721
1183
  let current = path;
722
- for (let hops = 0; hops < MAX_LINKS2; hops++) {
1184
+ for (let hops = 0; hops < MAX_LINKS3; hops++) {
723
1185
  const st = await fs.lstat(current).catch(() => void 0);
724
1186
  if (!st?.isSymbolicLink) return current;
725
1187
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -3159,61 +3621,6 @@ var HttpHandles = class {
3159
3621
  }
3160
3622
  };
3161
3623
 
3162
- // src/kernel/signals.ts
3163
- var SIG = {
3164
- HUP: 1,
3165
- INT: 2,
3166
- QUIT: 3,
3167
- ILL: 4,
3168
- TRAP: 5,
3169
- ABRT: 6,
3170
- BUS: 7,
3171
- FPE: 8,
3172
- KILL: 9,
3173
- USR1: 10,
3174
- SEGV: 11,
3175
- USR2: 12,
3176
- PIPE: 13,
3177
- ALRM: 14,
3178
- TERM: 15,
3179
- STKFLT: 16,
3180
- CHLD: 17,
3181
- CONT: 18,
3182
- STOP: 19,
3183
- TSTP: 20,
3184
- TTIN: 21,
3185
- TTOU: 22,
3186
- URG: 23,
3187
- XCPU: 24,
3188
- XFSZ: 25,
3189
- VTALRM: 26,
3190
- PROF: 27,
3191
- WINCH: 28,
3192
- POLL: 29,
3193
- PWR: 30,
3194
- SYS: 31
3195
- };
3196
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
3197
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
3198
- function defaultAction(sig) {
3199
- if (sig === SIG.CONT) return "continue";
3200
- if (STOPPING.has(sig)) return "stop";
3201
- return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
3202
- }
3203
- function isSignal(sig) {
3204
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
3205
- }
3206
- function sigbit(sig) {
3207
- return 1 << sig;
3208
- }
3209
- var SIGNAL_BY_NAME = {
3210
- SIGINT: SIG.INT,
3211
- SIGTERM: SIG.TERM,
3212
- SIGKILL: SIG.KILL,
3213
- SIGSTOP: SIG.STOP,
3214
- SIGCONT: SIG.CONT
3215
- };
3216
-
3217
3624
  // src/kernel/tty.ts
3218
3625
  var ICRNL = 256;
3219
3626
  var IUTF8 = 16384;
@@ -4416,6 +4823,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4416
4823
  "proc-captured",
4417
4824
  "proc-fork",
4418
4825
  "proc-kill",
4826
+ "proc-list",
4419
4827
  "proc-exec",
4420
4828
  "proc-setpgid",
4421
4829
  "proc-getpgid",
@@ -4864,6 +5272,12 @@ var WasmProcess = class {
4864
5272
  throw new KernelError("ESRCH");
4865
5273
  }
4866
5274
  return { ok: true, kind: "void" };
5275
+ case "proc-list":
5276
+ return {
5277
+ ok: true,
5278
+ kind: "json",
5279
+ json: this.options.processes?.() ?? { boot: 0, processes: [] }
5280
+ };
4867
5281
  case "proc-alarm":
4868
5282
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
4869
5283
  return { ok: true, kind: "void" };
@@ -4967,6 +5381,7 @@ function spawnWasmProcess(opts) {
4967
5381
  forker: opts.forker,
4968
5382
  fs: opts.fs,
4969
5383
  kill: opts.kill,
5384
+ processes: opts.processes,
4970
5385
  jobs: opts.jobs,
4971
5386
  ptys: opts.ptys,
4972
5387
  net: opts.net,
@@ -5111,6 +5526,9 @@ var JobTable = class {
5111
5526
  this.members.set(pid, member);
5112
5527
  return member;
5113
5528
  }
5529
+ list() {
5530
+ return [...this.members.values()];
5531
+ }
5114
5532
  remove(pid) {
5115
5533
  this.members.delete(pid);
5116
5534
  }
@@ -5441,6 +5859,7 @@ var Launcher = class {
5441
5859
  processes = /* @__PURE__ */ new Map();
5442
5860
  zombies = /* @__PURE__ */ new Set();
5443
5861
  orphans = /* @__PURE__ */ new Set();
5862
+ described = /* @__PURE__ */ new Map();
5444
5863
  jobs = new JobTable();
5445
5864
  locks = new LockTable();
5446
5865
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
@@ -5448,6 +5867,7 @@ var Launcher = class {
5448
5867
  ca;
5449
5868
  transport;
5450
5869
  nextPid = 1e3;
5870
+ boot = Date.now();
5451
5871
  terminals = 0;
5452
5872
  constructor(options) {
5453
5873
  this.base = options.fs;
@@ -5619,6 +6039,7 @@ var Launcher = class {
5619
6039
  spawner: this.spawner(pid, req.report),
5620
6040
  forker: this.forker(pid, req),
5621
6041
  kill: (target, sig) => this.kill(target, sig),
6042
+ processes: () => ({ boot: this.boot, processes: this.list() }),
5622
6043
  jobs: this.jobs,
5623
6044
  ptys: this.ptys,
5624
6045
  net: this.net,
@@ -5630,17 +6051,57 @@ var Launcher = class {
5630
6051
  });
5631
6052
  this.processes.set(pid, handle);
5632
6053
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
6054
+ this.described.set(pid, {
6055
+ argv: [req.argv0, ...req.args],
6056
+ tty: terminal?.name ?? null,
6057
+ started: Date.now()
6058
+ });
5633
6059
  void handle.exited.then(() => {
5634
6060
  this.processes.delete(pid);
5635
- if (req.ppid === void 0 || this.orphans.delete(pid)) this.jobs.remove(pid);
6061
+ if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
5636
6062
  else this.zombies.add(pid);
5637
6063
  });
5638
6064
  return handle;
5639
6065
  }
6066
+ forget(pid) {
6067
+ this.jobs.remove(pid);
6068
+ this.described.delete(pid);
6069
+ }
5640
6070
  reaped(pid) {
5641
- if (this.zombies.delete(pid)) this.jobs.remove(pid);
6071
+ if (this.zombies.delete(pid)) this.forget(pid);
5642
6072
  else this.orphans.add(pid);
5643
6073
  }
6074
+ list() {
6075
+ const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
6076
+ const rootOf = (member) => {
6077
+ let root = member;
6078
+ for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
6079
+ root = up;
6080
+ }
6081
+ return root;
6082
+ };
6083
+ const shown = (pid) => {
6084
+ const member = pid === void 0 ? void 0 : members.get(pid);
6085
+ return member ? rootOf(member).pid : pid ?? 0;
6086
+ };
6087
+ const listed = [];
6088
+ for (const member of members.values()) {
6089
+ const described = this.described.get(member.pid);
6090
+ if (member.execed || !described) continue;
6091
+ const root = rootOf(member);
6092
+ listed.push({
6093
+ pid: root.pid,
6094
+ tid: member.pid,
6095
+ ppid: shown(root.ppid),
6096
+ pgid: member.pgid,
6097
+ sid: member.sid,
6098
+ ...described,
6099
+ started: (this.described.get(root.pid) ?? described).started,
6100
+ state: this.processes.has(member.pid) ? "S" : "Z"
6101
+ });
6102
+ }
6103
+ return listed;
6104
+ }
5644
6105
  spawner(ppid, report) {
5645
6106
  return async (req, fds) => {
5646
6107
  const planned = await this.plan(req.file, req.argv, req.cwd);
@@ -5718,8 +6179,9 @@ var Launcher = class {
5718
6179
  const env = this.environment(cwd, options.env);
5719
6180
  const out = [];
5720
6181
  const err = [];
6182
+ const keep = options.collect !== false;
5721
6183
  const collect = (chunks2, tee) => (bytes) => {
5722
- chunks2.push(bytes);
6184
+ if (keep) chunks2.push(bytes);
5723
6185
  tee?.(bytes);
5724
6186
  };
5725
6187
  const stdout = collect(out, options.onStdout);
@@ -5735,6 +6197,7 @@ var Launcher = class {
5735
6197
  fds.installAt(1, sinkFile(stdout));
5736
6198
  fds.installAt(2, sinkFile(stderr));
5737
6199
  const handle = await this.launch(planned, { env, cwd, fds, report });
6200
+ options.onStarted?.(handle.pid);
5738
6201
  const status = await handle.exited;
5739
6202
  return { status, stdout: concat4(out), stderr: concat4(err) };
5740
6203
  }
@@ -5876,6 +6339,118 @@ function memoryRoot() {
5876
6339
  return new MemoryDirectory("", null);
5877
6340
  }
5878
6341
 
6342
+ // src/kernel/net/remote-transport.ts
6343
+ var TransportError = class extends Error {
6344
+ status;
6345
+ code;
6346
+ constructor(message2, status, code) {
6347
+ super(message2);
6348
+ this.status = status;
6349
+ this.code = code;
6350
+ }
6351
+ };
6352
+ var RemoteTransport = class {
6353
+ traits;
6354
+ port;
6355
+ waiting = /* @__PURE__ */ new Map();
6356
+ nextId = 0;
6357
+ failure;
6358
+ constructor(port, traits) {
6359
+ this.port = port;
6360
+ this.traits = traits;
6361
+ }
6362
+ receive(reply) {
6363
+ const waiter = this.waiting.get(reply.nid);
6364
+ if (!waiter) return;
6365
+ this.waiting.delete(reply.nid);
6366
+ if (reply.net === "error") {
6367
+ waiter.reject(new TransportError(reply.message, reply.status, reply.code));
6368
+ } else waiter.resolve(reply);
6369
+ }
6370
+ fail(error) {
6371
+ this.failure = error;
6372
+ for (const waiter of this.waiting.values()) waiter.reject(error);
6373
+ this.waiting.clear();
6374
+ }
6375
+ ask(call) {
6376
+ if (this.failure) return Promise.reject(this.failure);
6377
+ return new Promise((resolve, reject) => {
6378
+ this.waiting.set(call.nid, { resolve, reject });
6379
+ this.port.postMessage(call);
6380
+ });
6381
+ }
6382
+ cancel(nid) {
6383
+ const waiter = this.waiting.get(nid);
6384
+ this.waiting.delete(nid);
6385
+ waiter?.reject(new TransportError("the request was cancelled"));
6386
+ this.port.postMessage({ net: "cancel", nid });
6387
+ }
6388
+ async fetch(request) {
6389
+ const nid = ++this.nextId;
6390
+ const { signal } = request;
6391
+ signal.throwIfAborted();
6392
+ let finished = false;
6393
+ const abort = () => {
6394
+ finished = true;
6395
+ this.cancel(nid);
6396
+ };
6397
+ signal.addEventListener("abort", abort, { once: true });
6398
+ const finish = () => {
6399
+ if (finished) return;
6400
+ finished = true;
6401
+ signal.removeEventListener("abort", abort);
6402
+ };
6403
+ let head;
6404
+ try {
6405
+ head = await this.ask({
6406
+ net: "fetch",
6407
+ nid,
6408
+ url: request.url,
6409
+ method: request.method,
6410
+ headers: request.headers,
6411
+ ...request.body ? { body: request.body } : {}
6412
+ });
6413
+ } catch (e) {
6414
+ finish();
6415
+ throw e;
6416
+ }
6417
+ const { status, statusText, headers } = head;
6418
+ const read = async () => {
6419
+ if (finished) return void 0;
6420
+ const reply = await this.ask({ net: "read", nid }).catch((e) => {
6421
+ finish();
6422
+ throw e;
6423
+ });
6424
+ if (reply.net === "chunk") return reply.bytes;
6425
+ finish();
6426
+ return void 0;
6427
+ };
6428
+ const cancel = async () => {
6429
+ if (finished) return;
6430
+ finish();
6431
+ this.cancel(nid);
6432
+ };
6433
+ return {
6434
+ status,
6435
+ statusText,
6436
+ headers,
6437
+ cancel,
6438
+ body: {
6439
+ [Symbol.asyncIterator]: () => ({
6440
+ next: async () => {
6441
+ const bytes = await read();
6442
+ return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
6443
+ },
6444
+ return: async () => {
6445
+ await cancel();
6446
+ return { value: void 0, done: true };
6447
+ }
6448
+ })
6449
+ }
6450
+ };
6451
+ }
6452
+ };
6453
+
5879
6454
  // src/serve.ts
5880
6455
  function signalNumber(name2) {
5881
6456
  const key = name2.slice(3);
@@ -5884,94 +6459,262 @@ function signalNumber(name2) {
5884
6459
  return SIG[key];
5885
6460
  }
5886
6461
 
5887
- // src/body-idle.ts
5888
- var BODY_IDLE_MS = 5 * 60 * 1e3;
5889
- function idleFailure(ms) {
5890
- return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
5891
- code: "ETIMEDOUT"
6462
+ // src/client/attach.ts
6463
+ var KernelGoneError = class extends Error {
6464
+ constructor(message2 = "the slicc-kernel this client was attached to is gone") {
6465
+ super(message2);
6466
+ this.name = "KernelGoneError";
6467
+ }
6468
+ };
6469
+ var KernelCallError = class extends Error {
6470
+ code;
6471
+ constructor(message2, code) {
6472
+ super(message2);
6473
+ this.name = "KernelCallError";
6474
+ this.code = code;
6475
+ }
6476
+ };
6477
+ var encoder4 = new TextEncoder();
6478
+ var bytesOf2 = (data) => typeof data === "string" ? encoder4.encode(data) : data;
6479
+ var UNAVAILABLE = {
6480
+ manualRedirects: false,
6481
+ encodedBodies: false,
6482
+ maxRequestBody: 0,
6483
+ unavailable: true
6484
+ };
6485
+ async function holdLock(locks) {
6486
+ if (!locks) return { release() {
6487
+ } };
6488
+ const name2 = `slicc-kernel-client:${crypto.randomUUID()}`;
6489
+ const granted = Promise.withResolvers();
6490
+ const held = Promise.withResolvers();
6491
+ void locks.request(name2, () => {
6492
+ granted.resolve();
6493
+ return held.promise;
5892
6494
  });
6495
+ await granted.promise;
6496
+ return { name: name2, release: () => held.resolve() };
5893
6497
  }
5894
- async function readWithin(reader, ms) {
5895
- if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
5896
- let timer;
5897
- const idle = new Promise((_, reject) => {
5898
- timer = setTimeout(() => reject(idleFailure(ms)), ms);
5899
- });
5900
- try {
5901
- return await Promise.race([reader.read(), idle]);
5902
- } finally {
5903
- clearTimeout(timer);
6498
+ function concat5(chunks2) {
6499
+ const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
6500
+ let at = 0;
6501
+ for (const chunk2 of chunks2) {
6502
+ out.set(chunk2, at);
6503
+ at += chunk2.length;
5904
6504
  }
6505
+ return out;
5905
6506
  }
5906
-
5907
- // src/transport.ts
5908
- var MAX_REQUEST_BODY = 64 * 1024 * 1024;
5909
- function joinHeaders(headers) {
5910
- const out = new Headers();
5911
- for (const [name2, value] of headers) {
5912
- try {
5913
- out.append(name2, value);
5914
- } catch {
6507
+ var Channel = class {
6508
+ greeted = Promise.withResolvers();
6509
+ ended = Promise.withResolvers();
6510
+ remote;
6511
+ failure;
6512
+ pending = /* @__PURE__ */ new Map();
6513
+ nextId = 0;
6514
+ port;
6515
+ release;
6516
+ constructor(port, release) {
6517
+ this.port = port;
6518
+ this.release = release;
6519
+ this.greeted.promise.catch(() => void 0);
6520
+ port.addEventListener("message", (event) => this.receive(event.data));
6521
+ port.addEventListener("close", () => this.end(new KernelGoneError()));
6522
+ port.start?.();
6523
+ }
6524
+ post(message2) {
6525
+ if (!this.failure) this.port.postMessage(message2);
6526
+ }
6527
+ end(error) {
6528
+ if (this.failure) return;
6529
+ this.failure = error;
6530
+ this.greeted.reject(error);
6531
+ this.remote?.fail(error);
6532
+ for (const call of this.pending.values()) call.reject(error);
6533
+ this.pending.clear();
6534
+ this.release();
6535
+ this.port.close?.();
6536
+ this.ended.resolve(error);
6537
+ }
6538
+ request(call, handlers = {}) {
6539
+ const id = ++this.nextId;
6540
+ const { failure } = this;
6541
+ if (failure) return { id, done: Promise.reject(failure) };
6542
+ const done4 = new Promise((resolve, reject) => {
6543
+ this.pending.set(id, { resolve, reject, ...handlers });
6544
+ this.port.postMessage({ ...call, id });
6545
+ });
6546
+ return { id, done: done4 };
6547
+ }
6548
+ call(call) {
6549
+ return this.request(call).done;
6550
+ }
6551
+ receive(data) {
6552
+ if (data.bye !== void 0) {
6553
+ this.end(new KernelGoneError(data.bye));
6554
+ return;
6555
+ }
6556
+ if (data.hello) {
6557
+ this.greeted.resolve(data.hello);
6558
+ return;
5915
6559
  }
6560
+ if (data.net !== void 0) {
6561
+ this.remote?.receive(data);
6562
+ return;
6563
+ }
6564
+ const reply = data;
6565
+ const call = this.pending.get(reply.id);
6566
+ if (!call) return;
6567
+ if (reply.fd !== void 0) {
6568
+ call.output?.(reply.fd, reply.bytes);
6569
+ return;
6570
+ }
6571
+ if (reply.started !== void 0) {
6572
+ call.started?.(reply.started);
6573
+ return;
6574
+ }
6575
+ this.pending.delete(reply.id);
6576
+ if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
6577
+ else call.resolve(reply.result);
5916
6578
  }
5917
- return out;
6579
+ };
6580
+ async function spawnOn(channel, argv, opts) {
6581
+ const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
6582
+ const started = Promise.withResolvers();
6583
+ const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
6584
+ const { done: done4 } = channel.request(
6585
+ { op: "spawn", argv, options },
6586
+ {
6587
+ output: (fd, bytes) => (fd === 1 ? onStdout : onStderr)?.(bytes),
6588
+ started: started.resolve
6589
+ }
6590
+ );
6591
+ done4.catch(started.reject);
6592
+ const pid = await started.promise;
6593
+ return {
6594
+ pid,
6595
+ pgid: pid,
6596
+ exited: done4,
6597
+ signal: async (name2 = "SIGTERM") => void await channel.call({ op: "kill", pid: -pid, signal: name2 })
6598
+ };
5918
6599
  }
5919
- async function* chunks(body, idleMs) {
5920
- if (!body) return;
5921
- const reader = body.getReader();
5922
- try {
5923
- for (let next = await readWithin(reader, idleMs); !next.done; next = await readWithin(reader, idleMs)) {
5924
- if (next.value.byteLength > 0) yield next.value;
6600
+ async function runOn(channel, argv, opts) {
6601
+ const { onStdout, onStderr, ...rest } = opts;
6602
+ const chunks2 = { 1: [], 2: [] };
6603
+ const live = { 1: new TextDecoder(), 2: new TextDecoder() };
6604
+ const tee = (fd, on) => (bytes) => {
6605
+ chunks2[fd].push(bytes);
6606
+ on?.(live[fd].decode(bytes, { stream: true }));
6607
+ };
6608
+ const child = await spawnOn(channel, argv, {
6609
+ ...rest,
6610
+ onStdout: tee(1, onStdout),
6611
+ onStderr: tee(2, onStderr)
6612
+ });
6613
+ const status = await child.exited;
6614
+ const text = (fd) => new TextDecoder().decode(concat5(chunks2[fd]));
6615
+ return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
6616
+ }
6617
+ async function terminalOn(channel, argv, opts) {
6618
+ const { onData, ...options } = opts;
6619
+ let listener = onData ?? null;
6620
+ const backlog = [];
6621
+ const started = Promise.withResolvers();
6622
+ const { id, done: done4 } = channel.request(
6623
+ { op: "open-terminal", argv, options },
6624
+ {
6625
+ output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
6626
+ started: started.resolve
5925
6627
  }
6628
+ );
6629
+ done4.catch(started.reject);
6630
+ const pid = await started.promise;
6631
+ const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
6632
+ return {
6633
+ pid,
6634
+ exited: done4,
6635
+ get onData() {
6636
+ return listener;
6637
+ },
6638
+ set onData(next) {
6639
+ listener = next;
6640
+ if (next) for (const bytes of backlog.splice(0)) next(bytes);
6641
+ },
6642
+ write: (data) => send({ action: "write", bytes: bytesOf2(data) }),
6643
+ resize: (cols, rows) => send({ action: "resize", cols, rows }),
6644
+ signal: (name2) => send({ action: "signal", signal: name2 }),
6645
+ close: () => send({ action: "close" })
6646
+ };
6647
+ }
6648
+ function fsOn(channel) {
6649
+ const fs = (method, ...args) => channel.call({ op: "fs", method, args });
6650
+ const done4 = async (method, ...args) => void await fs(method, ...args);
6651
+ return {
6652
+ readFile: (path) => fs("readFile", path),
6653
+ readText: async (path) => new TextDecoder().decode(await fs("readFile", path)),
6654
+ writeFile: (path, data) => done4("writeFile", path, data),
6655
+ stat: (path) => fs("stat", path),
6656
+ lstat: (path) => fs("lstat", path),
6657
+ readdir: (path) => fs("readdir", path),
6658
+ mkdir: (path) => done4("mkdir", path),
6659
+ rm: (path, o = {}) => done4("rm", path, o.force === true),
6660
+ rename: (from, to) => done4("rename", from, to),
6661
+ realpath: (path) => fs("realpath", path),
6662
+ symlink: (target, path) => done4("symlink", target, path),
6663
+ readlink: (path) => fs("readlink", path),
6664
+ exists: (path) => fs("exists", path)
6665
+ };
6666
+ }
6667
+ async function greet(channel, lock, timeoutMs) {
6668
+ channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
6669
+ const timer = setTimeout(
6670
+ () => channel.end(
6671
+ new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
6672
+ ),
6673
+ timeoutMs
6674
+ );
6675
+ let hello;
6676
+ try {
6677
+ hello = await channel.greeted.promise;
5926
6678
  } finally {
5927
- await reader.cancel().catch(() => void 0);
6679
+ clearTimeout(timer);
5928
6680
  }
5929
- }
5930
- function fetchTransport(options = {}) {
5931
- const send = options.fetch ?? globalThis.fetch.bind(globalThis);
6681
+ const refused = hello.error ?? versionError(hello.protocol);
6682
+ if (refused) {
6683
+ const error = new Error(refused);
6684
+ channel.end(error);
6685
+ throw error;
6686
+ }
6687
+ return hello;
6688
+ }
6689
+ async function attachKernel(port, options = {}) {
6690
+ const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
6691
+ const own = await holdLock(locks);
6692
+ const channel = new Channel(port, own.release);
6693
+ const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
6694
+ if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
6695
+ const transport = new RemoteTransport(
6696
+ { postMessage: (call) => channel.post(call) },
6697
+ hello.traits ?? UNAVAILABLE
6698
+ );
6699
+ channel.remote = transport;
5932
6700
  return {
5933
- traits: {
5934
- manualRedirects: false,
5935
- encodedBodies: false,
5936
- maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
5937
- crossOrigin: "cors"
6701
+ protocol: hello.protocol,
6702
+ transport,
6703
+ closed: channel.ended.promise,
6704
+ spawn: (argv, opts = {}) => spawnOn(channel, argv, opts),
6705
+ run: (argv, opts = {}) => runOn(channel, argv, opts),
6706
+ openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
6707
+ ps: () => channel.call({ op: "ps" }),
6708
+ kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
6709
+ fetch: (req) => {
6710
+ const { signal, ...rest } = req;
6711
+ return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
5938
6712
  },
5939
- async fetch(request) {
5940
- let response;
5941
- try {
5942
- response = await send(request.url, {
5943
- method: request.method,
5944
- headers: joinHeaders(request.headers),
5945
- ...request.body ? { body: request.body } : {},
5946
- signal: request.signal,
5947
- redirect: "follow",
5948
- credentials: "omit",
5949
- mode: "cors"
5950
- });
5951
- } catch (e) {
5952
- if (request.signal.aborted) throw e;
5953
- throw Object.assign(
5954
- new Error(
5955
- `fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
5956
- ),
5957
- { status: 502 }
5958
- );
5959
- }
5960
- const headers = [];
5961
- response.headers.forEach((value, name2) => {
5962
- headers.push([name2, value]);
5963
- });
5964
- const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
5965
- return {
5966
- status: response.status,
5967
- statusText: response.statusText,
5968
- headers,
5969
- body,
5970
- cancel: async () => {
5971
- await body.return(void 0);
5972
- await response.body?.cancel().catch(() => void 0);
5973
- }
5974
- };
6713
+ fs: fsOn(channel),
6714
+ async close(o = {}) {
6715
+ if (channel.failure) return;
6716
+ await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
6717
+ channel.end(new KernelGoneError("this client is closed"));
5975
6718
  }
5976
6719
  };
5977
6720
  }
@@ -6033,6 +6776,7 @@ async function createNodeKernel(options = {}) {
6033
6776
  await fs.reconcile();
6034
6777
  const live = /* @__PURE__ */ new Set();
6035
6778
  const pending = /* @__PURE__ */ new Set();
6779
+ const clients = /* @__PURE__ */ new Set();
6036
6780
  let terminated = false;
6037
6781
  const file = options.worker ?? new URL("./node-process-worker.js", import.meta.url);
6038
6782
  const transport = options.network?.transport;
@@ -6102,14 +6846,29 @@ async function createNodeKernel(options = {}) {
6102
6846
  await fs.writeFile(path, data);
6103
6847
  },
6104
6848
  readFile: async (path) => fs.readFileBuffer(path),
6849
+ async connect() {
6850
+ if (terminated) throw new Error(TERMINATED);
6851
+ const { port1, port2 } = new MessageChannel2();
6852
+ const served = serveClient(port1, {
6853
+ launcher: async () => launcher,
6854
+ signal: signalNumber
6855
+ });
6856
+ clients.add(served);
6857
+ void served.closed.then(() => clients.delete(served));
6858
+ return port2;
6859
+ },
6105
6860
  terminate() {
6106
6861
  terminated = true;
6862
+ for (const client of [...clients]) client.detach();
6107
6863
  for (const reject of [...pending]) reject(new Error(TERMINATED));
6108
6864
  for (const kill of [...live]) kill();
6109
6865
  }
6110
6866
  };
6111
6867
  }
6112
6868
  export {
6869
+ KernelCallError,
6870
+ KernelGoneError,
6871
+ attachKernel,
6113
6872
  createNodeKernel,
6114
6873
  fetchTransport,
6115
6874
  memoryRoot,