@ai-ecoverse/slicc-kernel 1.8.12 → 1.10.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, 1];
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,378 @@ 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
+ const watches = /* @__PURE__ */ new Map();
455
+ let net;
456
+ const ended = Promise.withResolvers();
457
+ const send = (message2, transfer = []) => {
458
+ if (state !== "closed") port.postMessage(message2, transfer);
459
+ };
460
+ const reply = (id, body, transfer) => send({ id, ...body }, transfer);
461
+ function detach(kill = false, bye = "the slicc-kernel detached this client") {
462
+ if (state === "closed") return;
463
+ send({ bye });
464
+ state = "closed";
465
+ net?.close();
466
+ for (const unwatch of watches.values()) unwatch();
467
+ watches.clear();
468
+ for (const session of terminals.values()) session.close();
469
+ terminals.clear();
470
+ const pids = [...groups];
471
+ groups.clear();
472
+ if (kill && pids.length > 0) {
473
+ void host.launcher().then((launcher) => {
474
+ for (const pid of pids) launcher.kill(-pid, SIG.KILL);
475
+ });
476
+ }
477
+ port.close?.();
478
+ ended.resolve();
479
+ }
480
+ async function hello(message2) {
481
+ const refused = versionError(message2.protocol);
482
+ if (refused) {
483
+ send({ hello: { protocol: PROTOCOL, error: refused } });
484
+ detach();
485
+ return;
486
+ }
487
+ const launcher = await host.launcher();
488
+ const transport = host.transport?.() ?? launcher.transport;
489
+ net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
490
+ state = "open";
491
+ if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
492
+ send({
493
+ hello: {
494
+ protocol: PROTOCOL,
495
+ traits: transport.traits,
496
+ ...host.lock ? { lock: host.lock } : {}
497
+ }
498
+ });
499
+ }
500
+ async function terminal(req, l) {
501
+ const session = await l.openTerminal(req.argv, {
502
+ ...req.options,
503
+ onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
504
+ });
505
+ if (state === "closed") {
506
+ session.close();
507
+ } else {
508
+ terminals.set(req.id, session);
509
+ reply(req.id, { started: session.pid });
510
+ }
511
+ try {
512
+ return { result: await session.exited };
513
+ } finally {
514
+ terminals.delete(req.id);
515
+ }
516
+ }
517
+ async function spawn(req, l) {
518
+ const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
519
+ let leader;
520
+ const result = await l.run(req.argv, {
521
+ ...req.options,
522
+ collect: false,
523
+ onStdout: out(1),
524
+ onStderr: out(2),
525
+ onStarted: (pid) => {
526
+ leader = pid;
527
+ if (state === "closed") return;
528
+ groups.add(pid);
529
+ reply(req.id, { started: pid });
530
+ }
531
+ });
532
+ if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
533
+ if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
534
+ return { result: result.status };
535
+ }
536
+ async function handle(req) {
537
+ const l = await host.launcher();
538
+ switch (req.op) {
539
+ case "spawn":
540
+ return spawn(req, l);
541
+ case "open-terminal":
542
+ return terminal(req, l);
543
+ case "terminal": {
544
+ const session = terminals.get(req.terminal);
545
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
546
+ act(session, req, host.signal);
547
+ return { result: true };
548
+ }
549
+ case "kill":
550
+ if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
551
+ return { result: true };
552
+ case "ps":
553
+ return {
554
+ result: l.list().map(({ tid: _, ...entry }) => entry)
555
+ };
556
+ case "fs":
557
+ if (!FS_METHODS.includes(req.method)) {
558
+ throw new Error(`unknown file system call ${req.method}`);
559
+ }
560
+ return fsCall(l.fs, req.method, req.args);
561
+ case "watch": {
562
+ const unwatch = l.watchers.watch(
563
+ req.paths,
564
+ { recursive: req.recursive },
565
+ (change) => send({ watch: req.id, change })
566
+ );
567
+ watches.set(req.id, unwatch);
568
+ return { result: req.id };
569
+ }
570
+ case "unwatch":
571
+ watches.get(req.watch)?.();
572
+ watches.delete(req.watch);
573
+ return { result: true };
574
+ case "detach":
575
+ return { result: true };
576
+ default:
577
+ throw new Error(`unknown op ${req.op}`);
578
+ }
579
+ }
580
+ port.addEventListener("message", (event) => {
581
+ const data = event.data;
582
+ if (state === "closed") return;
583
+ if (data.hello) {
584
+ if (state === "new") {
585
+ hello(data.hello).catch((err) => {
586
+ send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
587
+ detach();
588
+ });
589
+ }
590
+ return;
591
+ }
592
+ if (state !== "open") return;
593
+ if (data.net !== void 0) {
594
+ net?.answer(data);
595
+ return;
596
+ }
597
+ const req = data;
598
+ handle(req).then(
599
+ ({ result, transfer }) => {
600
+ reply(req.id, { result }, transfer);
601
+ if (req.op === "detach") detach(req.kill === true);
602
+ },
603
+ (err) => {
604
+ const code = err?.code;
605
+ reply(req.id, {
606
+ error: err instanceof Error ? err.message : String(err),
607
+ ...typeof code === "string" ? { code } : {}
608
+ });
609
+ }
610
+ );
611
+ });
612
+ port.addEventListener("close", () => detach());
613
+ port.start?.();
614
+ return { detach, closed: ended.promise };
615
+ }
616
+
225
617
  // src/fs/opfs.ts
226
618
  var S_IFDIR = 16384;
227
619
  var S_IFREG = 32768;
228
620
  var S_IFLNK = 40960;
229
621
  var PERM_MASK = 4095;
230
622
  var READ_ATTEMPTS = 3;
231
- var MAX_LINKS = 40;
623
+ var MAX_LINKS2 = 40;
232
624
  var CHECK_MS = 1e3;
233
625
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
234
626
  var ERRNO_BY_DOM_ERROR = {
@@ -379,7 +771,7 @@ var OpfsFs = class {
379
771
  }
380
772
  async locate(path, follow) {
381
773
  let current = path;
382
- for (let hops = 0; hops <= MAX_LINKS; hops++) {
774
+ for (let hops = 0; hops <= MAX_LINKS2; hops++) {
383
775
  try {
384
776
  return { path: current, handle: await this.handle(current) };
385
777
  } catch (err) {
@@ -2412,6 +2804,7 @@ var RemoteTransport = class {
2412
2804
  port;
2413
2805
  waiting = /* @__PURE__ */ new Map();
2414
2806
  nextId = 0;
2807
+ failure;
2415
2808
  constructor(port, traits) {
2416
2809
  this.port = port;
2417
2810
  this.traits = traits;
@@ -2424,7 +2817,13 @@ var RemoteTransport = class {
2424
2817
  waiter.reject(new TransportError(reply.message, reply.status, reply.code));
2425
2818
  } else waiter.resolve(reply);
2426
2819
  }
2820
+ fail(error) {
2821
+ this.failure = error;
2822
+ for (const waiter of this.waiting.values()) waiter.reject(error);
2823
+ this.waiting.clear();
2824
+ }
2427
2825
  ask(call) {
2826
+ if (this.failure) return Promise.reject(this.failure);
2428
2827
  return new Promise((resolve, reject) => {
2429
2828
  this.waiting.set(call.nid, { resolve, reject });
2430
2829
  this.port.postMessage(call);
@@ -2502,61 +2901,6 @@ var RemoteTransport = class {
2502
2901
  }
2503
2902
  };
2504
2903
 
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
2904
  // src/commands.ts
2561
2905
  var NAME = /^[A-Za-z0-9._+-]+$/;
2562
2906
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -2687,7 +3031,7 @@ async function scanCommands(fs, modules) {
2687
3031
  var S_IFDIR2 = 16384;
2688
3032
  var S_IFREG2 = 32768;
2689
3033
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
2690
- var MAX_LINKS2 = 40;
3034
+ var MAX_LINKS3 = 40;
2691
3035
  function missing2(err) {
2692
3036
  return err?.code === "ENOENT";
2693
3037
  }
@@ -2707,7 +3051,7 @@ function synthetic(directory, path) {
2707
3051
  }
2708
3052
  async function followLinks(fs, path) {
2709
3053
  let current = path;
2710
- for (let hops = 0; hops < MAX_LINKS2; hops++) {
3054
+ for (let hops = 0; hops < MAX_LINKS3; hops++) {
2711
3055
  const st = await fs.lstat(current).catch(() => void 0);
2712
3056
  if (!st?.isSymbolicLink) return current;
2713
3057
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -2795,6 +3139,89 @@ function withCommandDirs(fs, names) {
2795
3139
  };
2796
3140
  }
2797
3141
 
3142
+ // src/fs/watch.ts
3143
+ var WATCH_BATCH_LIMIT = 1e3;
3144
+ var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
3145
+ var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3146
+ function touches(watcher, changed) {
3147
+ return watcher.paths.some(
3148
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
3149
+ );
3150
+ }
3151
+ var FsWatchers = class {
3152
+ watchers = /* @__PURE__ */ new Set();
3153
+ watch(paths, options, notify) {
3154
+ const watcher = {
3155
+ paths: paths.map(normalizePath),
3156
+ recursive: options.recursive === true,
3157
+ notify,
3158
+ pending: /* @__PURE__ */ new Set(),
3159
+ scheduled: false
3160
+ };
3161
+ this.watchers.add(watcher);
3162
+ return () => {
3163
+ this.watchers.delete(watcher);
3164
+ };
3165
+ }
3166
+ changed(...paths) {
3167
+ for (const watcher of this.watchers) {
3168
+ for (const path of paths) {
3169
+ const changed = normalizePath(path);
3170
+ if (touches(watcher, changed)) watcher.pending.add(changed);
3171
+ }
3172
+ if (watcher.pending.size === 0 || watcher.scheduled) continue;
3173
+ watcher.scheduled = true;
3174
+ setTimeout(() => this.flush(watcher), 0);
3175
+ }
3176
+ }
3177
+ flush(watcher) {
3178
+ watcher.scheduled = false;
3179
+ const paths = [...watcher.pending];
3180
+ watcher.pending.clear();
3181
+ if (!this.watchers.has(watcher)) return;
3182
+ watcher.notify(paths.length > WATCH_BATCH_LIMIT ? { overflow: true } : { paths });
3183
+ }
3184
+ wrap(fs) {
3185
+ const after = (run2, changed) => async (...args) => {
3186
+ try {
3187
+ await run2(...args);
3188
+ } finally {
3189
+ this.changed(...changed(...args));
3190
+ }
3191
+ };
3192
+ const overrides = {
3193
+ writeFile: after(fs.writeFile.bind(fs), (path) => [path]),
3194
+ mkdir: async (path, options) => {
3195
+ const created = [];
3196
+ if (options?.recursive) {
3197
+ const parts = normalizePath(path).split("/").filter(Boolean);
3198
+ for (let at = 1; at < parts.length; at++) {
3199
+ const ancestor = `/${parts.slice(0, at).join("/")}`;
3200
+ if (created.length > 0 || !await fs.exists(ancestor)) created.push(ancestor);
3201
+ }
3202
+ }
3203
+ try {
3204
+ await fs.mkdir(path, options);
3205
+ } finally {
3206
+ this.changed(...created, path);
3207
+ }
3208
+ },
3209
+ rm: after(fs.rm.bind(fs), (path) => [path]),
3210
+ rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
3211
+ symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
3212
+ chmod: after(fs.chmod.bind(fs), (path) => [path]),
3213
+ utimes: after(fs.utimes.bind(fs), (path) => [path])
3214
+ };
3215
+ return new Proxy(fs, {
3216
+ get(target, key, receiver) {
3217
+ if (Object.hasOwn(overrides, key)) return overrides[key];
3218
+ const value = Reflect.get(target, key, receiver);
3219
+ return typeof value === "function" ? value.bind(target) : value;
3220
+ }
3221
+ });
3222
+ }
3223
+ };
3224
+
2798
3225
  // src/kernel/children.ts
2799
3226
  var SpawnError = class extends Error {
2800
3227
  code;
@@ -4712,6 +5139,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4712
5139
  "proc-captured",
4713
5140
  "proc-fork",
4714
5141
  "proc-kill",
5142
+ "proc-list",
4715
5143
  "proc-exec",
4716
5144
  "proc-setpgid",
4717
5145
  "proc-getpgid",
@@ -5160,6 +5588,12 @@ var WasmProcess = class {
5160
5588
  throw new KernelError("ESRCH");
5161
5589
  }
5162
5590
  return { ok: true, kind: "void" };
5591
+ case "proc-list":
5592
+ return {
5593
+ ok: true,
5594
+ kind: "json",
5595
+ json: this.options.processes?.() ?? { boot: 0, processes: [] }
5596
+ };
5163
5597
  case "proc-alarm":
5164
5598
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
5165
5599
  return { ok: true, kind: "void" };
@@ -5263,6 +5697,7 @@ function spawnWasmProcess(opts) {
5263
5697
  forker: opts.forker,
5264
5698
  fs: opts.fs,
5265
5699
  kill: opts.kill,
5700
+ processes: opts.processes,
5266
5701
  jobs: opts.jobs,
5267
5702
  ptys: opts.ptys,
5268
5703
  net: opts.net,
@@ -5407,6 +5842,9 @@ var JobTable = class {
5407
5842
  this.members.set(pid, member);
5408
5843
  return member;
5409
5844
  }
5845
+ list() {
5846
+ return [...this.members.values()];
5847
+ }
5410
5848
  remove(pid) {
5411
5849
  this.members.delete(pid);
5412
5850
  }
@@ -5737,16 +6175,19 @@ var Launcher = class {
5737
6175
  processes = /* @__PURE__ */ new Map();
5738
6176
  zombies = /* @__PURE__ */ new Set();
5739
6177
  orphans = /* @__PURE__ */ new Set();
6178
+ described = /* @__PURE__ */ new Map();
5740
6179
  jobs = new JobTable();
5741
6180
  locks = new LockTable();
5742
6181
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
5743
6182
  net = new LoopbackNet();
5744
6183
  ca;
5745
6184
  transport;
6185
+ watchers = new FsWatchers();
5746
6186
  nextPid = 1e3;
6187
+ boot = Date.now();
5747
6188
  terminals = 0;
5748
6189
  constructor(options) {
5749
- this.base = options.fs;
6190
+ this.base = this.watchers.wrap(options.fs);
5750
6191
  this.createWorker = options.createWorker;
5751
6192
  this.modulesDir = options.modules ?? "/node_modules";
5752
6193
  this.env = { ...networkEnv(), ...options.env };
@@ -5757,7 +6198,7 @@ var Launcher = class {
5757
6198
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
5758
6199
  ca: this.ca
5759
6200
  });
5760
- this.fs = withCommandDirs(options.fs, async () => new Set((await this.commands()).keys()));
6201
+ this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
5761
6202
  }
5762
6203
  commands() {
5763
6204
  this.catalog ??= scanCommands(this.base, this.modulesDir);
@@ -5915,6 +6356,7 @@ var Launcher = class {
5915
6356
  spawner: this.spawner(pid, req.report),
5916
6357
  forker: this.forker(pid, req),
5917
6358
  kill: (target, sig) => this.kill(target, sig),
6359
+ processes: () => ({ boot: this.boot, processes: this.list() }),
5918
6360
  jobs: this.jobs,
5919
6361
  ptys: this.ptys,
5920
6362
  net: this.net,
@@ -5926,17 +6368,57 @@ var Launcher = class {
5926
6368
  });
5927
6369
  this.processes.set(pid, handle);
5928
6370
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
6371
+ this.described.set(pid, {
6372
+ argv: [req.argv0, ...req.args],
6373
+ tty: terminal?.name ?? null,
6374
+ started: Date.now()
6375
+ });
5929
6376
  void handle.exited.then(() => {
5930
6377
  this.processes.delete(pid);
5931
- if (req.ppid === void 0 || this.orphans.delete(pid)) this.jobs.remove(pid);
6378
+ if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
5932
6379
  else this.zombies.add(pid);
5933
6380
  });
5934
6381
  return handle;
5935
6382
  }
6383
+ forget(pid) {
6384
+ this.jobs.remove(pid);
6385
+ this.described.delete(pid);
6386
+ }
5936
6387
  reaped(pid) {
5937
- if (this.zombies.delete(pid)) this.jobs.remove(pid);
6388
+ if (this.zombies.delete(pid)) this.forget(pid);
5938
6389
  else this.orphans.add(pid);
5939
6390
  }
6391
+ list() {
6392
+ const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
6393
+ const rootOf = (member) => {
6394
+ let root = member;
6395
+ for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
6396
+ root = up;
6397
+ }
6398
+ return root;
6399
+ };
6400
+ const shown = (pid) => {
6401
+ const member = pid === void 0 ? void 0 : members.get(pid);
6402
+ return member ? rootOf(member).pid : pid ?? 0;
6403
+ };
6404
+ const listed = [];
6405
+ for (const member of members.values()) {
6406
+ const described = this.described.get(member.pid);
6407
+ if (member.execed || !described) continue;
6408
+ const root = rootOf(member);
6409
+ listed.push({
6410
+ pid: root.pid,
6411
+ tid: member.pid,
6412
+ ppid: shown(root.ppid),
6413
+ pgid: member.pgid,
6414
+ sid: member.sid,
6415
+ ...described,
6416
+ started: (this.described.get(root.pid) ?? described).started,
6417
+ state: this.processes.has(member.pid) ? "S" : "Z"
6418
+ });
6419
+ }
6420
+ return listed;
6421
+ }
5940
6422
  spawner(ppid, report) {
5941
6423
  return async (req, fds) => {
5942
6424
  const planned = await this.plan(req.file, req.argv, req.cwd);
@@ -6014,8 +6496,9 @@ var Launcher = class {
6014
6496
  const env = this.environment(cwd, options.env);
6015
6497
  const out = [];
6016
6498
  const err = [];
6499
+ const keep = options.collect !== false;
6017
6500
  const collect = (chunks, tee) => (bytes) => {
6018
- chunks.push(bytes);
6501
+ if (keep) chunks.push(bytes);
6019
6502
  tee?.(bytes);
6020
6503
  };
6021
6504
  const stdout = collect(out, options.onStdout);
@@ -6031,6 +6514,7 @@ var Launcher = class {
6031
6514
  fds.installAt(1, sinkFile(stdout));
6032
6515
  fds.installAt(2, sinkFile(stderr));
6033
6516
  const handle = await this.launch(planned, { env, cwd, fds, report });
6517
+ options.onStarted?.(handle.pid);
6034
6518
  const status = await handle.exited;
6035
6519
  return { status, stdout: concat4(out), stderr: concat4(err) };
6036
6520
  }
@@ -6043,7 +6527,7 @@ function signalNumber(name2) {
6043
6527
  throw new Error(`unknown signal ${name2}`);
6044
6528
  return SIG[key];
6045
6529
  }
6046
- function act(session, req) {
6530
+ function act2(session, req) {
6047
6531
  if (req.action === "write") session.write(req.bytes);
6048
6532
  else if (req.action === "resize") session.resize(req.cols, req.rows);
6049
6533
  else if (req.action === "signal") session.signal(signalNumber(req.signal));
@@ -6056,7 +6540,35 @@ function serveKernel(port, deps) {
6056
6540
  let launcher;
6057
6541
  let remote;
6058
6542
  const terminals = /* @__PURE__ */ new Map();
6059
- const reply = (id, body) => port.postMessage({ id, ...body });
6543
+ const reply = (id, body, transfer) => port.postMessage({ id, ...body }, transfer);
6544
+ const clients = /* @__PURE__ */ new Set();
6545
+ const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
6546
+ let holding;
6547
+ function holdLock(locks2) {
6548
+ holding ??= new Promise((resolve) => {
6549
+ const name2 = `slicc-kernel:${crypto.randomUUID()}`;
6550
+ void locks2.request(name2, () => {
6551
+ resolve(name2);
6552
+ return new Promise(() => {
6553
+ });
6554
+ });
6555
+ });
6556
+ return holding;
6557
+ }
6558
+ async function connect() {
6559
+ await ready2();
6560
+ const { port1, port2 } = new MessageChannel();
6561
+ const held = locks ? await holdLock(locks) : void 0;
6562
+ const served = serveClient(port1, {
6563
+ launcher: ready2,
6564
+ signal: signalNumber,
6565
+ ...locks ? { locks } : {},
6566
+ ...held ? { lock: held } : {}
6567
+ });
6568
+ clients.add(served);
6569
+ void served.closed.then(() => clients.delete(served));
6570
+ return port2;
6571
+ }
6060
6572
  async function ready2() {
6061
6573
  if (!launcher) throw new Error("the kernel is not initialized");
6062
6574
  return launcher;
@@ -6095,10 +6607,11 @@ function serveKernel(port, deps) {
6095
6607
  return true;
6096
6608
  }
6097
6609
  if (req.op === "open-terminal") return terminal(req);
6610
+ if (req.op === "connect") return connect();
6098
6611
  if (req.op === "terminal") {
6099
6612
  const session = terminals.get(req.terminal);
6100
6613
  if (!session) throw new Error(`no terminal ${req.terminal}`);
6101
- act(session, req);
6614
+ act2(session, req);
6102
6615
  return true;
6103
6616
  }
6104
6617
  return (await ready2()).run(req.argv, {
@@ -6114,7 +6627,7 @@ function serveKernel(port, deps) {
6114
6627
  }
6115
6628
  const req = event.data;
6116
6629
  handle(req).then(
6117
- (result) => reply(req.id, { result }),
6630
+ (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
6118
6631
  (err) => reply(req.id, { error: err instanceof Error ? err.message : String(err) })
6119
6632
  );
6120
6633
  });