@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.
package/dist/node.js CHANGED
@@ -1,5 +1,512 @@
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, 1];
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
+ const watches = /* @__PURE__ */ new Map();
349
+ let net;
350
+ const ended = Promise.withResolvers();
351
+ const send = (message2, transfer = []) => {
352
+ if (state !== "closed") port.postMessage(message2, transfer);
353
+ };
354
+ const reply = (id, body, transfer) => send({ id, ...body }, transfer);
355
+ function detach(kill = false, bye = "the slicc-kernel detached this client") {
356
+ if (state === "closed") return;
357
+ send({ bye });
358
+ state = "closed";
359
+ net?.close();
360
+ for (const unwatch of watches.values()) unwatch();
361
+ watches.clear();
362
+ for (const session of terminals.values()) session.close();
363
+ terminals.clear();
364
+ const pids = [...groups];
365
+ groups.clear();
366
+ if (kill && pids.length > 0) {
367
+ void host.launcher().then((launcher) => {
368
+ for (const pid of pids) launcher.kill(-pid, SIG.KILL);
369
+ });
370
+ }
371
+ port.close?.();
372
+ ended.resolve();
373
+ }
374
+ async function hello(message2) {
375
+ const refused = versionError(message2.protocol);
376
+ if (refused) {
377
+ send({ hello: { protocol: PROTOCOL, error: refused } });
378
+ detach();
379
+ return;
380
+ }
381
+ const launcher = await host.launcher();
382
+ const transport = host.transport?.() ?? launcher.transport;
383
+ net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
384
+ state = "open";
385
+ if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
386
+ send({
387
+ hello: {
388
+ protocol: PROTOCOL,
389
+ traits: transport.traits,
390
+ ...host.lock ? { lock: host.lock } : {}
391
+ }
392
+ });
393
+ }
394
+ async function terminal(req, l) {
395
+ const session = await l.openTerminal(req.argv, {
396
+ ...req.options,
397
+ onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
398
+ });
399
+ if (state === "closed") {
400
+ session.close();
401
+ } else {
402
+ terminals.set(req.id, session);
403
+ reply(req.id, { started: session.pid });
404
+ }
405
+ try {
406
+ return { result: await session.exited };
407
+ } finally {
408
+ terminals.delete(req.id);
409
+ }
410
+ }
411
+ async function spawn(req, l) {
412
+ const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
413
+ let leader;
414
+ const result = await l.run(req.argv, {
415
+ ...req.options,
416
+ collect: false,
417
+ onStdout: out(1),
418
+ onStderr: out(2),
419
+ onStarted: (pid) => {
420
+ leader = pid;
421
+ if (state === "closed") return;
422
+ groups.add(pid);
423
+ reply(req.id, { started: pid });
424
+ }
425
+ });
426
+ if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
427
+ if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
428
+ return { result: result.status };
429
+ }
430
+ async function handle(req) {
431
+ const l = await host.launcher();
432
+ switch (req.op) {
433
+ case "spawn":
434
+ return spawn(req, l);
435
+ case "open-terminal":
436
+ return terminal(req, l);
437
+ case "terminal": {
438
+ const session = terminals.get(req.terminal);
439
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
440
+ act(session, req, host.signal);
441
+ return { result: true };
442
+ }
443
+ case "kill":
444
+ if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
445
+ return { result: true };
446
+ case "ps":
447
+ return {
448
+ result: l.list().map(({ tid: _, ...entry }) => entry)
449
+ };
450
+ case "fs":
451
+ if (!FS_METHODS.includes(req.method)) {
452
+ throw new Error(`unknown file system call ${req.method}`);
453
+ }
454
+ return fsCall(l.fs, req.method, req.args);
455
+ case "watch": {
456
+ const unwatch = l.watchers.watch(
457
+ req.paths,
458
+ { recursive: req.recursive },
459
+ (change) => send({ watch: req.id, change })
460
+ );
461
+ watches.set(req.id, unwatch);
462
+ return { result: req.id };
463
+ }
464
+ case "unwatch":
465
+ watches.get(req.watch)?.();
466
+ watches.delete(req.watch);
467
+ return { result: true };
468
+ case "detach":
469
+ return { result: true };
470
+ default:
471
+ throw new Error(`unknown op ${req.op}`);
472
+ }
473
+ }
474
+ port.addEventListener("message", (event) => {
475
+ const data = event.data;
476
+ if (state === "closed") return;
477
+ if (data.hello) {
478
+ if (state === "new") {
479
+ hello(data.hello).catch((err) => {
480
+ send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
481
+ detach();
482
+ });
483
+ }
484
+ return;
485
+ }
486
+ if (state !== "open") return;
487
+ if (data.net !== void 0) {
488
+ net?.answer(data);
489
+ return;
490
+ }
491
+ const req = data;
492
+ handle(req).then(
493
+ ({ result, transfer }) => {
494
+ reply(req.id, { result }, transfer);
495
+ if (req.op === "detach") detach(req.kill === true);
496
+ },
497
+ (err) => {
498
+ const code = err?.code;
499
+ reply(req.id, {
500
+ error: err instanceof Error ? err.message : String(err),
501
+ ...typeof code === "string" ? { code } : {}
502
+ });
503
+ }
504
+ );
505
+ });
506
+ port.addEventListener("close", () => detach());
507
+ port.start?.();
508
+ return { detach, closed: ended.promise };
509
+ }
3
510
 
4
511
  // src/fs/meta.ts
5
512
  function parentOf(path) {
@@ -49,42 +556,13 @@ var MemoryMeta = class {
49
556
  }
50
557
  };
51
558
 
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);
62
- }
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);
75
- }
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
- }
80
-
81
559
  // src/fs/opfs.ts
82
560
  var S_IFDIR = 16384;
83
561
  var S_IFREG = 32768;
84
562
  var S_IFLNK = 40960;
85
563
  var PERM_MASK = 4095;
86
564
  var READ_ATTEMPTS = 3;
87
- var MAX_LINKS = 40;
565
+ var MAX_LINKS2 = 40;
88
566
  var CHECK_MS = 1e3;
89
567
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
90
568
  var ERRNO_BY_DOM_ERROR = {
@@ -235,7 +713,7 @@ var OpfsFs = class {
235
713
  }
236
714
  async locate(path, follow) {
237
715
  let current = path;
238
- for (let hops = 0; hops <= MAX_LINKS; hops++) {
716
+ for (let hops = 0; hops <= MAX_LINKS2; hops++) {
239
717
  try {
240
718
  return { path: current, handle: await this.handle(current) };
241
719
  } catch (err) {
@@ -699,7 +1177,7 @@ async function scanCommands(fs, modules) {
699
1177
  var S_IFDIR2 = 16384;
700
1178
  var S_IFREG2 = 32768;
701
1179
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
702
- var MAX_LINKS2 = 40;
1180
+ var MAX_LINKS3 = 40;
703
1181
  function missing2(err) {
704
1182
  return err?.code === "ENOENT";
705
1183
  }
@@ -719,7 +1197,7 @@ function synthetic(directory, path) {
719
1197
  }
720
1198
  async function followLinks(fs, path) {
721
1199
  let current = path;
722
- for (let hops = 0; hops < MAX_LINKS2; hops++) {
1200
+ for (let hops = 0; hops < MAX_LINKS3; hops++) {
723
1201
  const st = await fs.lstat(current).catch(() => void 0);
724
1202
  if (!st?.isSymbolicLink) return current;
725
1203
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -807,6 +1285,89 @@ function withCommandDirs(fs, names) {
807
1285
  };
808
1286
  }
809
1287
 
1288
+ // src/fs/watch.ts
1289
+ var WATCH_BATCH_LIMIT = 1e3;
1290
+ var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
1291
+ var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
1292
+ function touches(watcher, changed) {
1293
+ return watcher.paths.some(
1294
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
1295
+ );
1296
+ }
1297
+ var FsWatchers = class {
1298
+ watchers = /* @__PURE__ */ new Set();
1299
+ watch(paths, options, notify) {
1300
+ const watcher = {
1301
+ paths: paths.map(normalizePath),
1302
+ recursive: options.recursive === true,
1303
+ notify,
1304
+ pending: /* @__PURE__ */ new Set(),
1305
+ scheduled: false
1306
+ };
1307
+ this.watchers.add(watcher);
1308
+ return () => {
1309
+ this.watchers.delete(watcher);
1310
+ };
1311
+ }
1312
+ changed(...paths) {
1313
+ for (const watcher of this.watchers) {
1314
+ for (const path of paths) {
1315
+ const changed = normalizePath(path);
1316
+ if (touches(watcher, changed)) watcher.pending.add(changed);
1317
+ }
1318
+ if (watcher.pending.size === 0 || watcher.scheduled) continue;
1319
+ watcher.scheduled = true;
1320
+ setTimeout(() => this.flush(watcher), 0);
1321
+ }
1322
+ }
1323
+ flush(watcher) {
1324
+ watcher.scheduled = false;
1325
+ const paths = [...watcher.pending];
1326
+ watcher.pending.clear();
1327
+ if (!this.watchers.has(watcher)) return;
1328
+ watcher.notify(paths.length > WATCH_BATCH_LIMIT ? { overflow: true } : { paths });
1329
+ }
1330
+ wrap(fs) {
1331
+ const after = (run2, changed) => async (...args) => {
1332
+ try {
1333
+ await run2(...args);
1334
+ } finally {
1335
+ this.changed(...changed(...args));
1336
+ }
1337
+ };
1338
+ const overrides = {
1339
+ writeFile: after(fs.writeFile.bind(fs), (path) => [path]),
1340
+ mkdir: async (path, options) => {
1341
+ const created = [];
1342
+ if (options?.recursive) {
1343
+ const parts = normalizePath(path).split("/").filter(Boolean);
1344
+ for (let at = 1; at < parts.length; at++) {
1345
+ const ancestor = `/${parts.slice(0, at).join("/")}`;
1346
+ if (created.length > 0 || !await fs.exists(ancestor)) created.push(ancestor);
1347
+ }
1348
+ }
1349
+ try {
1350
+ await fs.mkdir(path, options);
1351
+ } finally {
1352
+ this.changed(...created, path);
1353
+ }
1354
+ },
1355
+ rm: after(fs.rm.bind(fs), (path) => [path]),
1356
+ rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
1357
+ symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
1358
+ chmod: after(fs.chmod.bind(fs), (path) => [path]),
1359
+ utimes: after(fs.utimes.bind(fs), (path) => [path])
1360
+ };
1361
+ return new Proxy(fs, {
1362
+ get(target, key, receiver) {
1363
+ if (Object.hasOwn(overrides, key)) return overrides[key];
1364
+ const value = Reflect.get(target, key, receiver);
1365
+ return typeof value === "function" ? value.bind(target) : value;
1366
+ }
1367
+ });
1368
+ }
1369
+ };
1370
+
810
1371
  // src/kernel/pipe.ts
811
1372
  var PIPE_CAPACITY = 64 * 1024;
812
1373
  var PipeError = class extends Error {
@@ -3159,61 +3720,6 @@ var HttpHandles = class {
3159
3720
  }
3160
3721
  };
3161
3722
 
3162
- // src/kernel/signals.ts
3163
- var SIG = {
3164
- HUP: 1,
3165
- INT: 2,
3166
- QUIT: 3,
3167
- ILL: 4,
3168
- TRAP: 5,
3169
- ABRT: 6,
3170
- BUS: 7,
3171
- FPE: 8,
3172
- KILL: 9,
3173
- USR1: 10,
3174
- SEGV: 11,
3175
- USR2: 12,
3176
- PIPE: 13,
3177
- ALRM: 14,
3178
- TERM: 15,
3179
- STKFLT: 16,
3180
- CHLD: 17,
3181
- CONT: 18,
3182
- STOP: 19,
3183
- TSTP: 20,
3184
- TTIN: 21,
3185
- TTOU: 22,
3186
- URG: 23,
3187
- XCPU: 24,
3188
- XFSZ: 25,
3189
- VTALRM: 26,
3190
- PROF: 27,
3191
- WINCH: 28,
3192
- POLL: 29,
3193
- PWR: 30,
3194
- SYS: 31
3195
- };
3196
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
3197
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
3198
- function defaultAction(sig) {
3199
- if (sig === SIG.CONT) return "continue";
3200
- if (STOPPING.has(sig)) return "stop";
3201
- return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
3202
- }
3203
- function isSignal(sig) {
3204
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
3205
- }
3206
- function sigbit(sig) {
3207
- return 1 << sig;
3208
- }
3209
- var SIGNAL_BY_NAME = {
3210
- SIGINT: SIG.INT,
3211
- SIGTERM: SIG.TERM,
3212
- SIGKILL: SIG.KILL,
3213
- SIGSTOP: SIG.STOP,
3214
- SIGCONT: SIG.CONT
3215
- };
3216
-
3217
3723
  // src/kernel/tty.ts
3218
3724
  var ICRNL = 256;
3219
3725
  var IUTF8 = 16384;
@@ -4416,6 +4922,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
4416
4922
  "proc-captured",
4417
4923
  "proc-fork",
4418
4924
  "proc-kill",
4925
+ "proc-list",
4419
4926
  "proc-exec",
4420
4927
  "proc-setpgid",
4421
4928
  "proc-getpgid",
@@ -4864,6 +5371,12 @@ var WasmProcess = class {
4864
5371
  throw new KernelError("ESRCH");
4865
5372
  }
4866
5373
  return { ok: true, kind: "void" };
5374
+ case "proc-list":
5375
+ return {
5376
+ ok: true,
5377
+ kind: "json",
5378
+ json: this.options.processes?.() ?? { boot: 0, processes: [] }
5379
+ };
4867
5380
  case "proc-alarm":
4868
5381
  this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
4869
5382
  return { ok: true, kind: "void" };
@@ -4967,6 +5480,7 @@ function spawnWasmProcess(opts) {
4967
5480
  forker: opts.forker,
4968
5481
  fs: opts.fs,
4969
5482
  kill: opts.kill,
5483
+ processes: opts.processes,
4970
5484
  jobs: opts.jobs,
4971
5485
  ptys: opts.ptys,
4972
5486
  net: opts.net,
@@ -5111,6 +5625,9 @@ var JobTable = class {
5111
5625
  this.members.set(pid, member);
5112
5626
  return member;
5113
5627
  }
5628
+ list() {
5629
+ return [...this.members.values()];
5630
+ }
5114
5631
  remove(pid) {
5115
5632
  this.members.delete(pid);
5116
5633
  }
@@ -5441,16 +5958,19 @@ var Launcher = class {
5441
5958
  processes = /* @__PURE__ */ new Map();
5442
5959
  zombies = /* @__PURE__ */ new Set();
5443
5960
  orphans = /* @__PURE__ */ new Set();
5961
+ described = /* @__PURE__ */ new Map();
5444
5962
  jobs = new JobTable();
5445
5963
  locks = new LockTable();
5446
5964
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
5447
5965
  net = new LoopbackNet();
5448
5966
  ca;
5449
5967
  transport;
5968
+ watchers = new FsWatchers();
5450
5969
  nextPid = 1e3;
5970
+ boot = Date.now();
5451
5971
  terminals = 0;
5452
5972
  constructor(options) {
5453
- this.base = options.fs;
5973
+ this.base = this.watchers.wrap(options.fs);
5454
5974
  this.createWorker = options.createWorker;
5455
5975
  this.modulesDir = options.modules ?? "/node_modules";
5456
5976
  this.env = { ...networkEnv(), ...options.env };
@@ -5461,7 +5981,7 @@ var Launcher = class {
5461
5981
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
5462
5982
  ca: this.ca
5463
5983
  });
5464
- this.fs = withCommandDirs(options.fs, async () => new Set((await this.commands()).keys()));
5984
+ this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
5465
5985
  }
5466
5986
  commands() {
5467
5987
  this.catalog ??= scanCommands(this.base, this.modulesDir);
@@ -5619,6 +6139,7 @@ var Launcher = class {
5619
6139
  spawner: this.spawner(pid, req.report),
5620
6140
  forker: this.forker(pid, req),
5621
6141
  kill: (target, sig) => this.kill(target, sig),
6142
+ processes: () => ({ boot: this.boot, processes: this.list() }),
5622
6143
  jobs: this.jobs,
5623
6144
  ptys: this.ptys,
5624
6145
  net: this.net,
@@ -5630,17 +6151,57 @@ var Launcher = class {
5630
6151
  });
5631
6152
  this.processes.set(pid, handle);
5632
6153
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
6154
+ this.described.set(pid, {
6155
+ argv: [req.argv0, ...req.args],
6156
+ tty: terminal?.name ?? null,
6157
+ started: Date.now()
6158
+ });
5633
6159
  void handle.exited.then(() => {
5634
6160
  this.processes.delete(pid);
5635
- if (req.ppid === void 0 || this.orphans.delete(pid)) this.jobs.remove(pid);
6161
+ if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
5636
6162
  else this.zombies.add(pid);
5637
6163
  });
5638
6164
  return handle;
5639
6165
  }
6166
+ forget(pid) {
6167
+ this.jobs.remove(pid);
6168
+ this.described.delete(pid);
6169
+ }
5640
6170
  reaped(pid) {
5641
- if (this.zombies.delete(pid)) this.jobs.remove(pid);
6171
+ if (this.zombies.delete(pid)) this.forget(pid);
5642
6172
  else this.orphans.add(pid);
5643
6173
  }
6174
+ list() {
6175
+ const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
6176
+ const rootOf = (member) => {
6177
+ let root = member;
6178
+ for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
6179
+ root = up;
6180
+ }
6181
+ return root;
6182
+ };
6183
+ const shown = (pid) => {
6184
+ const member = pid === void 0 ? void 0 : members.get(pid);
6185
+ return member ? rootOf(member).pid : pid ?? 0;
6186
+ };
6187
+ const listed = [];
6188
+ for (const member of members.values()) {
6189
+ const described = this.described.get(member.pid);
6190
+ if (member.execed || !described) continue;
6191
+ const root = rootOf(member);
6192
+ listed.push({
6193
+ pid: root.pid,
6194
+ tid: member.pid,
6195
+ ppid: shown(root.ppid),
6196
+ pgid: member.pgid,
6197
+ sid: member.sid,
6198
+ ...described,
6199
+ started: (this.described.get(root.pid) ?? described).started,
6200
+ state: this.processes.has(member.pid) ? "S" : "Z"
6201
+ });
6202
+ }
6203
+ return listed;
6204
+ }
5644
6205
  spawner(ppid, report) {
5645
6206
  return async (req, fds) => {
5646
6207
  const planned = await this.plan(req.file, req.argv, req.cwd);
@@ -5718,8 +6279,9 @@ var Launcher = class {
5718
6279
  const env = this.environment(cwd, options.env);
5719
6280
  const out = [];
5720
6281
  const err = [];
6282
+ const keep = options.collect !== false;
5721
6283
  const collect = (chunks2, tee) => (bytes) => {
5722
- chunks2.push(bytes);
6284
+ if (keep) chunks2.push(bytes);
5723
6285
  tee?.(bytes);
5724
6286
  };
5725
6287
  const stdout = collect(out, options.onStdout);
@@ -5735,6 +6297,7 @@ var Launcher = class {
5735
6297
  fds.installAt(1, sinkFile(stdout));
5736
6298
  fds.installAt(2, sinkFile(stderr));
5737
6299
  const handle = await this.launch(planned, { env, cwd, fds, report });
6300
+ options.onStarted?.(handle.pid);
5738
6301
  const status = await handle.exited;
5739
6302
  return { status, stdout: concat4(out), stderr: concat4(err) };
5740
6303
  }
@@ -5876,6 +6439,118 @@ function memoryRoot() {
5876
6439
  return new MemoryDirectory("", null);
5877
6440
  }
5878
6441
 
6442
+ // src/kernel/net/remote-transport.ts
6443
+ var TransportError = class extends Error {
6444
+ status;
6445
+ code;
6446
+ constructor(message2, status, code) {
6447
+ super(message2);
6448
+ this.status = status;
6449
+ this.code = code;
6450
+ }
6451
+ };
6452
+ var RemoteTransport = class {
6453
+ traits;
6454
+ port;
6455
+ waiting = /* @__PURE__ */ new Map();
6456
+ nextId = 0;
6457
+ failure;
6458
+ constructor(port, traits) {
6459
+ this.port = port;
6460
+ this.traits = traits;
6461
+ }
6462
+ receive(reply) {
6463
+ const waiter = this.waiting.get(reply.nid);
6464
+ if (!waiter) return;
6465
+ this.waiting.delete(reply.nid);
6466
+ if (reply.net === "error") {
6467
+ waiter.reject(new TransportError(reply.message, reply.status, reply.code));
6468
+ } else waiter.resolve(reply);
6469
+ }
6470
+ fail(error) {
6471
+ this.failure = error;
6472
+ for (const waiter of this.waiting.values()) waiter.reject(error);
6473
+ this.waiting.clear();
6474
+ }
6475
+ ask(call) {
6476
+ if (this.failure) return Promise.reject(this.failure);
6477
+ return new Promise((resolve, reject) => {
6478
+ this.waiting.set(call.nid, { resolve, reject });
6479
+ this.port.postMessage(call);
6480
+ });
6481
+ }
6482
+ cancel(nid) {
6483
+ const waiter = this.waiting.get(nid);
6484
+ this.waiting.delete(nid);
6485
+ waiter?.reject(new TransportError("the request was cancelled"));
6486
+ this.port.postMessage({ net: "cancel", nid });
6487
+ }
6488
+ async fetch(request) {
6489
+ const nid = ++this.nextId;
6490
+ const { signal } = request;
6491
+ signal.throwIfAborted();
6492
+ let finished = false;
6493
+ const abort = () => {
6494
+ finished = true;
6495
+ this.cancel(nid);
6496
+ };
6497
+ signal.addEventListener("abort", abort, { once: true });
6498
+ const finish = () => {
6499
+ if (finished) return;
6500
+ finished = true;
6501
+ signal.removeEventListener("abort", abort);
6502
+ };
6503
+ let head;
6504
+ try {
6505
+ head = await this.ask({
6506
+ net: "fetch",
6507
+ nid,
6508
+ url: request.url,
6509
+ method: request.method,
6510
+ headers: request.headers,
6511
+ ...request.body ? { body: request.body } : {}
6512
+ });
6513
+ } catch (e) {
6514
+ finish();
6515
+ throw e;
6516
+ }
6517
+ const { status, statusText, headers } = head;
6518
+ const read = async () => {
6519
+ if (finished) return void 0;
6520
+ const reply = await this.ask({ net: "read", nid }).catch((e) => {
6521
+ finish();
6522
+ throw e;
6523
+ });
6524
+ if (reply.net === "chunk") return reply.bytes;
6525
+ finish();
6526
+ return void 0;
6527
+ };
6528
+ const cancel = async () => {
6529
+ if (finished) return;
6530
+ finish();
6531
+ this.cancel(nid);
6532
+ };
6533
+ return {
6534
+ status,
6535
+ statusText,
6536
+ headers,
6537
+ cancel,
6538
+ body: {
6539
+ [Symbol.asyncIterator]: () => ({
6540
+ next: async () => {
6541
+ const bytes = await read();
6542
+ return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
6543
+ },
6544
+ return: async () => {
6545
+ await cancel();
6546
+ return { value: void 0, done: true };
6547
+ }
6548
+ })
6549
+ }
6550
+ };
6551
+ }
6552
+ };
6553
+
5879
6554
  // src/serve.ts
5880
6555
  function signalNumber(name2) {
5881
6556
  const key = name2.slice(3);
@@ -5884,97 +6559,298 @@ function signalNumber(name2) {
5884
6559
  return SIG[key];
5885
6560
  }
5886
6561
 
5887
- // src/body-idle.ts
5888
- var BODY_IDLE_MS = 5 * 60 * 1e3;
5889
- function idleFailure(ms) {
5890
- return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
5891
- code: "ETIMEDOUT"
5892
- });
5893
- }
5894
- async function readWithin(reader, ms) {
5895
- if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
5896
- let timer;
5897
- const idle = new Promise((_, reject) => {
5898
- timer = setTimeout(() => reject(idleFailure(ms)), ms);
5899
- });
5900
- try {
5901
- return await Promise.race([reader.read(), idle]);
5902
- } finally {
5903
- clearTimeout(timer);
6562
+ // src/client/attach.ts
6563
+ var KernelGoneError = class extends Error {
6564
+ constructor(message2 = "the slicc-kernel this client was attached to is gone") {
6565
+ super(message2);
6566
+ this.name = "KernelGoneError";
6567
+ }
6568
+ };
6569
+ var KernelCallError = class extends Error {
6570
+ code;
6571
+ constructor(message2, code) {
6572
+ super(message2);
6573
+ this.name = "KernelCallError";
6574
+ this.code = code;
5904
6575
  }
6576
+ };
6577
+ var encoder4 = new TextEncoder();
6578
+ var bytesOf2 = (data) => typeof data === "string" ? encoder4.encode(data) : data;
6579
+ var UNAVAILABLE = {
6580
+ manualRedirects: false,
6581
+ encodedBodies: false,
6582
+ maxRequestBody: 0,
6583
+ unavailable: true
6584
+ };
6585
+ async function holdLock(locks) {
6586
+ if (!locks) return { release() {
6587
+ } };
6588
+ const name2 = `slicc-kernel-client:${crypto.randomUUID()}`;
6589
+ const granted = Promise.withResolvers();
6590
+ const held = Promise.withResolvers();
6591
+ void locks.request(name2, () => {
6592
+ granted.resolve();
6593
+ return held.promise;
6594
+ });
6595
+ await granted.promise;
6596
+ return { name: name2, release: () => held.resolve() };
5905
6597
  }
5906
-
5907
- // src/transport.ts
5908
- var MAX_REQUEST_BODY = 64 * 1024 * 1024;
5909
- function joinHeaders(headers) {
5910
- const out = new Headers();
5911
- for (const [name2, value] of headers) {
5912
- try {
5913
- out.append(name2, value);
5914
- } catch {
5915
- }
6598
+ function concat5(chunks2) {
6599
+ const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
6600
+ let at = 0;
6601
+ for (const chunk2 of chunks2) {
6602
+ out.set(chunk2, at);
6603
+ at += chunk2.length;
5916
6604
  }
5917
6605
  return out;
5918
6606
  }
5919
- async function* chunks(body, idleMs) {
5920
- if (!body) return;
5921
- const reader = body.getReader();
5922
- try {
5923
- for (let next = await readWithin(reader, idleMs); !next.done; next = await readWithin(reader, idleMs)) {
5924
- if (next.value.byteLength > 0) yield next.value;
6607
+ var Channel = class {
6608
+ greeted = Promise.withResolvers();
6609
+ ended = Promise.withResolvers();
6610
+ remote;
6611
+ failure;
6612
+ pending = /* @__PURE__ */ new Map();
6613
+ watching = /* @__PURE__ */ new Map();
6614
+ protocol = PROTOCOL;
6615
+ nextId = 0;
6616
+ port;
6617
+ release;
6618
+ constructor(port, release) {
6619
+ this.port = port;
6620
+ this.release = release;
6621
+ this.greeted.promise.catch(() => void 0);
6622
+ port.addEventListener("message", (event) => this.receive(event.data));
6623
+ port.addEventListener("close", () => this.end(new KernelGoneError()));
6624
+ port.start?.();
6625
+ }
6626
+ post(message2) {
6627
+ if (!this.failure) this.port.postMessage(message2);
6628
+ }
6629
+ end(error) {
6630
+ if (this.failure) return;
6631
+ this.failure = error;
6632
+ this.greeted.reject(error);
6633
+ this.remote?.fail(error);
6634
+ for (const call of this.pending.values()) call.reject(error);
6635
+ this.pending.clear();
6636
+ this.release();
6637
+ this.port.close?.();
6638
+ this.ended.resolve(error);
6639
+ }
6640
+ request(call, handlers = {}) {
6641
+ const id = ++this.nextId;
6642
+ const { failure } = this;
6643
+ if (failure) return { id, done: Promise.reject(failure) };
6644
+ const done4 = new Promise((resolve, reject) => {
6645
+ this.pending.set(id, { resolve, reject, ...handlers });
6646
+ this.port.postMessage({ ...call, id });
6647
+ });
6648
+ return { id, done: done4 };
6649
+ }
6650
+ call(call) {
6651
+ return this.request(call).done;
6652
+ }
6653
+ receive(data) {
6654
+ if (data.watch !== void 0) {
6655
+ this.watching.get(data.watch)?.(data.change);
6656
+ return;
5925
6657
  }
5926
- } finally {
5927
- await reader.cancel().catch(() => void 0);
6658
+ if (data.bye !== void 0) {
6659
+ this.end(new KernelGoneError(data.bye));
6660
+ return;
6661
+ }
6662
+ if (data.hello) {
6663
+ this.greeted.resolve(data.hello);
6664
+ return;
6665
+ }
6666
+ if (data.net !== void 0) {
6667
+ this.remote?.receive(data);
6668
+ return;
6669
+ }
6670
+ const reply = data;
6671
+ const call = this.pending.get(reply.id);
6672
+ if (!call) return;
6673
+ if (reply.fd !== void 0) {
6674
+ call.output?.(reply.fd, reply.bytes);
6675
+ return;
6676
+ }
6677
+ if (reply.started !== void 0) {
6678
+ call.started?.(reply.started);
6679
+ return;
6680
+ }
6681
+ this.pending.delete(reply.id);
6682
+ if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
6683
+ else call.resolve(reply.result);
5928
6684
  }
6685
+ };
6686
+ async function spawnOn(channel, argv, opts) {
6687
+ const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
6688
+ const started = Promise.withResolvers();
6689
+ const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
6690
+ const { done: done4 } = channel.request(
6691
+ { op: "spawn", argv, options },
6692
+ {
6693
+ output: (fd, bytes) => (fd === 1 ? onStdout : onStderr)?.(bytes),
6694
+ started: started.resolve
6695
+ }
6696
+ );
6697
+ done4.catch(started.reject);
6698
+ const pid = await started.promise;
6699
+ return {
6700
+ pid,
6701
+ pgid: pid,
6702
+ exited: done4,
6703
+ signal: async (name2 = "SIGTERM") => void await channel.call({ op: "kill", pid: -pid, signal: name2 })
6704
+ };
5929
6705
  }
5930
- function fetchTransport(options = {}) {
5931
- const send = options.fetch ?? globalThis.fetch.bind(globalThis);
6706
+ async function runOn(channel, argv, opts) {
6707
+ const { onStdout, onStderr, ...rest } = opts;
6708
+ const chunks2 = { 1: [], 2: [] };
6709
+ const live = { 1: new TextDecoder(), 2: new TextDecoder() };
6710
+ const tee = (fd, on) => (bytes) => {
6711
+ chunks2[fd].push(bytes);
6712
+ on?.(live[fd].decode(bytes, { stream: true }));
6713
+ };
6714
+ const child = await spawnOn(channel, argv, {
6715
+ ...rest,
6716
+ onStdout: tee(1, onStdout),
6717
+ onStderr: tee(2, onStderr)
6718
+ });
6719
+ const status = await child.exited;
6720
+ const text = (fd) => new TextDecoder().decode(concat5(chunks2[fd]));
6721
+ return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
6722
+ }
6723
+ async function terminalOn(channel, argv, opts) {
6724
+ const { onData, ...options } = opts;
6725
+ let listener = onData ?? null;
6726
+ const backlog = [];
6727
+ const started = Promise.withResolvers();
6728
+ const { id, done: done4 } = channel.request(
6729
+ { op: "open-terminal", argv, options },
6730
+ {
6731
+ output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
6732
+ started: started.resolve
6733
+ }
6734
+ );
6735
+ done4.catch(started.reject);
6736
+ const pid = await started.promise;
6737
+ const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
5932
6738
  return {
5933
- traits: {
5934
- manualRedirects: false,
5935
- encodedBodies: false,
5936
- maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
5937
- crossOrigin: "cors"
6739
+ pid,
6740
+ exited: done4,
6741
+ get onData() {
6742
+ return listener;
5938
6743
  },
5939
- async fetch(request) {
5940
- let response;
5941
- try {
5942
- response = await send(request.url, {
5943
- method: request.method,
5944
- headers: joinHeaders(request.headers),
5945
- ...request.body ? { body: request.body } : {},
5946
- signal: request.signal,
5947
- redirect: "follow",
5948
- credentials: "omit",
5949
- mode: "cors"
5950
- });
5951
- } catch (e) {
5952
- if (request.signal.aborted) throw e;
5953
- throw Object.assign(
5954
- new Error(
5955
- `fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
5956
- ),
5957
- { status: 502 }
6744
+ set onData(next) {
6745
+ listener = next;
6746
+ if (next) for (const bytes of backlog.splice(0)) next(bytes);
6747
+ },
6748
+ write: (data) => send({ action: "write", bytes: bytesOf2(data) }),
6749
+ resize: (cols, rows) => send({ action: "resize", cols, rows }),
6750
+ signal: (name2) => send({ action: "signal", signal: name2 }),
6751
+ close: () => send({ action: "close" })
6752
+ };
6753
+ }
6754
+ function fsOn(channel) {
6755
+ const fs = (method, ...args) => channel.call({ op: "fs", method, args });
6756
+ const done4 = async (method, ...args) => void await fs(method, ...args);
6757
+ return {
6758
+ readFile: (path) => fs("readFile", path),
6759
+ readText: async (path) => new TextDecoder().decode(await fs("readFile", path)),
6760
+ writeFile: (path, data) => done4("writeFile", path, data),
6761
+ stat: (path) => fs("stat", path),
6762
+ lstat: (path) => fs("lstat", path),
6763
+ readdir: (path) => fs("readdir", path),
6764
+ mkdir: (path) => done4("mkdir", path),
6765
+ rm: (path, o = {}) => done4("rm", path, o.force === true),
6766
+ rename: (from, to) => done4("rename", from, to),
6767
+ realpath: (path) => fs("realpath", path),
6768
+ symlink: (target, path) => done4("symlink", target, path),
6769
+ readlink: (path) => fs("readlink", path),
6770
+ exists: (path) => fs("exists", path),
6771
+ async watch(paths, options, onChange) {
6772
+ if (channel.protocol[1] < 1) {
6773
+ throw new KernelCallError(
6774
+ `this kernel speaks protocol ${channel.protocol.join(".")}, which has no watch`,
6775
+ "ENOSYS"
5958
6776
  );
5959
6777
  }
5960
- const headers = [];
5961
- response.headers.forEach((value, name2) => {
5962
- headers.push([name2, value]);
6778
+ const { id, done: done5 } = channel.request({
6779
+ op: "watch",
6780
+ paths,
6781
+ recursive: options.recursive === true
5963
6782
  });
5964
- const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
6783
+ channel.watching.set(id, onChange);
6784
+ try {
6785
+ await done5;
6786
+ } catch (error) {
6787
+ channel.watching.delete(id);
6788
+ throw error;
6789
+ }
5965
6790
  return {
5966
- status: response.status,
5967
- statusText: response.statusText,
5968
- headers,
5969
- body,
5970
- cancel: async () => {
5971
- await body.return(void 0);
5972
- await response.body?.cancel().catch(() => void 0);
6791
+ close: () => {
6792
+ if (!channel.watching.delete(id)) return;
6793
+ void channel.call({ op: "unwatch", watch: id }).catch(() => void 0);
5973
6794
  }
5974
6795
  };
5975
6796
  }
5976
6797
  };
5977
6798
  }
6799
+ async function greet(channel, lock, timeoutMs) {
6800
+ channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
6801
+ const timer = setTimeout(
6802
+ () => channel.end(
6803
+ new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
6804
+ ),
6805
+ timeoutMs
6806
+ );
6807
+ let hello;
6808
+ try {
6809
+ hello = await channel.greeted.promise;
6810
+ } finally {
6811
+ clearTimeout(timer);
6812
+ }
6813
+ const refused = hello.error ?? versionError(hello.protocol);
6814
+ if (refused) {
6815
+ const error = new Error(refused);
6816
+ channel.end(error);
6817
+ throw error;
6818
+ }
6819
+ return hello;
6820
+ }
6821
+ async function attachKernel(port, options = {}) {
6822
+ const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
6823
+ const own = await holdLock(locks);
6824
+ const channel = new Channel(port, own.release);
6825
+ const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
6826
+ channel.protocol = hello.protocol;
6827
+ if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
6828
+ const transport = new RemoteTransport(
6829
+ { postMessage: (call) => channel.post(call) },
6830
+ hello.traits ?? UNAVAILABLE
6831
+ );
6832
+ channel.remote = transport;
6833
+ return {
6834
+ protocol: hello.protocol,
6835
+ transport,
6836
+ closed: channel.ended.promise,
6837
+ spawn: (argv, opts = {}) => spawnOn(channel, argv, opts),
6838
+ run: (argv, opts = {}) => runOn(channel, argv, opts),
6839
+ openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
6840
+ ps: () => channel.call({ op: "ps" }),
6841
+ kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
6842
+ fetch: (req) => {
6843
+ const { signal, ...rest } = req;
6844
+ return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
6845
+ },
6846
+ fs: fsOn(channel),
6847
+ async close(o = {}) {
6848
+ if (channel.failure) return;
6849
+ await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
6850
+ channel.end(new KernelGoneError("this client is closed"));
6851
+ }
6852
+ };
6853
+ }
5978
6854
 
5979
6855
  // src/node.ts
5980
6856
  function nodeTransport() {
@@ -6033,6 +6909,7 @@ async function createNodeKernel(options = {}) {
6033
6909
  await fs.reconcile();
6034
6910
  const live = /* @__PURE__ */ new Set();
6035
6911
  const pending = /* @__PURE__ */ new Set();
6912
+ const clients = /* @__PURE__ */ new Set();
6036
6913
  let terminated = false;
6037
6914
  const file = options.worker ?? new URL("./node-process-worker.js", import.meta.url);
6038
6915
  const transport = options.network?.transport;
@@ -6102,14 +6979,29 @@ async function createNodeKernel(options = {}) {
6102
6979
  await fs.writeFile(path, data);
6103
6980
  },
6104
6981
  readFile: async (path) => fs.readFileBuffer(path),
6982
+ async connect() {
6983
+ if (terminated) throw new Error(TERMINATED);
6984
+ const { port1, port2 } = new MessageChannel2();
6985
+ const served = serveClient(port1, {
6986
+ launcher: async () => launcher,
6987
+ signal: signalNumber
6988
+ });
6989
+ clients.add(served);
6990
+ void served.closed.then(() => clients.delete(served));
6991
+ return port2;
6992
+ },
6105
6993
  terminate() {
6106
6994
  terminated = true;
6995
+ for (const client of [...clients]) client.detach();
6107
6996
  for (const reject of [...pending]) reject(new Error(TERMINATED));
6108
6997
  for (const kill of [...live]) kill();
6109
6998
  }
6110
6999
  };
6111
7000
  }
6112
7001
  export {
7002
+ KernelCallError,
7003
+ KernelGoneError,
7004
+ attachKernel,
6113
7005
  createNodeKernel,
6114
7006
  fetchTransport,
6115
7007
  memoryRoot,