@ai-ecoverse/slicc-kernel 1.8.11 → 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) {
@@ -35,48 +526,19 @@ var MemoryMeta = class {
35
526
  }
36
527
  async remove(paths) {
37
528
  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);
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("/")) || "/";
@@ -1379,6 +1841,11 @@ function statJson(s) {
1379
1841
  async function readdirStat(fs, dir) {
1380
1842
  return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
1381
1843
  }
1844
+ async function storageUsage() {
1845
+ const estimate = await globalThis.navigator?.storage?.estimate?.();
1846
+ if (typeof estimate?.quota !== "number") return null;
1847
+ return { quota: estimate.quota, usage: estimate.usage ?? 0 };
1848
+ }
1382
1849
  var done = { ok: true, kind: "void" };
1383
1850
  function json(value) {
1384
1851
  return { ok: true, kind: "json", json: value };
@@ -1429,6 +1896,8 @@ async function run(fs, path, req, cwd) {
1429
1896
  case "utimes":
1430
1897
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
1431
1898
  return done;
1899
+ case "statfs":
1900
+ return json(await storageUsage());
1432
1901
  default:
1433
1902
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
1434
1903
  }
@@ -3152,61 +3621,6 @@ var HttpHandles = class {
3152
3621
  }
3153
3622
  };
3154
3623
 
3155
- // src/kernel/signals.ts
3156
- var SIG = {
3157
- HUP: 1,
3158
- INT: 2,
3159
- QUIT: 3,
3160
- ILL: 4,
3161
- TRAP: 5,
3162
- ABRT: 6,
3163
- BUS: 7,
3164
- FPE: 8,
3165
- KILL: 9,
3166
- USR1: 10,
3167
- SEGV: 11,
3168
- USR2: 12,
3169
- PIPE: 13,
3170
- ALRM: 14,
3171
- TERM: 15,
3172
- STKFLT: 16,
3173
- CHLD: 17,
3174
- CONT: 18,
3175
- STOP: 19,
3176
- TSTP: 20,
3177
- TTIN: 21,
3178
- TTOU: 22,
3179
- URG: 23,
3180
- XCPU: 24,
3181
- XFSZ: 25,
3182
- VTALRM: 26,
3183
- PROF: 27,
3184
- WINCH: 28,
3185
- POLL: 29,
3186
- PWR: 30,
3187
- SYS: 31
3188
- };
3189
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
3190
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
3191
- function defaultAction(sig) {
3192
- if (sig === SIG.CONT) return "continue";
3193
- if (STOPPING.has(sig)) return "stop";
3194
- return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
3195
- }
3196
- function isSignal(sig) {
3197
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
3198
- }
3199
- function sigbit(sig) {
3200
- return 1 << sig;
3201
- }
3202
- var SIGNAL_BY_NAME = {
3203
- SIGINT: SIG.INT,
3204
- SIGTERM: SIG.TERM,
3205
- SIGKILL: SIG.KILL,
3206
- SIGSTOP: SIG.STOP,
3207
- SIGCONT: SIG.CONT
3208
- };
3209
-
3210
3624
  // src/kernel/tty.ts
3211
3625
  var ICRNL = 256;
3212
3626
  var IUTF8 = 16384;
@@ -4409,6 +4823,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4409
4823
  "proc-captured",
4410
4824
  "proc-fork",
4411
4825
  "proc-kill",
4826
+ "proc-list",
4412
4827
  "proc-exec",
4413
4828
  "proc-setpgid",
4414
4829
  "proc-getpgid",
@@ -4857,6 +5272,12 @@ var WasmProcess = class {
4857
5272
  throw new KernelError("ESRCH");
4858
5273
  }
4859
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
+ };
4860
5281
  case "proc-alarm":
4861
5282
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
4862
5283
  return { ok: true, kind: "void" };
@@ -4960,6 +5381,7 @@ function spawnWasmProcess(opts) {
4960
5381
  forker: opts.forker,
4961
5382
  fs: opts.fs,
4962
5383
  kill: opts.kill,
5384
+ processes: opts.processes,
4963
5385
  jobs: opts.jobs,
4964
5386
  ptys: opts.ptys,
4965
5387
  net: opts.net,
@@ -5104,6 +5526,9 @@ var JobTable = class {
5104
5526
  this.members.set(pid, member);
5105
5527
  return member;
5106
5528
  }
5529
+ list() {
5530
+ return [...this.members.values()];
5531
+ }
5107
5532
  remove(pid) {
5108
5533
  this.members.delete(pid);
5109
5534
  }
@@ -5434,6 +5859,7 @@ var Launcher = class {
5434
5859
  processes = /* @__PURE__ */ new Map();
5435
5860
  zombies = /* @__PURE__ */ new Set();
5436
5861
  orphans = /* @__PURE__ */ new Set();
5862
+ described = /* @__PURE__ */ new Map();
5437
5863
  jobs = new JobTable();
5438
5864
  locks = new LockTable();
5439
5865
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
@@ -5441,6 +5867,7 @@ var Launcher = class {
5441
5867
  ca;
5442
5868
  transport;
5443
5869
  nextPid = 1e3;
5870
+ boot = Date.now();
5444
5871
  terminals = 0;
5445
5872
  constructor(options) {
5446
5873
  this.base = options.fs;
@@ -5612,6 +6039,7 @@ var Launcher = class {
5612
6039
  spawner: this.spawner(pid, req.report),
5613
6040
  forker: this.forker(pid, req),
5614
6041
  kill: (target, sig) => this.kill(target, sig),
6042
+ processes: () => ({ boot: this.boot, processes: this.list() }),
5615
6043
  jobs: this.jobs,
5616
6044
  ptys: this.ptys,
5617
6045
  net: this.net,
@@ -5623,17 +6051,57 @@ var Launcher = class {
5623
6051
  });
5624
6052
  this.processes.set(pid, handle);
5625
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
+ });
5626
6059
  void handle.exited.then(() => {
5627
6060
  this.processes.delete(pid);
5628
- 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);
5629
6062
  else this.zombies.add(pid);
5630
6063
  });
5631
6064
  return handle;
5632
6065
  }
6066
+ forget(pid) {
6067
+ this.jobs.remove(pid);
6068
+ this.described.delete(pid);
6069
+ }
5633
6070
  reaped(pid) {
5634
- if (this.zombies.delete(pid)) this.jobs.remove(pid);
6071
+ if (this.zombies.delete(pid)) this.forget(pid);
5635
6072
  else this.orphans.add(pid);
5636
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
+ }
5637
6105
  spawner(ppid, report) {
5638
6106
  return async (req, fds) => {
5639
6107
  const planned = await this.plan(req.file, req.argv, req.cwd);
@@ -5711,8 +6179,9 @@ var Launcher = class {
5711
6179
  const env = this.environment(cwd, options.env);
5712
6180
  const out = [];
5713
6181
  const err = [];
6182
+ const keep = options.collect !== false;
5714
6183
  const collect = (chunks2, tee) => (bytes) => {
5715
- chunks2.push(bytes);
6184
+ if (keep) chunks2.push(bytes);
5716
6185
  tee?.(bytes);
5717
6186
  };
5718
6187
  const stdout = collect(out, options.onStdout);
@@ -5728,6 +6197,7 @@ var Launcher = class {
5728
6197
  fds.installAt(1, sinkFile(stdout));
5729
6198
  fds.installAt(2, sinkFile(stderr));
5730
6199
  const handle = await this.launch(planned, { env, cwd, fds, report });
6200
+ options.onStarted?.(handle.pid);
5731
6201
  const status = await handle.exited;
5732
6202
  return { status, stdout: concat4(out), stderr: concat4(err) };
5733
6203
  }
@@ -5869,6 +6339,118 @@ function memoryRoot() {
5869
6339
  return new MemoryDirectory("", null);
5870
6340
  }
5871
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
+
5872
6454
  // src/serve.ts
5873
6455
  function signalNumber(name2) {
5874
6456
  const key = name2.slice(3);
@@ -5877,94 +6459,262 @@ function signalNumber(name2) {
5877
6459
  return SIG[key];
5878
6460
  }
5879
6461
 
5880
- // src/body-idle.ts
5881
- var BODY_IDLE_MS = 5 * 60 * 1e3;
5882
- function idleFailure(ms) {
5883
- return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
5884
- 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;
5885
6494
  });
6495
+ await granted.promise;
6496
+ return { name: name2, release: () => held.resolve() };
5886
6497
  }
5887
- async function readWithin(reader, ms) {
5888
- if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
5889
- let timer;
5890
- const idle = new Promise((_, reject) => {
5891
- timer = setTimeout(() => reject(idleFailure(ms)), ms);
5892
- });
5893
- try {
5894
- return await Promise.race([reader.read(), idle]);
5895
- } finally {
5896
- 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;
5897
6504
  }
6505
+ return out;
5898
6506
  }
5899
-
5900
- // src/transport.ts
5901
- var MAX_REQUEST_BODY = 64 * 1024 * 1024;
5902
- function joinHeaders(headers) {
5903
- const out = new Headers();
5904
- for (const [name2, value] of headers) {
5905
- try {
5906
- out.append(name2, value);
5907
- } 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;
5908
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);
5909
6578
  }
5910
- 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
+ };
5911
6599
  }
5912
- async function* chunks(body, idleMs) {
5913
- if (!body) return;
5914
- const reader = body.getReader();
5915
- try {
5916
- for (let next = await readWithin(reader, idleMs); !next.done; next = await readWithin(reader, idleMs)) {
5917
- 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
5918
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;
5919
6678
  } finally {
5920
- await reader.cancel().catch(() => void 0);
6679
+ clearTimeout(timer);
5921
6680
  }
5922
- }
5923
- function fetchTransport(options = {}) {
5924
- 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;
5925
6700
  return {
5926
- traits: {
5927
- manualRedirects: false,
5928
- encodedBodies: false,
5929
- maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
5930
- 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 });
5931
6712
  },
5932
- async fetch(request) {
5933
- let response;
5934
- try {
5935
- response = await send(request.url, {
5936
- method: request.method,
5937
- headers: joinHeaders(request.headers),
5938
- ...request.body ? { body: request.body } : {},
5939
- signal: request.signal,
5940
- redirect: "follow",
5941
- credentials: "omit",
5942
- mode: "cors"
5943
- });
5944
- } catch (e) {
5945
- if (request.signal.aborted) throw e;
5946
- throw Object.assign(
5947
- new Error(
5948
- `fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
5949
- ),
5950
- { status: 502 }
5951
- );
5952
- }
5953
- const headers = [];
5954
- response.headers.forEach((value, name2) => {
5955
- headers.push([name2, value]);
5956
- });
5957
- const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
5958
- return {
5959
- status: response.status,
5960
- statusText: response.statusText,
5961
- headers,
5962
- body,
5963
- cancel: async () => {
5964
- await body.return(void 0);
5965
- await response.body?.cancel().catch(() => void 0);
5966
- }
5967
- };
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"));
5968
6718
  }
5969
6719
  };
5970
6720
  }
@@ -6026,6 +6776,7 @@ async function createNodeKernel(options = {}) {
6026
6776
  await fs.reconcile();
6027
6777
  const live = /* @__PURE__ */ new Set();
6028
6778
  const pending = /* @__PURE__ */ new Set();
6779
+ const clients = /* @__PURE__ */ new Set();
6029
6780
  let terminated = false;
6030
6781
  const file = options.worker ?? new URL("./node-process-worker.js", import.meta.url);
6031
6782
  const transport = options.network?.transport;
@@ -6095,14 +6846,29 @@ async function createNodeKernel(options = {}) {
6095
6846
  await fs.writeFile(path, data);
6096
6847
  },
6097
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
+ },
6098
6860
  terminate() {
6099
6861
  terminated = true;
6862
+ for (const client of [...clients]) client.detach();
6100
6863
  for (const reject of [...pending]) reject(new Error(TERMINATED));
6101
6864
  for (const kill of [...live]) kill();
6102
6865
  }
6103
6866
  };
6104
6867
  }
6105
6868
  export {
6869
+ KernelCallError,
6870
+ KernelGoneError,
6871
+ attachKernel,
6106
6872
  createNodeKernel,
6107
6873
  fetchTransport,
6108
6874
  memoryRoot,