@ai-ecoverse/slicc-kernel 1.182.0 → 1.183.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.
Files changed (37) hide show
  1. package/README.md +54 -5
  2. package/dist/driver-worker.js +290 -90
  3. package/dist/driver-worker.js.map +4 -4
  4. package/dist/index.js +328 -102
  5. package/dist/index.js.map +4 -4
  6. package/dist/kernel-worker.js +1705 -695
  7. package/dist/kernel-worker.js.map +4 -4
  8. package/dist/node-driver-worker.js +298 -98
  9. package/dist/node-driver-worker.js.map +4 -4
  10. package/dist/node-process-worker.cjs +849 -193
  11. package/dist/node-process-worker.cjs.map +4 -4
  12. package/dist/node-process-worker.js +849 -193
  13. package/dist/node-process-worker.js.map +4 -4
  14. package/dist/node.js +1875 -743
  15. package/dist/node.js.map +4 -4
  16. package/dist/process-worker.js +805 -149
  17. package/dist/process-worker.js.map +4 -4
  18. package/dist/types/client/attach.d.ts +2 -0
  19. package/dist/types/client/protocol.d.ts +1 -0
  20. package/dist/types/client/serve-client.d.ts +1 -0
  21. package/dist/types/devices-relay.d.ts +74 -0
  22. package/dist/types/index.d.ts +14 -0
  23. package/dist/types/kernel/devices.d.ts +155 -0
  24. package/dist/types/kernel/embedder.d.ts +2 -0
  25. package/dist/types/kernel/fd-table.d.ts +3 -1
  26. package/dist/types/kernel/host.d.ts +2 -0
  27. package/dist/types/kernel/process.d.ts +20 -0
  28. package/dist/types/kernel/serial-ioctl.d.ts +73 -0
  29. package/dist/types/launcher.d.ts +4 -0
  30. package/dist/types/node.d.ts +6 -2
  31. package/dist/types/process/js/js-dev.d.ts +24 -0
  32. package/dist/types/process/kernel-streams.d.ts +11 -1
  33. package/dist/types/process/process-pty.d.ts +1 -0
  34. package/dist/types/process/wasi/wasi-fds.d.ts +1 -0
  35. package/dist/types/process/wasi/wasi-termios.d.ts +1 -0
  36. package/dist/types/serve.d.ts +14 -1
  37. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -326,11 +326,120 @@ function clipboardAnswerer(port, hook) {
326
326
  };
327
327
  }
328
328
 
329
- // src/embedder-relay.ts
329
+ // src/devices-relay.ts
330
+ function quiet() {
331
+ return void 0;
332
+ }
333
+ function errorOf(err) {
334
+ const name = err?.name;
335
+ const message = err instanceof Error ? err.message : String(err);
336
+ return { name: typeof name === "string" ? name : "Error", message };
337
+ }
330
338
  function fresh2(bytes) {
331
339
  return bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes : bytes.slice();
332
340
  }
333
- function errorOf(err) {
341
+ function devicesAnswerer(port, hookFor) {
342
+ const locals = /* @__PURE__ */ new Map();
343
+ const post = (message, transfer = []) => port.postMessage({ devicesReply: message }, transfer);
344
+ const open = async (id, asked) => {
345
+ const local = { abort: new AbortController() };
346
+ locals.set(id, local);
347
+ try {
348
+ const hook = hookFor(asked.client);
349
+ if (!hook)
350
+ throw Object.assign(new Error("this page has no devices hook"), {
351
+ name: "NotSupportedError"
352
+ });
353
+ const streams = await hook.open({
354
+ path: asked.path,
355
+ flags: asked.flags,
356
+ stamp: asked.stamp,
357
+ signal: local.abort.signal
358
+ });
359
+ if (!locals.has(id)) {
360
+ await streams?.readable?.cancel("closed").catch(quiet);
361
+ await streams?.writable?.abort("closed").catch(quiet);
362
+ await Promise.resolve(streams?.control?.("close")).catch(quiet);
363
+ return;
364
+ }
365
+ local.streams = streams;
366
+ local.reader = streams.readable.getReader();
367
+ local.writer = streams.writable.getWriter();
368
+ post({ id, opened: true });
369
+ } catch (err) {
370
+ locals.delete(id);
371
+ post({ id, failed: errorOf(err) });
372
+ }
373
+ };
374
+ const pull = async (id, local) => {
375
+ try {
376
+ const got = await local.reader.read();
377
+ if (got.done) return post({ id, done: true });
378
+ const bytes = fresh2(got.value);
379
+ post({ id, chunk: bytes }, [bytes.buffer]);
380
+ } catch (err) {
381
+ post({ id, readError: errorOf(err) });
382
+ }
383
+ };
384
+ const write = async (id, local, bytes, seq) => {
385
+ try {
386
+ await local.writer.write(bytes);
387
+ post({ id, wrote: seq });
388
+ } catch (err) {
389
+ post({ id, writeError: errorOf(err), seq });
390
+ }
391
+ };
392
+ const control = async (id, local, asked) => {
393
+ try {
394
+ await local.closing;
395
+ const value = await local.streams.control(asked.op, asked.args);
396
+ post({ id, answered: asked.seq, ...value !== void 0 ? { value } : {} });
397
+ } catch (err) {
398
+ post({ id, refused: asked.seq, error: errorOf(err) });
399
+ }
400
+ if (asked.op === "close") locals.delete(id);
401
+ };
402
+ const release = async (local, reason) => {
403
+ const { reader, writer } = local;
404
+ local.reader = void 0;
405
+ local.writer = void 0;
406
+ await reader?.cancel(reason).catch(quiet);
407
+ reader?.releaseLock();
408
+ await writer?.abort(reason).catch(quiet);
409
+ writer?.releaseLock();
410
+ };
411
+ const cancel = (id, local, reason) => {
412
+ local.abort.abort(new DOMException(reason, "AbortError"));
413
+ if (!local.streams) {
414
+ locals.delete(id);
415
+ return;
416
+ }
417
+ local.closing = Promise.resolve(local.closing).then(() => release(local, reason));
418
+ };
419
+ return {
420
+ handle(data) {
421
+ const message = data?.devices;
422
+ if (!message) return false;
423
+ if ("open" in message) {
424
+ void open(message.id, message.open);
425
+ return true;
426
+ }
427
+ const local = locals.get(message.id);
428
+ if (!local) return true;
429
+ if ("pull" in message) void pull(message.id, local);
430
+ else if ("write" in message) void write(message.id, local, message.write, message.seq);
431
+ else if ("control" in message) void control(message.id, local, message.control);
432
+ else cancel(message.id, local, message.cancel);
433
+ return true;
434
+ }
435
+ };
436
+ }
437
+
438
+ // src/embedder-relay.ts
439
+ function fresh3(bytes) {
440
+ return bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes : bytes.slice();
441
+ }
442
+ function errorOf2(err) {
334
443
  const name = err?.name;
335
444
  const message = err instanceof Error ? err.message : String(err);
336
445
  return { name: typeof name === "string" ? name : "Error", message };
@@ -369,7 +478,7 @@ function embedderAnswerer(port, hookFor) {
369
478
  const post = (message, transfer = []) => port.postMessage({ embedderReply: message }, transfer);
370
479
  const fail = (id, err) => {
371
480
  running.delete(id);
372
- post({ id, error: errorOf(err) });
481
+ post({ id, error: errorOf2(err) });
373
482
  };
374
483
  const start = async (ask) => {
375
484
  const state = { abort: new AbortController(), body: [], wakeBody: void 0 };
@@ -404,7 +513,7 @@ function embedderAnswerer(port, hookFor) {
404
513
  post({ id, done: true });
405
514
  return;
406
515
  }
407
- const bytes = fresh2(got.value);
516
+ const bytes = fresh3(got.value);
408
517
  post({ id, chunk: bytes }, [bytes.buffer]);
409
518
  } catch (err) {
410
519
  fail(id, err);
@@ -591,6 +700,9 @@ function integrityAnswerer(port, lookup) {
591
700
  };
592
701
  }
593
702
 
703
+ // src/fs/owners.ts
704
+ var ROOT_OWNER = Object.freeze({ uid: 0, gid: 0 });
705
+
594
706
  // src/kernel/embedder.ts
595
707
  var EMBEDDER_HEADER_MAX = 64 * 1024;
596
708
  var EMBEDDER_WINDOW = 1024 * 1024;
@@ -610,6 +722,147 @@ function kindsOf(hook) {
610
722
  );
611
723
  }
612
724
 
725
+ // src/kernel/serial-ioctl.ts
726
+ var IOCTL = {
727
+ TCGETS: 21505,
728
+ TCSETS: 21506,
729
+ TCSETSW: 21507,
730
+ TCSETSF: 21508,
731
+ TCSBRK: 21513,
732
+ TCFLSH: 21515,
733
+ TIOCEXCL: 21516,
734
+ TIOCNXCL: 21517,
735
+ TIOCSCTTY: 21518,
736
+ TIOCGPGRP: 21519,
737
+ TIOCSPGRP: 21520,
738
+ TIOCOUTQ: 21521,
739
+ TIOCGWINSZ: 21523,
740
+ TIOCSWINSZ: 21524,
741
+ TIOCMGET: 21525,
742
+ TIOCMBIS: 21526,
743
+ TIOCMBIC: 21527,
744
+ TIOCMSET: 21528,
745
+ FIONREAD: 21531,
746
+ TIOCNOTTY: 21538,
747
+ TCSBRKP: 21541,
748
+ TIOCSBRK: 21543,
749
+ TIOCCBRK: 21544,
750
+ TCGETS2: 2150388778,
751
+ TCSETS2: 1076646955,
752
+ TCSETSW2: 1076646956,
753
+ TCSETSF2: 1076646957
754
+ };
755
+ var TERMIOS_GETS = /* @__PURE__ */ new Set([IOCTL.TCGETS, IOCTL.TCGETS2]);
756
+ var TERMIOS_SETS = /* @__PURE__ */ new Set([
757
+ IOCTL.TCSETS,
758
+ IOCTL.TCSETSW,
759
+ IOCTL.TCSETSF,
760
+ IOCTL.TCSETS2,
761
+ IOCTL.TCSETSW2,
762
+ IOCTL.TCSETSF2
763
+ ]);
764
+ var INT_OUT = /* @__PURE__ */ new Set([
765
+ IOCTL.TIOCMGET,
766
+ IOCTL.FIONREAD,
767
+ IOCTL.TIOCOUTQ
768
+ ]);
769
+ var INT_IN = /* @__PURE__ */ new Set([
770
+ IOCTL.TIOCMBIS,
771
+ IOCTL.TIOCMBIC,
772
+ IOCTL.TIOCMSET
773
+ ]);
774
+ var INT_ARG = /* @__PURE__ */ new Set([IOCTL.TCSBRK, IOCTL.TCSBRKP, IOCTL.TCFLSH]);
775
+ var CSIZE = 48;
776
+ var CSTOPB = 64;
777
+ var PARENB = 256;
778
+ var PARODD = 512;
779
+ var CRTSCTS = 2147483648;
780
+ function settingsOf(cflag, baudRate) {
781
+ return {
782
+ baudRate,
783
+ dataBits: 5 + ((cflag & CSIZE) >> 4),
784
+ parity: cflag & PARENB ? cflag & PARODD ? "odd" : "even" : "none",
785
+ stopBits: cflag & CSTOPB ? 2 : 1,
786
+ flowControl: cflag & CRTSCTS ? "hardware" : "none"
787
+ };
788
+ }
789
+ var SERIAL_CFLAG = 13 | 48 | 128 | 1024 | 2048;
790
+ var SERIAL_LFLAG = 16 | 32 | 512 | 2048;
791
+
792
+ // src/kernel/signals.ts
793
+ var SIG = {
794
+ HUP: 1,
795
+ INT: 2,
796
+ QUIT: 3,
797
+ ILL: 4,
798
+ TRAP: 5,
799
+ ABRT: 6,
800
+ BUS: 7,
801
+ FPE: 8,
802
+ KILL: 9,
803
+ USR1: 10,
804
+ SEGV: 11,
805
+ USR2: 12,
806
+ PIPE: 13,
807
+ ALRM: 14,
808
+ TERM: 15,
809
+ STKFLT: 16,
810
+ CHLD: 17,
811
+ CONT: 18,
812
+ STOP: 19,
813
+ TSTP: 20,
814
+ TTIN: 21,
815
+ TTOU: 22,
816
+ URG: 23,
817
+ XCPU: 24,
818
+ XFSZ: 25,
819
+ VTALRM: 26,
820
+ PROF: 27,
821
+ WINCH: 28,
822
+ POLL: 29,
823
+ PWR: 30,
824
+ SYS: 31
825
+ };
826
+ var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
827
+ var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
828
+ function signalName(sig) {
829
+ const name = Object.entries(SIG).find(([, n]) => n === sig)?.[0];
830
+ return name === void 0 ? void 0 : `SIG${name}`;
831
+ }
832
+ function quiet2(p) {
833
+ p.catch(() => void 0);
834
+ return p;
835
+ }
836
+ function signalField(sig) {
837
+ const name = signalName(sig);
838
+ return name ? { signal: name } : {};
839
+ }
840
+ var SIGNAL_BY_NAME = {
841
+ SIGINT: SIG.INT,
842
+ SIGTERM: SIG.TERM,
843
+ SIGKILL: SIG.KILL,
844
+ SIGSTOP: SIG.STOP,
845
+ SIGCONT: SIG.CONT
846
+ };
847
+ var SI = { USER: 0, QUEUE: -1, TIMER: -2, TKILL: -6, KERNEL: 128 };
848
+ var KERNEL_SIGINFO = Object.freeze({
849
+ code: SI.KERNEL,
850
+ pid: 0,
851
+ uid: 0,
852
+ status: 0
853
+ });
854
+
855
+ // src/kernel/tty.ts
856
+ var encoder3 = new TextEncoder();
857
+
858
+ // src/kernel/devices.ts
859
+ var HIGH_WATER = 64 * 1024;
860
+ var DEFAULT_SETTINGS = settingsOf(SERIAL_CFLAG, 9600);
861
+ function devicesHook(hook) {
862
+ if (typeof hook?.open !== "function") throw new TypeError("devices needs open");
863
+ return { open: (request) => hook.open(request) };
864
+ }
865
+
613
866
  // src/kernel/net/loopback-names.ts
614
867
  var HOST_LOOPBACK = "host.slicc.internal";
615
868
  var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
@@ -928,69 +1181,6 @@ function transferOf(reply) {
928
1181
  return [reply.bytes.buffer];
929
1182
  }
930
1183
 
931
- // src/kernel/signals.ts
932
- var SIG = {
933
- HUP: 1,
934
- INT: 2,
935
- QUIT: 3,
936
- ILL: 4,
937
- TRAP: 5,
938
- ABRT: 6,
939
- BUS: 7,
940
- FPE: 8,
941
- KILL: 9,
942
- USR1: 10,
943
- SEGV: 11,
944
- USR2: 12,
945
- PIPE: 13,
946
- ALRM: 14,
947
- TERM: 15,
948
- STKFLT: 16,
949
- CHLD: 17,
950
- CONT: 18,
951
- STOP: 19,
952
- TSTP: 20,
953
- TTIN: 21,
954
- TTOU: 22,
955
- URG: 23,
956
- XCPU: 24,
957
- XFSZ: 25,
958
- VTALRM: 26,
959
- PROF: 27,
960
- WINCH: 28,
961
- POLL: 29,
962
- PWR: 30,
963
- SYS: 31
964
- };
965
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
966
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
967
- function signalName(sig) {
968
- const name = Object.entries(SIG).find(([, n]) => n === sig)?.[0];
969
- return name === void 0 ? void 0 : `SIG${name}`;
970
- }
971
- function quiet(p) {
972
- p.catch(() => void 0);
973
- return p;
974
- }
975
- function signalField(sig) {
976
- const name = signalName(sig);
977
- return name ? { signal: name } : {};
978
- }
979
- var SIGNAL_BY_NAME = {
980
- SIGINT: SIG.INT,
981
- SIGTERM: SIG.TERM,
982
- SIGKILL: SIG.KILL,
983
- SIGSTOP: SIG.STOP,
984
- SIGCONT: SIG.CONT
985
- };
986
- var SI = { USER: 0, QUEUE: -1, TIMER: -2, TKILL: -6, KERNEL: 128 };
987
- var KERNEL_SIGINFO = Object.freeze({
988
- code: SI.KERNEL,
989
- pid: 0,
990
- uid: 0,
991
- status: 0
992
- });
993
-
994
1184
  // src/sealed-relay.ts
995
1185
  function textOf(err) {
996
1186
  return err instanceof Error ? err.message : String(err);
@@ -1410,13 +1600,13 @@ function unsendableHeaders(url, names, hint) {
1410
1600
  function refuseUnsendable(url, out, hint) {
1411
1601
  if (!platformFilters()) return;
1412
1602
  const kept = new Request(PROBE, { headers: out }).headers;
1413
- const refused2 = [];
1603
+ const refused3 = [];
1414
1604
  for (const name of new Set(out.keys())) {
1415
1605
  if (kept.has(name)) continue;
1416
1606
  if (TRANSPORT_OWN.has(name)) out.delete(name);
1417
- else refused2.push(name);
1607
+ else refused3.push(name);
1418
1608
  }
1419
- if (refused2.length > 0) throw unsendableHeaders(url, refused2, hint);
1609
+ if (refused3.length > 0) throw unsendableHeaders(url, refused3, hint);
1420
1610
  }
1421
1611
  function streamOf(body) {
1422
1612
  const it = body[Symbol.asyncIterator]();
@@ -1748,7 +1938,7 @@ function honors(traits, redirect) {
1748
1938
  const raw = traits.manualRedirects === true && (hops === void 0 || hops === false);
1749
1939
  return redirect === "manual" && raw;
1750
1940
  }
1751
- function refused(message) {
1941
+ function refused2(message) {
1752
1942
  return new TransportError(message, 502, void 0, "transport");
1753
1943
  }
1754
1944
  var RemoteTransport = class {
@@ -1839,7 +2029,7 @@ var RemoteTransport = class {
1839
2029
  const { signal, redirect } = request;
1840
2030
  signal.throwIfAborted();
1841
2031
  if (unfollowed(redirect) && !honors(this.traits, redirect)) {
1842
- throw refused(`this transport cannot honor redirect: ${redirect}`);
2032
+ throw refused2(`this transport cannot honor redirect: ${redirect}`);
1843
2033
  }
1844
2034
  let finished = false;
1845
2035
  const abort = () => {
@@ -1890,7 +2080,7 @@ var RemoteTransport = class {
1890
2080
  };
1891
2081
  if (unfollowed(redirect) && (redirected || redirects?.length)) {
1892
2082
  await cancel();
1893
- throw refused(`this transport followed a redirect despite redirect: ${redirect}`);
2083
+ throw refused2(`this transport followed a redirect despite redirect: ${redirect}`);
1894
2084
  }
1895
2085
  return {
1896
2086
  ...url !== void 0 ? { url } : {},
@@ -1958,7 +2148,7 @@ var RemoteTransport = class {
1958
2148
  };
1959
2149
 
1960
2150
  // src/client/protocol.ts
1961
- var PROTOCOL = [1, 17];
2151
+ var PROTOCOL = [1, 18];
1962
2152
  function locksOf(scope = globalThis) {
1963
2153
  const locks = scope.navigator?.locks;
1964
2154
  return typeof locks?.request === "function" ? locks : void 0;
@@ -1985,8 +2175,8 @@ var KernelCallError = class extends Error {
1985
2175
  this.code = code;
1986
2176
  }
1987
2177
  };
1988
- var encoder3 = new TextEncoder();
1989
- var bytesOf2 = (data) => typeof data === "string" ? encoder3.encode(data) : data;
2178
+ var encoder4 = new TextEncoder();
2179
+ var bytesOf2 = (data) => typeof data === "string" ? encoder4.encode(data) : data;
1990
2180
  var UNAVAILABLE = {
1991
2181
  manualRedirects: false,
1992
2182
  encodedBodies: false,
@@ -2072,8 +2262,10 @@ var Channel = class {
2072
2262
  return this.request(call).done;
2073
2263
  }
2074
2264
  requests;
2265
+ devices;
2075
2266
  receive(data) {
2076
2267
  if (this.requests?.handle(data)) return;
2268
+ if (this.devices?.handle(data)) return;
2077
2269
  if (data.traits && data.id === void 0) {
2078
2270
  if (this.remote) this.remote.traits = data.traits;
2079
2271
  for (const listener of [...this.traitsListeners]) listener(data.traits);
@@ -2278,7 +2470,7 @@ async function sudoRunOn(channel, replay, { stdout, stderr, tag }) {
2278
2470
  return { pid, pgid: pid, ended: endedOf(done, () => endedBy) };
2279
2471
  }
2280
2472
  function endedOf(done, signal) {
2281
- return quiet(
2473
+ return quiet2(
2282
2474
  done.then((status) => {
2283
2475
  const by = signal();
2284
2476
  return { status, ...by ? { signal: by } : {} };
@@ -2408,9 +2600,14 @@ function since(channel, minor, what, run) {
2408
2600
  }
2409
2601
  return run();
2410
2602
  }
2411
- async function greet(channel, lock, timeoutMs, requests) {
2603
+ async function greet(channel, lock, timeoutMs, requests, devices) {
2412
2604
  channel.post({
2413
- hello: { protocol: PROTOCOL, ...lock ? { lock } : {}, ...requests ? { requests } : {} }
2605
+ hello: {
2606
+ protocol: PROTOCOL,
2607
+ ...lock ? { lock } : {},
2608
+ ...requests ? { requests } : {},
2609
+ ...devices ? { devices: true } : {}
2610
+ }
2414
2611
  });
2415
2612
  const timer = setTimeout(
2416
2613
  () => channel.end(
@@ -2424,9 +2621,9 @@ async function greet(channel, lock, timeoutMs, requests) {
2424
2621
  } finally {
2425
2622
  clearTimeout(timer);
2426
2623
  }
2427
- const refused2 = hello.error ?? versionError(hello.protocol);
2428
- if (refused2) {
2429
- const error = new Error(refused2);
2624
+ const refused3 = hello.error ?? versionError(hello.protocol);
2625
+ if (refused3) {
2626
+ const error = new Error(refused3);
2430
2627
  channel.end(error);
2431
2628
  throw error;
2432
2629
  }
@@ -2438,11 +2635,14 @@ async function attachKernel(port, options = {}) {
2438
2635
  const channel = new Channel(port, own.release);
2439
2636
  const requests = options.requests;
2440
2637
  if (requests) channel.requests = embedderAnswerer(port, () => requests);
2638
+ const devices = options.devices ? devicesHook(options.devices) : void 0;
2639
+ if (devices) channel.devices = devicesAnswerer(port, () => devices);
2441
2640
  const hello = await greet(
2442
2641
  channel,
2443
2642
  own.name,
2444
2643
  options.timeoutMs ?? 1e4,
2445
- requests ? kindsOf(requests) : void 0
2644
+ requests ? kindsOf(requests) : void 0,
2645
+ devices !== void 0
2446
2646
  );
2447
2647
  channel.protocol = hello.protocol;
2448
2648
  if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
@@ -2495,7 +2695,7 @@ function fsError2(code, message = code) {
2495
2695
  // src/mount/hostfs.ts
2496
2696
  var HOSTFS_IO = 16 * 1024 * 1024;
2497
2697
  var codeOf = (err) => err?.code;
2498
- var errorOf2 = (err) => ({
2698
+ var errorOf3 = (err) => ({
2499
2699
  code: typeof codeOf(err) === "string" ? codeOf(err) : "EIO",
2500
2700
  message: err?.message ?? String(err)
2501
2701
  });
@@ -2545,7 +2745,7 @@ function serveHostfsPort(port, transport) {
2545
2745
  );
2546
2746
  send({ id, end: true });
2547
2747
  } catch (err) {
2548
- send({ id, error: errorOf2(err) });
2748
+ send({ id, error: errorOf3(err) });
2549
2749
  } finally {
2550
2750
  watches.delete(id);
2551
2751
  }
@@ -2573,7 +2773,7 @@ function serveHostfsPort(port, transport) {
2573
2773
  if (op !== "call") throw fsError2("EINVAL", `unknown hostfs port op ${op}`);
2574
2774
  send({ id, result: await answer(data.name, data.body) });
2575
2775
  } catch (err) {
2576
- send({ id, error: errorOf2(err) });
2776
+ send({ id, error: errorOf3(err) });
2577
2777
  }
2578
2778
  };
2579
2779
  const stop = () => {
@@ -2651,6 +2851,7 @@ async function initCall(options, transport) {
2651
2851
  ...hooks.fetch ? { sealedFetch: true } : {},
2652
2852
  ...options.clipboard ? { clipboard: true } : {},
2653
2853
  ...options.requests ? { requests: kindsOf(options.requests) } : {},
2854
+ ...options.devices ? { devices: true } : {},
2654
2855
  ...options.approve ? { approve: true } : {},
2655
2856
  ...options.protect ? { protect: protectInit(options.protect) } : {},
2656
2857
  ...options.processMounts !== void 0 ? {
@@ -2826,24 +3027,44 @@ function checkOptions(options) {
2826
3027
  }
2827
3028
  if (options.timeZone !== void 0) systemTimeZone(options.timeZone);
2828
3029
  if (options.protect) protectInit(options.protect);
3030
+ if (options.devices) devicesHook(options.devices);
2829
3031
  integrityOptions(options.protect?.integrity);
2830
3032
  facadesInit(options.facades);
2831
3033
  sealedHooks(options.sealed);
2832
3034
  }
2833
3035
  function connector(call, bound) {
2834
3036
  return async (connectOptions = {}) => {
2835
- const { requests, ...rest } = connectOptions;
2836
- if (requests === void 0 || requests === "attach")
2837
- return await call({
2838
- op: "connect",
2839
- ...rest,
2840
- ...requests ? { requests } : {}
2841
- });
2842
- const made = await call({ op: "connect", ...rest, requests: kindsOf(requests) });
2843
- bound.set(made.client, requests);
3037
+ const { requests, devices, ...rest } = connectOptions;
3038
+ const hook = devices !== void 0 && devices !== "attach" ? devicesHook(devices) : void 0;
3039
+ const made = await call({
3040
+ op: "connect",
3041
+ ...rest,
3042
+ ...requests === "attach" ? { requests } : requests ? { requests: kindsOf(requests) } : {},
3043
+ ...devices === "attach" ? { devices } : hook ? { devices: true } : {}
3044
+ });
3045
+ if (!("client" in made)) return made;
3046
+ if (requests && requests !== "attach") bound.requests.set(made.client, requests);
3047
+ if (hook) bound.devices.set(made.client, hook);
3048
+ bound.ports.set(made.port, made.client);
2844
3049
  return made.port;
2845
3050
  };
2846
3051
  }
3052
+ function clientApi(call, bound) {
3053
+ return { connect: connector(call, bound), devices: kernelDevices(call, bound) };
3054
+ }
3055
+ function kernelDevices(call, bound) {
3056
+ return {
3057
+ async add({ client, ...spec }) {
3058
+ const id = client === void 0 ? 0 : bound.ports.get(client);
3059
+ if (id === void 0)
3060
+ throw Object.assign(new Error("EINVAL: client is not a port from connect({ devices })"), {
3061
+ code: "EINVAL"
3062
+ });
3063
+ const node = await call({ op: "devices-add", spec, client: id });
3064
+ return { remove: async () => void await call({ op: "devices-remove", node }) };
3065
+ }
3066
+ };
3067
+ }
2847
3068
  function hookRelays(worker, options, bound) {
2848
3069
  const hooks = sealedHooks(options.sealed);
2849
3070
  const sealed = sealedAnswerer(worker, hooks.request, hooks.fetch);
@@ -2851,10 +3072,15 @@ function hookRelays(worker, options, bound) {
2851
3072
  const integrity = integrityAnswerer(worker, options.protect?.integrity?.lookup);
2852
3073
  const requests = embedderAnswerer(
2853
3074
  worker,
2854
- (client) => client === 0 ? options.requests : bound.get(client)
3075
+ (client) => client === 0 ? options.requests : bound.requests.get(client)
3076
+ );
3077
+ const pageDevices = options.devices ? devicesHook(options.devices) : void 0;
3078
+ const devices = devicesAnswerer(
3079
+ worker,
3080
+ (client) => client === 0 ? pageDevices : bound.devices.get(client)
2855
3081
  );
2856
3082
  return {
2857
- handle: (data) => sealed.handle(data) || clipboard.handle(data) || requests.handle(data) || integrity(data),
3083
+ handle: (data) => sealed.handle(data) || clipboard.handle(data) || requests.handle(data) || devices.handle(data) || integrity(data),
2858
3084
  close: () => sealed.close()
2859
3085
  };
2860
3086
  }
@@ -2875,7 +3101,7 @@ async function sudoRunOn2(request, replay, { stdout, stderr, tag }) {
2875
3101
  );
2876
3102
  done.catch(started.reject);
2877
3103
  const pid = await started.promise;
2878
- return { pid, pgid: pid, ended: quiet(done.then(endedOf2)) };
3104
+ return { pid, pgid: pid, ended: quiet2(done.then(endedOf2)) };
2879
3105
  }
2880
3106
  function endedOf2(r) {
2881
3107
  if (typeof r === "number") return { status: r };
@@ -2916,7 +3142,7 @@ async function createKernel(options = {}) {
2916
3142
  );
2917
3143
  const cdp = cdpBridge(options.cdp);
2918
3144
  const facades = facadeBridge(options.facades);
2919
- const bound = /* @__PURE__ */ new Map();
3145
+ const bound = { requests: /* @__PURE__ */ new Map(), devices: /* @__PURE__ */ new Map(), ports: /* @__PURE__ */ new WeakMap() };
2920
3146
  const sealed = hookRelays(worker, options, bound);
2921
3147
  const approvals = approver(options.approve, worker);
2922
3148
  const fail = (error) => {
@@ -3010,8 +3236,8 @@ async function createKernel(options = {}) {
3010
3236
  const send = (action) => void call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
3011
3237
  return {
3012
3238
  pid,
3013
- exited: quiet(done.then((r) => endedOf2(r).status)),
3014
- ended: quiet(done.then(endedOf2)),
3239
+ exited: quiet2(done.then((r) => endedOf2(r).status)),
3240
+ ended: quiet2(done.then(endedOf2)),
3015
3241
  get onData() {
3016
3242
  return listener;
3017
3243
  },
@@ -3043,7 +3269,7 @@ async function createKernel(options = {}) {
3043
3269
  },
3044
3270
  openTerminal,
3045
3271
  sudoRun: (replay, sudoOptions = {}) => sudoRunOn2(request, replay, sudoOptions),
3046
- connect: connector(call, bound),
3272
+ ...clientApi(call, bound),
3047
3273
  ...accounts(call, options.users === void 0 ? DEFAULT_USERS : options.users),
3048
3274
  setRoutes: async (routes) => void await call({ op: "routes", routes }),
3049
3275
  chmod: async (path, mode) => void await call({ op: "chmod", path, mode }),