@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.
@@ -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("/")) || "/";
@@ -4712,6 +5040,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4712
5040
  "proc-captured",
4713
5041
  "proc-fork",
4714
5042
  "proc-kill",
5043
+ "proc-list",
4715
5044
  "proc-exec",
4716
5045
  "proc-setpgid",
4717
5046
  "proc-getpgid",
@@ -5160,6 +5489,12 @@ var WasmProcess = class {
5160
5489
  throw new KernelError("ESRCH");
5161
5490
  }
5162
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
+ };
5163
5498
  case "proc-alarm":
5164
5499
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
5165
5500
  return { ok: true, kind: "void" };
@@ -5263,6 +5598,7 @@ function spawnWasmProcess(opts) {
5263
5598
  forker: opts.forker,
5264
5599
  fs: opts.fs,
5265
5600
  kill: opts.kill,
5601
+ processes: opts.processes,
5266
5602
  jobs: opts.jobs,
5267
5603
  ptys: opts.ptys,
5268
5604
  net: opts.net,
@@ -5407,6 +5743,9 @@ var JobTable = class {
5407
5743
  this.members.set(pid, member);
5408
5744
  return member;
5409
5745
  }
5746
+ list() {
5747
+ return [...this.members.values()];
5748
+ }
5410
5749
  remove(pid) {
5411
5750
  this.members.delete(pid);
5412
5751
  }
@@ -5737,6 +6076,7 @@ var Launcher = class {
5737
6076
  processes = /* @__PURE__ */ new Map();
5738
6077
  zombies = /* @__PURE__ */ new Set();
5739
6078
  orphans = /* @__PURE__ */ new Set();
6079
+ described = /* @__PURE__ */ new Map();
5740
6080
  jobs = new JobTable();
5741
6081
  locks = new LockTable();
5742
6082
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
@@ -5744,6 +6084,7 @@ var Launcher = class {
5744
6084
  ca;
5745
6085
  transport;
5746
6086
  nextPid = 1e3;
6087
+ boot = Date.now();
5747
6088
  terminals = 0;
5748
6089
  constructor(options) {
5749
6090
  this.base = options.fs;
@@ -5915,6 +6256,7 @@ var Launcher = class {
5915
6256
  spawner: this.spawner(pid, req.report),
5916
6257
  forker: this.forker(pid, req),
5917
6258
  kill: (target, sig) => this.kill(target, sig),
6259
+ processes: () => ({ boot: this.boot, processes: this.list() }),
5918
6260
  jobs: this.jobs,
5919
6261
  ptys: this.ptys,
5920
6262
  net: this.net,
@@ -5926,17 +6268,57 @@ var Launcher = class {
5926
6268
  });
5927
6269
  this.processes.set(pid, handle);
5928
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
+ });
5929
6276
  void handle.exited.then(() => {
5930
6277
  this.processes.delete(pid);
5931
- 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);
5932
6279
  else this.zombies.add(pid);
5933
6280
  });
5934
6281
  return handle;
5935
6282
  }
6283
+ forget(pid) {
6284
+ this.jobs.remove(pid);
6285
+ this.described.delete(pid);
6286
+ }
5936
6287
  reaped(pid) {
5937
- if (this.zombies.delete(pid)) this.jobs.remove(pid);
6288
+ if (this.zombies.delete(pid)) this.forget(pid);
5938
6289
  else this.orphans.add(pid);
5939
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
+ }
5940
6322
  spawner(ppid, report) {
5941
6323
  return async (req, fds) => {
5942
6324
  const planned = await this.plan(req.file, req.argv, req.cwd);
@@ -6014,8 +6396,9 @@ var Launcher = class {
6014
6396
  const env = this.environment(cwd, options.env);
6015
6397
  const out = [];
6016
6398
  const err = [];
6399
+ const keep = options.collect !== false;
6017
6400
  const collect = (chunks, tee) => (bytes) => {
6018
- chunks.push(bytes);
6401
+ if (keep) chunks.push(bytes);
6019
6402
  tee?.(bytes);
6020
6403
  };
6021
6404
  const stdout = collect(out, options.onStdout);
@@ -6031,6 +6414,7 @@ var Launcher = class {
6031
6414
  fds.installAt(1, sinkFile(stdout));
6032
6415
  fds.installAt(2, sinkFile(stderr));
6033
6416
  const handle = await this.launch(planned, { env, cwd, fds, report });
6417
+ options.onStarted?.(handle.pid);
6034
6418
  const status = await handle.exited;
6035
6419
  return { status, stdout: concat4(out), stderr: concat4(err) };
6036
6420
  }
@@ -6043,7 +6427,7 @@ function signalNumber(name2) {
6043
6427
  throw new Error(`unknown signal ${name2}`);
6044
6428
  return SIG[key];
6045
6429
  }
6046
- function act(session, req) {
6430
+ function act2(session, req) {
6047
6431
  if (req.action === "write") session.write(req.bytes);
6048
6432
  else if (req.action === "resize") session.resize(req.cols, req.rows);
6049
6433
  else if (req.action === "signal") session.signal(signalNumber(req.signal));
@@ -6056,7 +6440,35 @@ function serveKernel(port, deps) {
6056
6440
  let launcher;
6057
6441
  let remote;
6058
6442
  const terminals = /* @__PURE__ */ new Map();
6059
- 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
+ }
6060
6472
  async function ready2() {
6061
6473
  if (!launcher) throw new Error("the kernel is not initialized");
6062
6474
  return launcher;
@@ -6095,10 +6507,11 @@ function serveKernel(port, deps) {
6095
6507
  return true;
6096
6508
  }
6097
6509
  if (req.op === "open-terminal") return terminal(req);
6510
+ if (req.op === "connect") return connect();
6098
6511
  if (req.op === "terminal") {
6099
6512
  const session = terminals.get(req.terminal);
6100
6513
  if (!session) throw new Error(`no terminal ${req.terminal}`);
6101
- act(session, req);
6514
+ act2(session, req);
6102
6515
  return true;
6103
6516
  }
6104
6517
  return (await ready2()).run(req.argv, {
@@ -6114,7 +6527,7 @@ function serveKernel(port, deps) {
6114
6527
  }
6115
6528
  const req = event.data;
6116
6529
  handle(req).then(
6117
- (result) => reply(req.id, { result }),
6530
+ (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
6118
6531
  (err) => reply(req.id, { error: err instanceof Error ? err.message : String(err) })
6119
6532
  );
6120
6533
  });