@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.
@@ -193,6 +193,33 @@ function pinScript(url) {
193
193
  }
194
194
  }
195
195
 
196
+ // src/client/protocol.ts
197
+ var PROTOCOL = [1, 0];
198
+ var FS_METHODS = [
199
+ "readFile",
200
+ "writeFile",
201
+ "stat",
202
+ "lstat",
203
+ "readdir",
204
+ "mkdir",
205
+ "rm",
206
+ "rename",
207
+ "realpath",
208
+ "symlink",
209
+ "readlink",
210
+ "exists"
211
+ ];
212
+ function locksOf(scope = globalThis) {
213
+ const locks = scope.navigator?.locks;
214
+ return typeof locks?.request === "function" ? locks : void 0;
215
+ }
216
+ function versionError(theirs) {
217
+ const major = Array.isArray(theirs) ? theirs[0] : void 0;
218
+ if (major === PROTOCOL[0]) return void 0;
219
+ const named = typeof major === "number" ? `${major}.x` : "without a version";
220
+ return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
221
+ }
222
+
196
223
  // src/fs/types.ts
197
224
  function fsError(code, path) {
198
225
  return Object.assign(new Error(`${code}: ${path}`), { code });
@@ -222,13 +249,362 @@ function inodeOf(path) {
222
249
  return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
223
250
  }
224
251
 
252
+ // src/kernel/signals.ts
253
+ var SIG = {
254
+ HUP: 1,
255
+ INT: 2,
256
+ QUIT: 3,
257
+ ILL: 4,
258
+ TRAP: 5,
259
+ ABRT: 6,
260
+ BUS: 7,
261
+ FPE: 8,
262
+ KILL: 9,
263
+ USR1: 10,
264
+ SEGV: 11,
265
+ USR2: 12,
266
+ PIPE: 13,
267
+ ALRM: 14,
268
+ TERM: 15,
269
+ STKFLT: 16,
270
+ CHLD: 17,
271
+ CONT: 18,
272
+ STOP: 19,
273
+ TSTP: 20,
274
+ TTIN: 21,
275
+ TTOU: 22,
276
+ URG: 23,
277
+ XCPU: 24,
278
+ XFSZ: 25,
279
+ VTALRM: 26,
280
+ PROF: 27,
281
+ WINCH: 28,
282
+ POLL: 29,
283
+ PWR: 30,
284
+ SYS: 31
285
+ };
286
+ var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
287
+ var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
288
+ function defaultAction(sig) {
289
+ if (sig === SIG.CONT) return "continue";
290
+ if (STOPPING.has(sig)) return "stop";
291
+ return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
292
+ }
293
+ function isSignal(sig) {
294
+ return Number.isInteger(sig) && sig >= 1 && sig <= 31;
295
+ }
296
+ function sigbit(sig) {
297
+ return 1 << sig;
298
+ }
299
+ var SIGNAL_BY_NAME = {
300
+ SIGINT: SIG.INT,
301
+ SIGTERM: SIG.TERM,
302
+ SIGKILL: SIG.KILL,
303
+ SIGSTOP: SIG.STOP,
304
+ SIGCONT: SIG.CONT
305
+ };
306
+
307
+ // src/body-idle.ts
308
+ var BODY_IDLE_MS = 5 * 60 * 1e3;
309
+
310
+ // src/transport.ts
311
+ var MAX_REQUEST_BODY = 64 * 1024 * 1024;
312
+ function serveTransport(peer, transport) {
313
+ const open = /* @__PURE__ */ new Map();
314
+ const fail = (nid, e) => {
315
+ open.delete(nid);
316
+ const { status, code } = e;
317
+ peer.postMessage({
318
+ net: "error",
319
+ nid,
320
+ message: e instanceof Error ? e.message : String(e),
321
+ ...typeof status === "number" ? { status } : {},
322
+ ...typeof code === "string" ? { code } : {}
323
+ });
324
+ };
325
+ const start = async (call) => {
326
+ const abort = new AbortController();
327
+ const entry = { abort };
328
+ open.set(call.nid, entry);
329
+ try {
330
+ const response = await transport.fetch({
331
+ url: call.url,
332
+ method: call.method,
333
+ headers: call.headers,
334
+ ...call.body ? { body: call.body } : {},
335
+ signal: abort.signal
336
+ });
337
+ if (!open.has(call.nid)) {
338
+ await response.cancel();
339
+ return;
340
+ }
341
+ open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
342
+ peer.postMessage({
343
+ net: "head",
344
+ nid: call.nid,
345
+ status: response.status,
346
+ statusText: response.statusText,
347
+ headers: response.headers
348
+ });
349
+ } catch (e) {
350
+ fail(call.nid, e);
351
+ }
352
+ };
353
+ const read = async (nid) => {
354
+ const body = open.get(nid)?.body;
355
+ if (!body) return;
356
+ try {
357
+ const next = await body.next();
358
+ if (next.done) {
359
+ open.delete(nid);
360
+ peer.postMessage({ net: "end", nid });
361
+ } else {
362
+ const bytes = next.value.slice();
363
+ peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
364
+ }
365
+ } catch (e) {
366
+ fail(nid, e);
367
+ }
368
+ };
369
+ const drop = (nid) => {
370
+ const entry = open.get(nid);
371
+ open.delete(nid);
372
+ entry?.abort.abort();
373
+ void entry?.response?.cancel().catch(() => void 0);
374
+ };
375
+ return {
376
+ answer(call) {
377
+ if (call.net === "fetch") void start(call);
378
+ else if (call.net === "read") void read(call.nid);
379
+ else drop(call.nid);
380
+ },
381
+ close() {
382
+ for (const nid of [...open.keys()]) drop(nid);
383
+ }
384
+ };
385
+ }
386
+
387
+ // src/client/serve-client.ts
388
+ var MAX_LINKS = 40;
389
+ function act(session, req, signal) {
390
+ if (req.action === "write") session.write(req.bytes);
391
+ else if (req.action === "resize") session.resize(req.cols, req.rows);
392
+ else if (req.action === "signal") session.signal(signal(req.signal));
393
+ else session.close();
394
+ }
395
+ async function realpath(fs, path) {
396
+ let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
397
+ let resolved = "";
398
+ for (let hops = 0; parts.length > 0; ) {
399
+ const [name2, ...rest] = parts;
400
+ const next = `${resolved}/${name2}`;
401
+ if (!(await fs.lstat(next)).isSymbolicLink) {
402
+ resolved = next;
403
+ parts = rest;
404
+ continue;
405
+ }
406
+ if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
407
+ const target = await fs.readlink(next);
408
+ const base = target.startsWith("/") ? "/" : resolved || "/";
409
+ parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
410
+ resolved = "";
411
+ }
412
+ return resolved || "/";
413
+ }
414
+ async function fsCall(fs, method, args) {
415
+ const path = (i) => fs.resolvePath("/", String(args[i]));
416
+ switch (method) {
417
+ case "readFile": {
418
+ const bytes = (await fs.readFileBuffer(path(0))).slice();
419
+ return { result: bytes, transfer: [bytes.buffer] };
420
+ }
421
+ case "writeFile":
422
+ await fs.writeFile(path(0), args[1]);
423
+ return { result: true };
424
+ case "stat":
425
+ return { result: await fs.stat(path(0)) };
426
+ case "lstat":
427
+ return { result: await fs.lstat(path(0)) };
428
+ case "readdir":
429
+ return { result: await fs.readdir(path(0)) };
430
+ case "mkdir":
431
+ await fs.mkdir(path(0), { recursive: true });
432
+ return { result: true };
433
+ case "rm":
434
+ await fs.rm(path(0), { recursive: true, force: args[1] === true });
435
+ return { result: true };
436
+ case "rename":
437
+ await fs.rename(path(0), path(1));
438
+ return { result: true };
439
+ case "realpath":
440
+ return { result: await realpath(fs, String(args[0])) };
441
+ case "symlink":
442
+ await fs.symlink(String(args[0]), path(1));
443
+ return { result: true };
444
+ case "readlink":
445
+ return { result: await fs.readlink(path(0)) };
446
+ default:
447
+ return { result: await fs.exists(path(0)) };
448
+ }
449
+ }
450
+ function serveClient(port, host) {
451
+ let state = "new";
452
+ const terminals = /* @__PURE__ */ new Map();
453
+ const groups = /* @__PURE__ */ new Set();
454
+ let net;
455
+ const ended = Promise.withResolvers();
456
+ const send = (message2, transfer = []) => {
457
+ if (state !== "closed") port.postMessage(message2, transfer);
458
+ };
459
+ const reply = (id, body, transfer) => send({ id, ...body }, transfer);
460
+ function detach(kill = false, bye = "the slicc-kernel detached this client") {
461
+ if (state === "closed") return;
462
+ send({ bye });
463
+ state = "closed";
464
+ net?.close();
465
+ for (const session of terminals.values()) session.close();
466
+ terminals.clear();
467
+ const pids = [...groups];
468
+ groups.clear();
469
+ if (kill && pids.length > 0) {
470
+ void host.launcher().then((launcher) => {
471
+ for (const pid of pids) launcher.kill(-pid, SIG.KILL);
472
+ });
473
+ }
474
+ port.close?.();
475
+ ended.resolve();
476
+ }
477
+ async function hello(message2) {
478
+ const refused = versionError(message2.protocol);
479
+ if (refused) {
480
+ send({ hello: { protocol: PROTOCOL, error: refused } });
481
+ detach();
482
+ return;
483
+ }
484
+ const launcher = await host.launcher();
485
+ const transport = host.transport?.() ?? launcher.transport;
486
+ net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
487
+ state = "open";
488
+ if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
489
+ send({
490
+ hello: {
491
+ protocol: PROTOCOL,
492
+ traits: transport.traits,
493
+ ...host.lock ? { lock: host.lock } : {}
494
+ }
495
+ });
496
+ }
497
+ async function terminal(req, l) {
498
+ const session = await l.openTerminal(req.argv, {
499
+ ...req.options,
500
+ onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
501
+ });
502
+ if (state === "closed") {
503
+ session.close();
504
+ } else {
505
+ terminals.set(req.id, session);
506
+ reply(req.id, { started: session.pid });
507
+ }
508
+ try {
509
+ return { result: await session.exited };
510
+ } finally {
511
+ terminals.delete(req.id);
512
+ }
513
+ }
514
+ async function spawn(req, l) {
515
+ const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
516
+ let leader;
517
+ const result = await l.run(req.argv, {
518
+ ...req.options,
519
+ collect: false,
520
+ onStdout: out(1),
521
+ onStderr: out(2),
522
+ onStarted: (pid) => {
523
+ leader = pid;
524
+ if (state === "closed") return;
525
+ groups.add(pid);
526
+ reply(req.id, { started: pid });
527
+ }
528
+ });
529
+ if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
530
+ if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
531
+ return { result: result.status };
532
+ }
533
+ async function handle(req) {
534
+ const l = await host.launcher();
535
+ switch (req.op) {
536
+ case "spawn":
537
+ return spawn(req, l);
538
+ case "open-terminal":
539
+ return terminal(req, l);
540
+ case "terminal": {
541
+ const session = terminals.get(req.terminal);
542
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
543
+ act(session, req, host.signal);
544
+ return { result: true };
545
+ }
546
+ case "kill":
547
+ if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
548
+ return { result: true };
549
+ case "ps":
550
+ return {
551
+ result: l.list().map(({ tid: _, ...entry }) => entry)
552
+ };
553
+ case "fs":
554
+ if (!FS_METHODS.includes(req.method)) {
555
+ throw new Error(`unknown file system call ${req.method}`);
556
+ }
557
+ return fsCall(l.fs, req.method, req.args);
558
+ case "detach":
559
+ return { result: true };
560
+ default:
561
+ throw new Error(`unknown op ${req.op}`);
562
+ }
563
+ }
564
+ port.addEventListener("message", (event) => {
565
+ const data = event.data;
566
+ if (state === "closed") return;
567
+ if (data.hello) {
568
+ if (state === "new") {
569
+ hello(data.hello).catch((err) => {
570
+ send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
571
+ detach();
572
+ });
573
+ }
574
+ return;
575
+ }
576
+ if (state !== "open") return;
577
+ if (data.net !== void 0) {
578
+ net?.answer(data);
579
+ return;
580
+ }
581
+ const req = data;
582
+ handle(req).then(
583
+ ({ result, transfer }) => {
584
+ reply(req.id, { result }, transfer);
585
+ if (req.op === "detach") detach(req.kill === true);
586
+ },
587
+ (err) => {
588
+ const code = err?.code;
589
+ reply(req.id, {
590
+ error: err instanceof Error ? err.message : String(err),
591
+ ...typeof code === "string" ? { code } : {}
592
+ });
593
+ }
594
+ );
595
+ });
596
+ port.addEventListener("close", () => detach());
597
+ port.start?.();
598
+ return { detach, closed: ended.promise };
599
+ }
600
+
225
601
  // src/fs/opfs.ts
226
602
  var S_IFDIR = 16384;
227
603
  var S_IFREG = 32768;
228
604
  var S_IFLNK = 40960;
229
605
  var PERM_MASK = 4095;
230
606
  var READ_ATTEMPTS = 3;
231
- var MAX_LINKS = 40;
607
+ var MAX_LINKS2 = 40;
232
608
  var CHECK_MS = 1e3;
233
609
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
234
610
  var ERRNO_BY_DOM_ERROR = {
@@ -379,7 +755,7 @@ var OpfsFs = class {
379
755
  }
380
756
  async locate(path, follow) {
381
757
  let current = path;
382
- for (let hops = 0; hops <= MAX_LINKS; hops++) {
758
+ for (let hops = 0; hops <= MAX_LINKS2; hops++) {
383
759
  try {
384
760
  return { path: current, handle: await this.handle(current) };
385
761
  } catch (err) {
@@ -2412,6 +2788,7 @@ var RemoteTransport = class {
2412
2788
  port;
2413
2789
  waiting = /* @__PURE__ */ new Map();
2414
2790
  nextId = 0;
2791
+ failure;
2415
2792
  constructor(port, traits) {
2416
2793
  this.port = port;
2417
2794
  this.traits = traits;
@@ -2424,7 +2801,13 @@ var RemoteTransport = class {
2424
2801
  waiter.reject(new TransportError(reply.message, reply.status, reply.code));
2425
2802
  } else waiter.resolve(reply);
2426
2803
  }
2804
+ fail(error) {
2805
+ this.failure = error;
2806
+ for (const waiter of this.waiting.values()) waiter.reject(error);
2807
+ this.waiting.clear();
2808
+ }
2427
2809
  ask(call) {
2810
+ if (this.failure) return Promise.reject(this.failure);
2428
2811
  return new Promise((resolve, reject) => {
2429
2812
  this.waiting.set(call.nid, { resolve, reject });
2430
2813
  this.port.postMessage(call);
@@ -2502,61 +2885,6 @@ var RemoteTransport = class {
2502
2885
  }
2503
2886
  };
2504
2887
 
2505
- // src/kernel/signals.ts
2506
- var SIG = {
2507
- HUP: 1,
2508
- INT: 2,
2509
- QUIT: 3,
2510
- ILL: 4,
2511
- TRAP: 5,
2512
- ABRT: 6,
2513
- BUS: 7,
2514
- FPE: 8,
2515
- KILL: 9,
2516
- USR1: 10,
2517
- SEGV: 11,
2518
- USR2: 12,
2519
- PIPE: 13,
2520
- ALRM: 14,
2521
- TERM: 15,
2522
- STKFLT: 16,
2523
- CHLD: 17,
2524
- CONT: 18,
2525
- STOP: 19,
2526
- TSTP: 20,
2527
- TTIN: 21,
2528
- TTOU: 22,
2529
- URG: 23,
2530
- XCPU: 24,
2531
- XFSZ: 25,
2532
- VTALRM: 26,
2533
- PROF: 27,
2534
- WINCH: 28,
2535
- POLL: 29,
2536
- PWR: 30,
2537
- SYS: 31
2538
- };
2539
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
2540
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
2541
- function defaultAction(sig) {
2542
- if (sig === SIG.CONT) return "continue";
2543
- if (STOPPING.has(sig)) return "stop";
2544
- return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
2545
- }
2546
- function isSignal(sig) {
2547
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
2548
- }
2549
- function sigbit(sig) {
2550
- return 1 << sig;
2551
- }
2552
- var SIGNAL_BY_NAME = {
2553
- SIGINT: SIG.INT,
2554
- SIGTERM: SIG.TERM,
2555
- SIGKILL: SIG.KILL,
2556
- SIGSTOP: SIG.STOP,
2557
- SIGCONT: SIG.CONT
2558
- };
2559
-
2560
2888
  // src/commands.ts
2561
2889
  var NAME = /^[A-Za-z0-9._+-]+$/;
2562
2890
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -2687,7 +3015,7 @@ async function scanCommands(fs, modules) {
2687
3015
  var S_IFDIR2 = 16384;
2688
3016
  var S_IFREG2 = 32768;
2689
3017
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
2690
- var MAX_LINKS2 = 40;
3018
+ var MAX_LINKS3 = 40;
2691
3019
  function missing2(err) {
2692
3020
  return err?.code === "ENOENT";
2693
3021
  }
@@ -2707,7 +3035,7 @@ function synthetic(directory, path) {
2707
3035
  }
2708
3036
  async function followLinks(fs, path) {
2709
3037
  let current = path;
2710
- for (let hops = 0; hops < MAX_LINKS2; hops++) {
3038
+ for (let hops = 0; hops < MAX_LINKS3; hops++) {
2711
3039
  const st = await fs.lstat(current).catch(() => void 0);
2712
3040
  if (!st?.isSymbolicLink) return current;
2713
3041
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -3023,6 +3351,11 @@ function statJson(s) {
3023
3351
  async function readdirStat(fs, dir) {
3024
3352
  return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
3025
3353
  }
3354
+ async function storageUsage() {
3355
+ const estimate = await globalThis.navigator?.storage?.estimate?.();
3356
+ if (typeof estimate?.quota !== "number") return null;
3357
+ return { quota: estimate.quota, usage: estimate.usage ?? 0 };
3358
+ }
3026
3359
  var done = { ok: true, kind: "void" };
3027
3360
  function json(value) {
3028
3361
  return { ok: true, kind: "json", json: value };
@@ -3073,6 +3406,8 @@ async function run(fs, path, req, cwd) {
3073
3406
  case "utimes":
3074
3407
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
3075
3408
  return done;
3409
+ case "statfs":
3410
+ return json(await storageUsage());
3076
3411
  default:
3077
3412
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
3078
3413
  }
@@ -4705,6 +5040,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4705
5040
  "proc-captured",
4706
5041
  "proc-fork",
4707
5042
  "proc-kill",
5043
+ "proc-list",
4708
5044
  "proc-exec",
4709
5045
  "proc-setpgid",
4710
5046
  "proc-getpgid",
@@ -5153,6 +5489,12 @@ var WasmProcess = class {
5153
5489
  throw new KernelError("ESRCH");
5154
5490
  }
5155
5491
  return { ok: true, kind: "void" };
5492
+ case "proc-list":
5493
+ return {
5494
+ ok: true,
5495
+ kind: "json",
5496
+ json: this.options.processes?.() ?? { boot: 0, processes: [] }
5497
+ };
5156
5498
  case "proc-alarm":
5157
5499
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
5158
5500
  return { ok: true, kind: "void" };
@@ -5256,6 +5598,7 @@ function spawnWasmProcess(opts) {
5256
5598
  forker: opts.forker,
5257
5599
  fs: opts.fs,
5258
5600
  kill: opts.kill,
5601
+ processes: opts.processes,
5259
5602
  jobs: opts.jobs,
5260
5603
  ptys: opts.ptys,
5261
5604
  net: opts.net,
@@ -5400,6 +5743,9 @@ var JobTable = class {
5400
5743
  this.members.set(pid, member);
5401
5744
  return member;
5402
5745
  }
5746
+ list() {
5747
+ return [...this.members.values()];
5748
+ }
5403
5749
  remove(pid) {
5404
5750
  this.members.delete(pid);
5405
5751
  }
@@ -5730,6 +6076,7 @@ var Launcher = class {
5730
6076
  processes = /* @__PURE__ */ new Map();
5731
6077
  zombies = /* @__PURE__ */ new Set();
5732
6078
  orphans = /* @__PURE__ */ new Set();
6079
+ described = /* @__PURE__ */ new Map();
5733
6080
  jobs = new JobTable();
5734
6081
  locks = new LockTable();
5735
6082
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
@@ -5737,6 +6084,7 @@ var Launcher = class {
5737
6084
  ca;
5738
6085
  transport;
5739
6086
  nextPid = 1e3;
6087
+ boot = Date.now();
5740
6088
  terminals = 0;
5741
6089
  constructor(options) {
5742
6090
  this.base = options.fs;
@@ -5908,6 +6256,7 @@ var Launcher = class {
5908
6256
  spawner: this.spawner(pid, req.report),
5909
6257
  forker: this.forker(pid, req),
5910
6258
  kill: (target, sig) => this.kill(target, sig),
6259
+ processes: () => ({ boot: this.boot, processes: this.list() }),
5911
6260
  jobs: this.jobs,
5912
6261
  ptys: this.ptys,
5913
6262
  net: this.net,
@@ -5919,17 +6268,57 @@ var Launcher = class {
5919
6268
  });
5920
6269
  this.processes.set(pid, handle);
5921
6270
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
6271
+ this.described.set(pid, {
6272
+ argv: [req.argv0, ...req.args],
6273
+ tty: terminal?.name ?? null,
6274
+ started: Date.now()
6275
+ });
5922
6276
  void handle.exited.then(() => {
5923
6277
  this.processes.delete(pid);
5924
- if (req.ppid === void 0 || this.orphans.delete(pid)) this.jobs.remove(pid);
6278
+ if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
5925
6279
  else this.zombies.add(pid);
5926
6280
  });
5927
6281
  return handle;
5928
6282
  }
6283
+ forget(pid) {
6284
+ this.jobs.remove(pid);
6285
+ this.described.delete(pid);
6286
+ }
5929
6287
  reaped(pid) {
5930
- if (this.zombies.delete(pid)) this.jobs.remove(pid);
6288
+ if (this.zombies.delete(pid)) this.forget(pid);
5931
6289
  else this.orphans.add(pid);
5932
6290
  }
6291
+ list() {
6292
+ const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
6293
+ const rootOf = (member) => {
6294
+ let root = member;
6295
+ for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
6296
+ root = up;
6297
+ }
6298
+ return root;
6299
+ };
6300
+ const shown = (pid) => {
6301
+ const member = pid === void 0 ? void 0 : members.get(pid);
6302
+ return member ? rootOf(member).pid : pid ?? 0;
6303
+ };
6304
+ const listed = [];
6305
+ for (const member of members.values()) {
6306
+ const described = this.described.get(member.pid);
6307
+ if (member.execed || !described) continue;
6308
+ const root = rootOf(member);
6309
+ listed.push({
6310
+ pid: root.pid,
6311
+ tid: member.pid,
6312
+ ppid: shown(root.ppid),
6313
+ pgid: member.pgid,
6314
+ sid: member.sid,
6315
+ ...described,
6316
+ started: (this.described.get(root.pid) ?? described).started,
6317
+ state: this.processes.has(member.pid) ? "S" : "Z"
6318
+ });
6319
+ }
6320
+ return listed;
6321
+ }
5933
6322
  spawner(ppid, report) {
5934
6323
  return async (req, fds) => {
5935
6324
  const planned = await this.plan(req.file, req.argv, req.cwd);
@@ -6007,8 +6396,9 @@ var Launcher = class {
6007
6396
  const env = this.environment(cwd, options.env);
6008
6397
  const out = [];
6009
6398
  const err = [];
6399
+ const keep = options.collect !== false;
6010
6400
  const collect = (chunks, tee) => (bytes) => {
6011
- chunks.push(bytes);
6401
+ if (keep) chunks.push(bytes);
6012
6402
  tee?.(bytes);
6013
6403
  };
6014
6404
  const stdout = collect(out, options.onStdout);
@@ -6024,6 +6414,7 @@ var Launcher = class {
6024
6414
  fds.installAt(1, sinkFile(stdout));
6025
6415
  fds.installAt(2, sinkFile(stderr));
6026
6416
  const handle = await this.launch(planned, { env, cwd, fds, report });
6417
+ options.onStarted?.(handle.pid);
6027
6418
  const status = await handle.exited;
6028
6419
  return { status, stdout: concat4(out), stderr: concat4(err) };
6029
6420
  }
@@ -6036,7 +6427,7 @@ function signalNumber(name2) {
6036
6427
  throw new Error(`unknown signal ${name2}`);
6037
6428
  return SIG[key];
6038
6429
  }
6039
- function act(session, req) {
6430
+ function act2(session, req) {
6040
6431
  if (req.action === "write") session.write(req.bytes);
6041
6432
  else if (req.action === "resize") session.resize(req.cols, req.rows);
6042
6433
  else if (req.action === "signal") session.signal(signalNumber(req.signal));
@@ -6049,7 +6440,35 @@ function serveKernel(port, deps) {
6049
6440
  let launcher;
6050
6441
  let remote;
6051
6442
  const terminals = /* @__PURE__ */ new Map();
6052
- const reply = (id, body) => port.postMessage({ id, ...body });
6443
+ const reply = (id, body, transfer) => port.postMessage({ id, ...body }, transfer);
6444
+ const clients = /* @__PURE__ */ new Set();
6445
+ const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
6446
+ let holding;
6447
+ function holdLock(locks2) {
6448
+ holding ??= new Promise((resolve) => {
6449
+ const name2 = `slicc-kernel:${crypto.randomUUID()}`;
6450
+ void locks2.request(name2, () => {
6451
+ resolve(name2);
6452
+ return new Promise(() => {
6453
+ });
6454
+ });
6455
+ });
6456
+ return holding;
6457
+ }
6458
+ async function connect() {
6459
+ await ready2();
6460
+ const { port1, port2 } = new MessageChannel();
6461
+ const held = locks ? await holdLock(locks) : void 0;
6462
+ const served = serveClient(port1, {
6463
+ launcher: ready2,
6464
+ signal: signalNumber,
6465
+ ...locks ? { locks } : {},
6466
+ ...held ? { lock: held } : {}
6467
+ });
6468
+ clients.add(served);
6469
+ void served.closed.then(() => clients.delete(served));
6470
+ return port2;
6471
+ }
6053
6472
  async function ready2() {
6054
6473
  if (!launcher) throw new Error("the kernel is not initialized");
6055
6474
  return launcher;
@@ -6088,10 +6507,11 @@ function serveKernel(port, deps) {
6088
6507
  return true;
6089
6508
  }
6090
6509
  if (req.op === "open-terminal") return terminal(req);
6510
+ if (req.op === "connect") return connect();
6091
6511
  if (req.op === "terminal") {
6092
6512
  const session = terminals.get(req.terminal);
6093
6513
  if (!session) throw new Error(`no terminal ${req.terminal}`);
6094
- act(session, req);
6514
+ act2(session, req);
6095
6515
  return true;
6096
6516
  }
6097
6517
  return (await ready2()).run(req.argv, {
@@ -6107,7 +6527,7 @@ function serveKernel(port, deps) {
6107
6527
  }
6108
6528
  const req = event.data;
6109
6529
  handle(req).then(
6110
- (result) => reply(req.id, { result }),
6530
+ (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
6111
6531
  (err) => reply(req.id, { error: err instanceof Error ? err.message : String(err) })
6112
6532
  );
6113
6533
  });