@ai-ecoverse/slicc-kernel 1.22.1 → 1.24.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,6 +1,92 @@
1
1
  // src/node.ts
2
2
  import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
3
3
 
4
+ // src/cdp/port.ts
5
+ function serveCdpPort(port, hook, request, onDone) {
6
+ let conn;
7
+ let done4 = false;
8
+ const end = () => {
9
+ done4 = true;
10
+ port.close();
11
+ onDone?.();
12
+ };
13
+ const finish = (reason) => {
14
+ if (done4) return;
15
+ port.postMessage({ close: reason });
16
+ end();
17
+ };
18
+ port.addEventListener("message", ({ data }) => {
19
+ if (data.m !== void 0) conn?.send(data.m);
20
+ else if (data.close && !done4) {
21
+ end();
22
+ conn?.close();
23
+ }
24
+ });
25
+ port.start?.();
26
+ new Promise((resolve) => resolve(hook(request))).then(
27
+ (opened2) => {
28
+ conn = opened2;
29
+ if (done4) return opened2.close();
30
+ opened2.onmessage = (m) => {
31
+ if (!done4) port.postMessage({ m });
32
+ };
33
+ opened2.onclose = (reason) => finish(reason ?? null);
34
+ port.postMessage({ open: true });
35
+ },
36
+ (e) => {
37
+ if (done4) return;
38
+ port.postMessage({ error: e instanceof Error ? e.message : String(e) });
39
+ end();
40
+ }
41
+ );
42
+ return () => {
43
+ if (done4) return;
44
+ conn?.close();
45
+ finish("the CDP host is gone");
46
+ };
47
+ }
48
+ function portCdpHook(ask, gone) {
49
+ return (request) => new Promise((resolve, reject) => {
50
+ const { port1, port2 } = new MessageChannel();
51
+ let closed = false;
52
+ const conn = {
53
+ send: (m) => {
54
+ if (!closed) port1.postMessage({ m });
55
+ },
56
+ onmessage: null,
57
+ onclose: null,
58
+ close: () => {
59
+ if (closed) return;
60
+ closed = true;
61
+ port1.postMessage({ close: true });
62
+ port1.close();
63
+ }
64
+ };
65
+ port1.addEventListener("message", ({ data }) => {
66
+ if ("m" in data) conn.onmessage?.(data.m);
67
+ else if ("open" in data) resolve(conn);
68
+ else if ("error" in data) {
69
+ closed = true;
70
+ port1.close();
71
+ reject(new Error(data.error));
72
+ } else if (!closed) {
73
+ closed = true;
74
+ port1.close();
75
+ conn.onclose?.(data.close ?? void 0);
76
+ }
77
+ });
78
+ port1.start();
79
+ void gone?.then(() => {
80
+ if (closed) return;
81
+ closed = true;
82
+ port1.close();
83
+ reject(new Error("the CDP host is gone"));
84
+ conn.onclose?.("the CDP host is gone");
85
+ });
86
+ ask(request, port2);
87
+ });
88
+ }
89
+
4
90
  // src/fs/types.ts
5
91
  function rangedOps(fs) {
6
92
  const { pread, pwrite, truncate } = fs;
@@ -637,7 +723,12 @@ function keyOf(addr) {
637
723
  var LoopbackNet = class {
638
724
  bound = /* @__PURE__ */ new Map();
639
725
  activators = /* @__PURE__ */ new Map();
726
+ internal = /* @__PURE__ */ new Set();
640
727
  nextEphemeral = EPHEMERAL_FIRST;
728
+ kernelOnly(port, reserve = false) {
729
+ if (reserve) this.internal.add(port);
730
+ return this.internal.has(port);
731
+ }
641
732
  activate(addr, start) {
642
733
  this.activators.set(keyOf(addr), start);
643
734
  }
@@ -698,6 +789,14 @@ var LOOPBACK_NAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "
698
789
  function dialSocket(net, port, host = "127.0.0.1") {
699
790
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new KernelError("EINVAL");
700
791
  const target = LOOPBACK_NAMES.has(host.toLowerCase()) ? "127.0.0.1" : host;
792
+ if (net.kernelOnly(port)) {
793
+ throw Object.assign(
794
+ new Error(`ECONNREFUSED: port ${port} only takes connections from inside the kernel`),
795
+ {
796
+ code: "ECONNREFUSED"
797
+ }
798
+ );
799
+ }
701
800
  return net.connect({ family: "inet", host: target, port });
702
801
  }
703
802
  var codeOf = (err) => {
@@ -985,7 +1084,7 @@ function serveTransport(peer, transport) {
985
1084
  }
986
1085
 
987
1086
  // src/client/protocol.ts
988
- var PROTOCOL = [1, 4];
1087
+ var PROTOCOL = [1, 5];
989
1088
  var FS_METHODS = [
990
1089
  "readFile",
991
1090
  "writeFile",
@@ -1099,6 +1198,43 @@ function joinable(l, pgid, ours) {
1099
1198
  if (sid === void 0) throw fsError("ESRCH", `no process group ${pgid}`);
1100
1199
  if (!ours(sid)) throw fsError("EPERM", `process group ${pgid} is in another session`);
1101
1200
  }
1201
+ function cdpRegistry(send, gone) {
1202
+ const registered = /* @__PURE__ */ new Map();
1203
+ const drop = (id) => {
1204
+ registered.get(id)?.();
1205
+ return registered.delete(id);
1206
+ };
1207
+ return {
1208
+ serve(l, req) {
1209
+ const hook = portCdpHook(
1210
+ (request, cdp) => send({ cdpOpen: { ...request, registration: req.id }, port: cdp }, [
1211
+ cdp
1212
+ ]),
1213
+ gone
1214
+ );
1215
+ registered.set(req.id, l.cdp.register(hook, req.runtime));
1216
+ return req.id;
1217
+ },
1218
+ drop,
1219
+ close: () => {
1220
+ for (const id of [...registered.keys()]) drop(id);
1221
+ }
1222
+ };
1223
+ }
1224
+ function watchOp(l, req, watches, send) {
1225
+ if (req.op === "unwatch") {
1226
+ watches.get(req.watch)?.();
1227
+ watches.delete(req.watch);
1228
+ return { result: true };
1229
+ }
1230
+ const unwatch = l.watchers.watch(
1231
+ req.paths,
1232
+ { recursive: req.recursive },
1233
+ (change) => send({ watch: req.id, change })
1234
+ );
1235
+ watches.set(req.id, unwatch);
1236
+ return { result: req.id };
1237
+ }
1102
1238
  function serveClient(port, host) {
1103
1239
  let state = "new";
1104
1240
  const terminals = /* @__PURE__ */ new Map();
@@ -1111,6 +1247,7 @@ function serveClient(port, host) {
1111
1247
  if (state !== "closed") port.postMessage(message2, transfer);
1112
1248
  };
1113
1249
  const reply = (id, body, transfer) => send({ id, ...body }, transfer);
1250
+ const cdps = cdpRegistry(send, ended.promise);
1114
1251
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
1115
1252
  if (state === "closed") return;
1116
1253
  send({ bye });
@@ -1118,6 +1255,7 @@ function serveClient(port, host) {
1118
1255
  net?.close();
1119
1256
  for (const unwatch of watches.values()) unwatch();
1120
1257
  watches.clear();
1258
+ cdps.close();
1121
1259
  for (const session of terminals.values()) session.close();
1122
1260
  terminals.clear();
1123
1261
  if (kill) killGroups(host, [...groups]);
@@ -1212,19 +1350,9 @@ function serveClient(port, host) {
1212
1350
  throw new Error(`unknown file system call ${req.method}`);
1213
1351
  }
1214
1352
  return fsCall(l.fs, req.method, req.args);
1215
- case "watch": {
1216
- const unwatch = l.watchers.watch(
1217
- req.paths,
1218
- { recursive: req.recursive },
1219
- (change) => send({ watch: req.id, change })
1220
- );
1221
- watches.set(req.id, unwatch);
1222
- return { result: req.id };
1223
- }
1353
+ case "watch":
1224
1354
  case "unwatch":
1225
- watches.get(req.watch)?.();
1226
- watches.delete(req.watch);
1227
- return { result: true };
1355
+ return watchOp(l, req, watches, send);
1228
1356
  case "mount":
1229
1357
  return { result: await l.mount(req.spec) };
1230
1358
  case "umount":
@@ -1234,6 +1362,10 @@ function serveClient(port, host) {
1234
1362
  return { result: l.mounts.list() };
1235
1363
  case "dial":
1236
1364
  return dialFor(l, req, watches);
1365
+ case "serve-cdp":
1366
+ return { result: cdps.serve(l, req) };
1367
+ case "unserve-cdp":
1368
+ return { result: cdps.drop(req.registration) };
1237
1369
  case "detach":
1238
1370
  return { result: true };
1239
1371
  default:
@@ -1367,6 +1499,56 @@ function prefixes(path) {
1367
1499
  function within(path, root) {
1368
1500
  return path === root || path.startsWith(`${root}/`);
1369
1501
  }
1502
+ var LOCKED = /* @__PURE__ */ new Set(["NotReadableError", "NoModificationAllowedError", "InvalidStateError"]);
1503
+ var SYNC_BACKOFF_MS = [5, 10, 20, 40, 80];
1504
+ async function openSync(handle) {
1505
+ const sync = handle.createSyncAccessHandle;
1506
+ if (!sync) return void 0;
1507
+ for (let attempt = 0; ; attempt++) {
1508
+ try {
1509
+ return await sync.call(handle, { mode: "readwrite-unsafe" });
1510
+ } catch (err) {
1511
+ const delay = SYNC_BACKOFF_MS[attempt];
1512
+ if (!LOCKED.has(err?.name)) throw err;
1513
+ if (delay === void 0) return void 0;
1514
+ await new Promise((resolve) => setTimeout(resolve, delay));
1515
+ }
1516
+ }
1517
+ }
1518
+ function writeAll(access, at, bytes) {
1519
+ for (let done4 = 0; done4 < bytes.length; ) {
1520
+ const n = access.write(bytes.subarray(done4), { at: at + done4 });
1521
+ if (!(n > 0))
1522
+ throw Object.assign(new Error("the file system took no more bytes"), { code: "ENOSPC" });
1523
+ done4 += n;
1524
+ }
1525
+ }
1526
+ async function positioned(handle, op) {
1527
+ const access = await openSync(handle);
1528
+ if (access) {
1529
+ try {
1530
+ op({
1531
+ write: (at, bytes) => writeAll(access, at, bytes),
1532
+ truncate: (n) => access.truncate(n)
1533
+ });
1534
+ access.flush();
1535
+ } finally {
1536
+ access.close();
1537
+ }
1538
+ return;
1539
+ }
1540
+ const writable = await handle.createWritable({ keepExistingData: true });
1541
+ try {
1542
+ await op({
1543
+ write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
1544
+ truncate: (n) => writable.truncate(n)
1545
+ });
1546
+ } catch (err) {
1547
+ await writable.abort();
1548
+ throw err;
1549
+ }
1550
+ await writable.close();
1551
+ }
1370
1552
  function isHandle(child) {
1371
1553
  return "kind" in child;
1372
1554
  }
@@ -1376,9 +1558,11 @@ var OpfsFs = class {
1376
1558
  dirs = /* @__PURE__ */ new Map();
1377
1559
  checked = /* @__PURE__ */ new WeakMap();
1378
1560
  channel;
1379
- constructor(root, meta = new MemoryMeta(), channel) {
1561
+ ranged;
1562
+ constructor(root, meta = new MemoryMeta(), channel, options = {}) {
1380
1563
  this.root = root;
1381
1564
  this.meta = meta;
1565
+ this.ranged = options.ranged === true;
1382
1566
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
1383
1567
  if (this.channel) {
1384
1568
  this.channel.onmessage = ({ data }) => {
@@ -1515,7 +1699,8 @@ var OpfsFs = class {
1515
1699
  mtime: new Date(mtime),
1516
1700
  atime: new Date(meta?.atimeMs ?? mtime),
1517
1701
  ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
1518
- ino: meta?.ino ?? inodeOf(path)
1702
+ ino: meta?.ino ?? inodeOf(path),
1703
+ ...this.ranged && !directory ? { ranged: true } : {}
1519
1704
  };
1520
1705
  }
1521
1706
  linkStat(entry) {
@@ -1549,20 +1734,47 @@ var OpfsFs = class {
1549
1734
  () => false
1550
1735
  );
1551
1736
  }
1552
- async readFileBuffer(path) {
1553
- const handle = (await this.locate(path, true)).handle;
1554
- if (handle.kind === "directory") throw fsError("EISDIR", path);
1737
+ async retrying(path, op, locks) {
1555
1738
  for (let attempt = 1; ; attempt++) {
1556
1739
  try {
1557
- return new Uint8Array(await (await handle.getFile()).arrayBuffer());
1740
+ return await op();
1558
1741
  } catch (err) {
1559
- if (err?.name !== "NotReadableError" || attempt >= READ_ATTEMPTS) {
1560
- throw translate(err, path);
1561
- }
1742
+ const name2 = err?.name;
1743
+ const again = name2 === "NotReadableError" || locks && LOCKED.has(name2);
1744
+ if (!again || attempt >= READ_ATTEMPTS) throw translate(err, path);
1562
1745
  await new Promise((resolve) => setTimeout(resolve, attempt * 10));
1563
1746
  }
1564
1747
  }
1565
1748
  }
1749
+ async existing(path) {
1750
+ const handle = (await this.locate(path, true)).handle;
1751
+ if (handle.kind === "directory") throw fsError("EISDIR", path);
1752
+ return handle;
1753
+ }
1754
+ async readFileBuffer(path) {
1755
+ const handle = await this.existing(path);
1756
+ return this.retrying(
1757
+ path,
1758
+ async () => new Uint8Array(await (await handle.getFile()).arrayBuffer()),
1759
+ false
1760
+ );
1761
+ }
1762
+ async pread(path, offset, length2) {
1763
+ const handle = await this.existing(path);
1764
+ return this.retrying(
1765
+ path,
1766
+ async () => new Uint8Array(await (await handle.getFile()).slice(offset, offset + length2).arrayBuffer()),
1767
+ false
1768
+ );
1769
+ }
1770
+ async pwrite(path, offset, bytes) {
1771
+ const handle = (await this.fileFor(path))[0];
1772
+ await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
1773
+ }
1774
+ async truncate(path, size) {
1775
+ const handle = await this.existing(path);
1776
+ await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
1777
+ }
1566
1778
  async readFile(path) {
1567
1779
  return new TextDecoder().decode(await this.readFileBuffer(path));
1568
1780
  }
@@ -1576,16 +1788,21 @@ var OpfsFs = class {
1576
1788
  const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
1577
1789
  return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
1578
1790
  }
1579
- async writeFile(path, content) {
1791
+ async fileFor(path) {
1580
1792
  const [target, existed] = await this.writeTarget(path);
1581
1793
  const [dir, name2] = await this.parent(target);
1582
- let file;
1583
1794
  try {
1584
- file = await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true }));
1795
+ return [
1796
+ await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true })),
1797
+ existed
1798
+ ];
1585
1799
  } catch (err) {
1586
1800
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
1587
1801
  throw code ? fsError(code, path) : translate(err, path);
1588
1802
  }
1803
+ }
1804
+ async writeFile(path, content) {
1805
+ const [file, existed] = await this.fileFor(path);
1589
1806
  if (!existed && content.length === 0) return;
1590
1807
  const writable = await file.createWritable();
1591
1808
  await writable.write(content);
@@ -1817,121 +2034,820 @@ var OpfsFs = class {
1817
2034
  }
1818
2035
  };
1819
2036
 
1820
- // src/commands.ts
1821
- var NAME = /^[A-Za-z0-9._+-]+$/;
1822
- var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
1823
- var PACKAGE = /\$\{package\}/g;
1824
- function inside(pkg, rel2) {
1825
- if (typeof rel2 !== "string" || rel2 === "") return void 0;
1826
- const clean = rel2.replace(/^\.\//, "");
1827
- if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
1828
- return `${pkg}/${clean}`;
1829
- }
1830
- function envOf(pkg, raw) {
1831
- const env = {};
1832
- if (!raw || typeof raw !== "object") return env;
1833
- for (const [key, value] of Object.entries(raw)) {
1834
- if (!ENV_KEY.test(key)) continue;
1835
- if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
1836
- else if (value === null) env[key] = null;
2037
+ // src/kernel/net/http1.ts
2038
+ var HttpError = class extends Error {
2039
+ status;
2040
+ constructor(status, message2) {
2041
+ super(message2);
2042
+ this.status = status;
1837
2043
  }
1838
- return env;
1839
- }
1840
- function envFields(merged) {
1841
- const env = {};
1842
- const unset = [];
1843
- for (const [key, value] of Object.entries(merged)) {
1844
- if (value === null) unset.push(key);
1845
- else env[key] = value;
2044
+ };
2045
+ var CR = 13;
2046
+ var LF = 10;
2047
+ var READ_SIZE = 64 * 1024;
2048
+ var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
2049
+ var REASON = {
2050
+ 100: "Continue",
2051
+ 200: "OK",
2052
+ 400: "Bad Request",
2053
+ 403: "Forbidden",
2054
+ 404: "Not Found",
2055
+ 405: "Method Not Allowed",
2056
+ 408: "Request Timeout",
2057
+ 411: "Length Required",
2058
+ 413: "Content Too Large",
2059
+ 417: "Expectation Failed",
2060
+ 421: "Misdirected Request",
2061
+ 431: "Request Header Fields Too Large",
2062
+ 501: "Not Implemented",
2063
+ 502: "Bad Gateway",
2064
+ 503: "Service Unavailable",
2065
+ 504: "Gateway Timeout",
2066
+ 505: "HTTP Version Not Supported"
2067
+ };
2068
+ var Incoming = class {
2069
+ source;
2070
+ buf = new Uint8Array(0);
2071
+ eof = false;
2072
+ constructor(source) {
2073
+ this.source = source;
1846
2074
  }
1847
- return {
1848
- ...Object.keys(env).length > 0 ? { env } : {},
1849
- ...unset.length > 0 ? { unset } : {}
1850
- };
1851
- }
1852
- function argsOf(pkg, raw) {
1853
- if (!Array.isArray(raw) || raw.length === 0) return void 0;
1854
- if (!raw.every((arg) => typeof arg === "string")) return void 0;
1855
- return raw.map((arg) => arg.replace(PACKAGE, pkg));
1856
- }
1857
- function abiOf(raw, fallback) {
1858
- const abi = raw ?? fallback;
1859
- return abi === "emscripten" || abi === "wasi" ? abi : void 0;
1860
- }
1861
- function commandOf(pkg, name2, raw, packageAbi, shared) {
1862
- const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
1863
- const script = inside(pkg, raw.script);
1864
- if (script) {
1865
- return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
2075
+ get buffered() {
2076
+ return this.buf.length;
1866
2077
  }
1867
- const abi = abiOf(raw.abi, packageAbi);
1868
- const wasm = inside(pkg, raw.wasm);
1869
- const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
1870
- if (!abi || !glue || !wasm) return void 0;
1871
- const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
1872
- const args = argsOf(pkg, raw.args);
1873
- const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
1874
- return {
1875
- name: name2,
1876
- abi,
1877
- glue,
1878
- wasm,
1879
- argv0,
1880
- ...args ? { args } : {},
1881
- ...withEnv,
1882
- ...imports ? { imports } : {}
1883
- };
1884
- }
1885
- function commandsOf(pkg, manifest) {
1886
- const slicc = manifest.slicc;
1887
- if (!slicc || typeof slicc !== "object") return [];
1888
- const packageAbi = abiOf(slicc.abi, "emscripten");
1889
- const entries = slicc.commands;
1890
- if (!packageAbi || !entries || typeof entries !== "object") return [];
1891
- const shared = envOf(pkg, slicc.env);
1892
- const out = [];
1893
- for (const [name2, raw] of Object.entries(entries)) {
1894
- if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
1895
- continue;
2078
+ async fill(signal) {
2079
+ if (this.eof) return false;
2080
+ const chunk2 = await this.source.read(READ_SIZE, signal);
2081
+ if (chunk2.length === 0) {
2082
+ this.eof = true;
2083
+ return false;
1896
2084
  }
1897
- const command = commandOf(pkg, name2, raw, packageAbi, shared);
1898
- if (command) out.push(command);
2085
+ const next = new Uint8Array(this.buf.length + chunk2.length);
2086
+ next.set(this.buf);
2087
+ next.set(chunk2, this.buf.length);
2088
+ this.buf = next;
2089
+ return true;
1899
2090
  }
1900
- return out;
1901
- }
1902
- async function withPackagePaths(fs, pkg, command) {
1903
- if (!command.env) return command;
1904
- const env = {};
1905
- for (const [key, value] of Object.entries(command.env)) {
1906
- const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
1907
- env[key] = path && await fs.exists(path) ? path : value;
2091
+ take(n) {
2092
+ const out = this.buf.slice(0, n);
2093
+ this.buf = this.buf.subarray(n);
2094
+ return out;
1908
2095
  }
1909
- return { ...command, env };
1910
- }
1911
- async function packages(fs, modules) {
1912
- const names = await fs.readdir(modules).catch(() => []);
1913
- const dirs = [];
1914
- for (const name2 of names) {
1915
- if (name2.startsWith(".")) continue;
1916
- if (!name2.startsWith("@")) {
1917
- dirs.push(`${modules}/${name2}`);
1918
- continue;
1919
- }
1920
- for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
1921
- dirs.push(`${modules}/${name2}/${inner}`);
1922
- }
2096
+ async some(max, signal) {
2097
+ if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
2098
+ if (this.eof) return new Uint8Array(0);
2099
+ const chunk2 = await this.source.read(max, signal);
2100
+ if (chunk2.length === 0) this.eof = true;
2101
+ return chunk2;
1923
2102
  }
1924
- return dirs;
1925
- }
1926
- async function pnpmGlobalRoots(fs, home) {
1927
- const roots = [];
1928
- const global = `${home}/global`;
1929
- for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
1930
- for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
1931
- const dir = `${global}/${version}/${group}`;
1932
- const st = await fs.lstat(dir).catch(() => void 0);
1933
- if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
1934
- roots.push(`${dir}/node_modules`);
2103
+ async head(limit, signal) {
2104
+ let from = 0;
2105
+ for (; ; ) {
2106
+ const end = headEnd(this.buf, from);
2107
+ if (end > 0) {
2108
+ if (end > limit) throw new HttpError(431, "request head too large");
2109
+ return this.take(end);
2110
+ }
2111
+ if (this.buf.length > limit) throw new HttpError(431, "request head too large");
2112
+ from = Math.max(0, this.buf.length - 3);
2113
+ if (!await this.fill(signal)) {
2114
+ if (this.buf.length === 0) return null;
2115
+ throw new HttpError(400, "connection closed inside a request head");
2116
+ }
2117
+ }
2118
+ }
2119
+ async line(limit, signal) {
2120
+ let from = 0;
2121
+ for (; ; ) {
2122
+ const at = this.buf.indexOf(LF, from);
2123
+ if (at >= 0) {
2124
+ const line = this.take(at + 1);
2125
+ const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
2126
+ return text;
2127
+ }
2128
+ if (this.buf.length > limit) throw new HttpError(400, "line too long");
2129
+ from = this.buf.length;
2130
+ if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
2131
+ }
2132
+ }
2133
+ async exactly(n, signal) {
2134
+ while (this.buf.length < n) {
2135
+ if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
2136
+ }
2137
+ return this.take(n);
2138
+ }
2139
+ };
2140
+ function headEnd(buf, from) {
2141
+ for (let i = from; i < buf.length; i++) {
2142
+ if (buf[i] !== LF) continue;
2143
+ if (buf[i + 1] === LF) return i + 2;
2144
+ if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
2145
+ }
2146
+ return 0;
2147
+ }
2148
+ function latin1(bytes) {
2149
+ let out = "";
2150
+ for (let i = 0; i < bytes.length; i += 32768) {
2151
+ out += String.fromCharCode(...bytes.subarray(i, i + 32768));
2152
+ }
2153
+ return out;
2154
+ }
2155
+ function latin1Bytes(text) {
2156
+ const out = new Uint8Array(text.length);
2157
+ for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
2158
+ return out;
2159
+ }
2160
+ function parseRequestHead(bytes) {
2161
+ const lines = latin1(bytes).split(/\r?\n/);
2162
+ while (lines.length > 0 && lines[0] === "") lines.shift();
2163
+ const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
2164
+ if (!match) throw new HttpError(400, "malformed request line");
2165
+ if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
2166
+ return {
2167
+ method: match[1],
2168
+ target: match[2],
2169
+ minor: Number(match[4]),
2170
+ headers: parseFields(lines)
2171
+ };
2172
+ }
2173
+ function parseFields(lines) {
2174
+ const headers = [];
2175
+ for (const line of lines) {
2176
+ if (line === "") continue;
2177
+ if (line.startsWith(" ") || line.startsWith(" ")) {
2178
+ throw new HttpError(400, "obsolete line folding");
2179
+ }
2180
+ const colon = line.indexOf(":");
2181
+ const name2 = colon > 0 ? line.slice(0, colon) : "";
2182
+ if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
2183
+ headers.push([name2, line.slice(colon + 1).trim()]);
2184
+ }
2185
+ return headers;
2186
+ }
2187
+ function fieldValues(headers, name2) {
2188
+ const lower = name2.toLowerCase();
2189
+ return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
2190
+ }
2191
+ function fieldTokens(headers, name2) {
2192
+ return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
2193
+ }
2194
+ function requestFraming(headers) {
2195
+ const codings = fieldTokens(headers, "transfer-encoding");
2196
+ const lengths = fieldValues(headers, "content-length");
2197
+ if (codings.length > 0) {
2198
+ if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
2199
+ if (codings.length !== 1 || codings[0] !== "chunked") {
2200
+ throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
2201
+ }
2202
+ return { kind: "chunked" };
2203
+ }
2204
+ if (lengths.length === 0) return { kind: "none" };
2205
+ const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
2206
+ const [only] = distinct;
2207
+ if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
2208
+ throw new HttpError(400, "invalid Content-Length");
2209
+ }
2210
+ const length2 = Number(only);
2211
+ return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
2212
+ }
2213
+ var MAX_CHUNK_LINE = 4096;
2214
+ var MAX_TRAILERS = 64 * 1024;
2215
+ async function readBody(incoming, framing, cap, signal) {
2216
+ if (framing.kind === "none") return void 0;
2217
+ if (framing.kind === "length") {
2218
+ if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
2219
+ return incoming.exactly(framing.length, signal);
2220
+ }
2221
+ const chunks2 = [];
2222
+ let total = 0;
2223
+ for (; ; ) {
2224
+ const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
2225
+ const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
2226
+ if (!size) throw new HttpError(400, "malformed chunk size");
2227
+ const n = Number.parseInt(size[1], 16);
2228
+ if (n === 0) break;
2229
+ total += n;
2230
+ if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
2231
+ chunks2.push(await incoming.exactly(n, signal));
2232
+ if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
2233
+ }
2234
+ let trailers = 0;
2235
+ for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
2236
+ trailers += line.length;
2237
+ if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
2238
+ line = await incoming.line(MAX_TRAILERS, signal);
2239
+ }
2240
+ return concat(chunks2, total);
2241
+ }
2242
+ function concat(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
2243
+ const out = new Uint8Array(total);
2244
+ let at = 0;
2245
+ for (const chunk2 of chunks2) {
2246
+ out.set(chunk2, at);
2247
+ at += chunk2.length;
2248
+ }
2249
+ return out;
2250
+ }
2251
+ function safeValue(value) {
2252
+ return !/[\r\n\0]/.test(value);
2253
+ }
2254
+ function responseHead(status, reason, headers) {
2255
+ const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
2256
+ let head = `HTTP/1.1 ${status} ${phrase}\r
2257
+ `;
2258
+ for (const [name2, value] of headers) {
2259
+ if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
2260
+ `;
2261
+ }
2262
+ return latin1Bytes(`${head}\r
2263
+ `);
2264
+ }
2265
+ function chunk(bytes) {
2266
+ const size = latin1Bytes(`${bytes.length.toString(16)}\r
2267
+ `);
2268
+ const out = new Uint8Array(size.length + bytes.length + 2);
2269
+ out.set(size);
2270
+ out.set(bytes, size.length);
2271
+ out[out.length - 2] = CR;
2272
+ out[out.length - 1] = LF;
2273
+ return out;
2274
+ }
2275
+ var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
2276
+
2277
+ // src/cdp/types.ts
2278
+ var CDP_PORT = 9222;
2279
+ var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
2280
+
2281
+ // src/cdp/websocket.ts
2282
+ var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
2283
+ var MAX_MESSAGE = 256 * 1024 * 1024;
2284
+ var CLOSE = {
2285
+ normal: 1e3,
2286
+ protocol: 1002,
2287
+ unsupported: 1003,
2288
+ invalid: 1007,
2289
+ tooBig: 1009,
2290
+ error: 1011
2291
+ };
2292
+ var OP_TEXT = 1;
2293
+ var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
2294
+ var WsError = class extends Error {
2295
+ code;
2296
+ constructor(code, message2) {
2297
+ super(message2);
2298
+ this.code = code;
2299
+ }
2300
+ };
2301
+ function field(headers, name2) {
2302
+ return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
2303
+ }
2304
+ async function acceptKey(headers) {
2305
+ const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
2306
+ if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
2307
+ throw new HttpError(400, "expected a WebSocket upgrade");
2308
+ }
2309
+ if (field(headers, "sec-websocket-version") !== "13") {
2310
+ throw new HttpError(400, "Sec-WebSocket-Version must be 13");
2311
+ }
2312
+ const key = field(headers, "sec-websocket-key") ?? "";
2313
+ if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
2314
+ const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
2315
+ return btoa(String.fromCharCode(...new Uint8Array(digest)));
2316
+ }
2317
+ function isUpgrade(headers) {
2318
+ return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
2319
+ }
2320
+ function frame(opcode, payload) {
2321
+ const n = payload.length;
2322
+ const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
2323
+ const out = new Uint8Array(size + n);
2324
+ const view = new DataView(out.buffer);
2325
+ out[0] = 128 | opcode;
2326
+ if (n < 126) out[1] = n;
2327
+ else if (n < 65536) {
2328
+ out[1] = 126;
2329
+ view.setUint16(2, n);
2330
+ } else {
2331
+ out[1] = 127;
2332
+ view.setBigUint64(2, BigInt(n));
2333
+ }
2334
+ out.set(payload, size);
2335
+ return out;
2336
+ }
2337
+ function closeFrame(code, reason = "") {
2338
+ let bytes = new TextEncoder().encode(reason);
2339
+ if (bytes.length > 123) bytes = bytes.subarray(0, 123);
2340
+ const payload = new Uint8Array(2 + bytes.length);
2341
+ new DataView(payload.buffer).setUint16(0, code);
2342
+ payload.set(bytes, 2);
2343
+ return frame(OP.close, payload);
2344
+ }
2345
+ function pongFrame(payload) {
2346
+ return frame(OP.pong, payload);
2347
+ }
2348
+ async function fill(incoming, out, signal) {
2349
+ for (let at = 0; at < out.length; ) {
2350
+ const piece = await incoming.some(out.length - at, signal);
2351
+ if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
2352
+ out.set(piece, at);
2353
+ at += piece.length;
2354
+ }
2355
+ }
2356
+ var fatal = new TextDecoder("utf-8", { fatal: true });
2357
+ function decode(bytes) {
2358
+ try {
2359
+ return fatal.decode(bytes);
2360
+ } catch {
2361
+ throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
2362
+ }
2363
+ }
2364
+ var WsReader = class {
2365
+ incoming;
2366
+ max;
2367
+ parts = [];
2368
+ size = 0;
2369
+ continuing = false;
2370
+ constructor(incoming, max = MAX_MESSAGE) {
2371
+ this.incoming = incoming;
2372
+ this.max = max;
2373
+ }
2374
+ async next(signal) {
2375
+ for (; ; ) {
2376
+ const first = await this.incoming.some(1, signal);
2377
+ if (first.length === 0) return null;
2378
+ const head = new Uint8Array(2);
2379
+ head[0] = first[0];
2380
+ await fill(this.incoming, head.subarray(1), signal);
2381
+ const fin = (head[0] & 128) !== 0;
2382
+ const opcode = head[0] & 15;
2383
+ if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
2384
+ if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
2385
+ const length2 = await this.length(head[1] & 127, signal);
2386
+ const control = opcode >= 8;
2387
+ if (control && (length2 > 125 || !fin)) {
2388
+ throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
2389
+ }
2390
+ if (!control && this.size + length2 > this.max) {
2391
+ throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
2392
+ }
2393
+ const mask = new Uint8Array(4);
2394
+ await fill(this.incoming, mask, signal);
2395
+ const payload = new Uint8Array(length2);
2396
+ await fill(this.incoming, payload, signal);
2397
+ for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
2398
+ const event = this.take(opcode, fin, payload);
2399
+ if (event) return event;
2400
+ }
2401
+ }
2402
+ async length(short, signal) {
2403
+ if (short < 126) return short;
2404
+ const ext = new Uint8Array(short === 126 ? 2 : 8);
2405
+ await fill(this.incoming, ext, signal);
2406
+ const view = new DataView(ext.buffer);
2407
+ if (short === 126) return view.getUint16(0);
2408
+ const long = view.getBigUint64(0);
2409
+ return long > BigInt(this.max) ? this.max + 1 : Number(long);
2410
+ }
2411
+ take(opcode, fin, payload) {
2412
+ switch (opcode) {
2413
+ case OP.ping:
2414
+ return { kind: "ping", payload };
2415
+ case OP.pong:
2416
+ return void 0;
2417
+ case OP.close: {
2418
+ if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
2419
+ const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
2420
+ return { kind: "close", code, reason: decode(payload.subarray(2)) };
2421
+ }
2422
+ case OP.text:
2423
+ case OP.continuation: {
2424
+ if (opcode === OP.continuation !== this.continuing) {
2425
+ throw new WsError(CLOSE.protocol, "unexpected continuation frame");
2426
+ }
2427
+ this.parts.push(payload);
2428
+ this.size += payload.length;
2429
+ this.continuing = !fin;
2430
+ if (!fin) return void 0;
2431
+ const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
2432
+ this.parts = [];
2433
+ this.size = 0;
2434
+ return { kind: "text", text: decode(whole) };
2435
+ }
2436
+ case OP.binary:
2437
+ throw new WsError(CLOSE.unsupported, "CDP messages are text");
2438
+ default:
2439
+ throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
2440
+ }
2441
+ }
2442
+ };
2443
+ function switchingHead(accept2) {
2444
+ return latin1Bytes(
2445
+ `HTTP/1.1 101 Switching Protocols\r
2446
+ Upgrade: websocket\r
2447
+ Connection: Upgrade\r
2448
+ Sec-WebSocket-Accept: ${accept2}\r
2449
+ \r
2450
+ `
2451
+ );
2452
+ }
2453
+
2454
+ // src/cdp/facade.ts
2455
+ var MAX_HEAD = 64 * 1024;
2456
+ var LIST_TIMEOUT = 1e4;
2457
+ function respond(status, body, type = "text/plain; charset=utf-8") {
2458
+ const bytes = new TextEncoder().encode(body);
2459
+ const head = latin1Bytes(
2460
+ `HTTP/1.1 ${status} ${REASON[status]}\r
2461
+ Content-Type: ${type}\r
2462
+ Content-Length: ${bytes.length}\r
2463
+ Connection: close\r
2464
+ \r
2465
+ `
2466
+ );
2467
+ const out = new Uint8Array(head.length + bytes.length);
2468
+ out.set(head);
2469
+ out.set(bytes, head.length);
2470
+ return out;
2471
+ }
2472
+ function json(value) {
2473
+ return respond(200, `${JSON.stringify(value, null, 2)}
2474
+ `, "application/json; charset=UTF-8");
2475
+ }
2476
+ var CdpFacade = class {
2477
+ options;
2478
+ listener;
2479
+ stop = new AbortController();
2480
+ connections = /* @__PURE__ */ new Set();
2481
+ closed;
2482
+ constructor(options) {
2483
+ this.options = options;
2484
+ this.listener = options.net.listen({
2485
+ family: "inet",
2486
+ host: "127.0.0.1",
2487
+ port: options.port ?? CDP_PORT
2488
+ });
2489
+ this.closed = this.acceptLoop();
2490
+ }
2491
+ close() {
2492
+ if (this.stop.signal.aborted) return;
2493
+ this.stop.abort();
2494
+ this.listener.close();
2495
+ for (const conn of this.connections) conn.close();
2496
+ }
2497
+ async acceptLoop() {
2498
+ const served = /* @__PURE__ */ new Set();
2499
+ for (; ; ) {
2500
+ let conn;
2501
+ try {
2502
+ conn = await this.listener.accept(this.stop.signal);
2503
+ } catch {
2504
+ break;
2505
+ }
2506
+ const done4 = this.serve(conn).finally(() => served.delete(done4));
2507
+ served.add(done4);
2508
+ }
2509
+ await Promise.allSettled([...served]);
2510
+ }
2511
+ async serve(conn) {
2512
+ this.connections.add(conn);
2513
+ const incoming = new Incoming(conn);
2514
+ try {
2515
+ const head = await incoming.head(MAX_HEAD, this.stop.signal);
2516
+ if (head) await this.route(conn, incoming, parseRequestHead(head));
2517
+ } catch (e) {
2518
+ if (e instanceof HttpError) await this.write(conn, respond(e.status, `${e.message}
2519
+ `));
2520
+ } finally {
2521
+ this.connections.delete(conn);
2522
+ conn.close();
2523
+ }
2524
+ }
2525
+ async write(conn, bytes) {
2526
+ try {
2527
+ await conn.write(bytes, this.stop.signal);
2528
+ } catch {
2529
+ }
2530
+ }
2531
+ async route(conn, incoming, req) {
2532
+ const url = new URL(req.target, "http://127.0.0.1");
2533
+ const runtime = url.searchParams.get("runtime") ?? void 0;
2534
+ const host = req.headers.find(([n]) => n.toLowerCase() === "host")?.[1] ?? "127.0.0.1";
2535
+ if (req.method !== "GET") throw new HttpError(405, `${req.method} is not supported here`);
2536
+ const { hosts } = this.options;
2537
+ if (url.pathname === hosts.path && isUpgrade(req.headers)) {
2538
+ return this.upgrade(conn, incoming, req, runtime);
2539
+ }
2540
+ const path = url.pathname.replace(/\/+$/, "");
2541
+ if (path !== "/json/version" && path !== "/json/list" && path !== "/json") {
2542
+ throw new HttpError(404, "not found: try /json/version");
2543
+ }
2544
+ if (!this.options.hosts.available(runtime)) {
2545
+ throw new HttpError(503, NO_CDP_HOST);
2546
+ }
2547
+ if (path === "/json/version") {
2548
+ await this.write(
2549
+ conn,
2550
+ json({
2551
+ Browser: "slicc-kernel",
2552
+ "Protocol-Version": "1.3",
2553
+ webSocketDebuggerUrl: `ws://${host}${hosts.path}${url.search}`
2554
+ })
2555
+ );
2556
+ return;
2557
+ }
2558
+ await this.write(conn, json(await this.list(runtime)));
2559
+ }
2560
+ async list(runtime) {
2561
+ const conn = await this.host(runtime);
2562
+ try {
2563
+ const reply = await new Promise(
2564
+ (resolve, reject) => {
2565
+ const timer = setTimeout(
2566
+ () => reject(new HttpError(504, "the CDP host did not answer Target.getTargets")),
2567
+ this.options.listTimeout ?? LIST_TIMEOUT
2568
+ );
2569
+ conn.onclose = (reason) => {
2570
+ clearTimeout(timer);
2571
+ reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
2572
+ };
2573
+ conn.onmessage = (message2) => {
2574
+ const parsed = JSON.parse(message2);
2575
+ if (parsed.id !== 1) return;
2576
+ clearTimeout(timer);
2577
+ resolve(parsed);
2578
+ };
2579
+ conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
2580
+ }
2581
+ );
2582
+ if (!reply.result) {
2583
+ throw new HttpError(502, `Target.getTargets failed: ${JSON.stringify(reply.error)}`);
2584
+ }
2585
+ return (reply.result.targetInfos ?? []).map((t) => ({
2586
+ description: "",
2587
+ id: t.targetId,
2588
+ title: t.title,
2589
+ type: t.type,
2590
+ url: t.url
2591
+ }));
2592
+ } finally {
2593
+ conn.close();
2594
+ }
2595
+ }
2596
+ host(runtime) {
2597
+ return this.options.hosts.connect(runtime ? { runtime } : {});
2598
+ }
2599
+ async upgrade(conn, incoming, req, runtime) {
2600
+ const accept2 = await acceptKey(req.headers);
2601
+ const host = await this.host(runtime);
2602
+ await new Relay(conn, incoming, host, this.options.maxMessage ?? MAX_MESSAGE, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
2603
+ }
2604
+ };
2605
+ var Relay = class {
2606
+ writes = Promise.resolve();
2607
+ ended = false;
2608
+ conn;
2609
+ incoming;
2610
+ host;
2611
+ max;
2612
+ stop;
2613
+ constructor(conn, incoming, host, max, stop) {
2614
+ this.conn = conn;
2615
+ this.incoming = incoming;
2616
+ this.host = host;
2617
+ this.max = max;
2618
+ this.stop = stop;
2619
+ }
2620
+ send(bytes) {
2621
+ this.writes = this.writes.then(() => this.conn.write(bytes, this.stop)).catch(() => void 0);
2622
+ return this.writes;
2623
+ }
2624
+ end(code, reason) {
2625
+ if (this.ended) return this.writes;
2626
+ this.ended = true;
2627
+ return this.send(closeFrame(code, reason));
2628
+ }
2629
+ async start(head) {
2630
+ await this.send(head);
2631
+ const hostGone = new Promise((resolve) => {
2632
+ this.host.onclose = (reason) => {
2633
+ void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
2634
+ };
2635
+ });
2636
+ this.host.onmessage = (message2) => {
2637
+ if (this.ended) return;
2638
+ const bytes = new TextEncoder().encode(message2);
2639
+ if (bytes.length > this.max)
2640
+ void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
2641
+ else void this.send(frame(OP_TEXT, bytes));
2642
+ };
2643
+ await Promise.race([this.pump(), hostGone]);
2644
+ await this.writes;
2645
+ }
2646
+ async pump() {
2647
+ const reader = new WsReader(this.incoming, this.max);
2648
+ try {
2649
+ for (; ; ) {
2650
+ const event = await reader.next(this.stop);
2651
+ if (!event) return;
2652
+ if (event.kind === "text") this.host.send(event.text);
2653
+ else if (event.kind === "ping") void this.send(pongFrame(event.payload));
2654
+ else {
2655
+ await this.end(event.code === 1005 ? CLOSE.normal : event.code, "");
2656
+ return;
2657
+ }
2658
+ }
2659
+ } catch (e) {
2660
+ if (e instanceof WsError) await this.end(e.code, e.message);
2661
+ }
2662
+ }
2663
+ };
2664
+ function enableCdp(net, hosts) {
2665
+ let facade;
2666
+ net.kernelOnly(CDP_PORT, true);
2667
+ net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
2668
+ const started = new CdpFacade({ net, hosts });
2669
+ facade = started;
2670
+ void started.closed.then(() => {
2671
+ if (facade === started) facade = void 0;
2672
+ });
2673
+ });
2674
+ return () => facade;
2675
+ }
2676
+
2677
+ // src/cdp/hosts.ts
2678
+ var CdpHosts = class {
2679
+ embedder;
2680
+ clients = [];
2681
+ closed = false;
2682
+ path = `/devtools/browser/${crypto.randomUUID()}`;
2683
+ url = `ws://127.0.0.1:${CDP_PORT}${this.path}`;
2684
+ constructor(hook) {
2685
+ this.embedder = hook ? { hook, runtime: void 0, open: /* @__PURE__ */ new Set() } : void 0;
2686
+ }
2687
+ register(hook, runtime) {
2688
+ const entry = { hook, runtime, open: /* @__PURE__ */ new Set() };
2689
+ this.clients.push(entry);
2690
+ return () => {
2691
+ const at = this.clients.indexOf(entry);
2692
+ if (at >= 0) this.clients.splice(at, 1);
2693
+ closeAll(entry);
2694
+ };
2695
+ }
2696
+ pick(runtime) {
2697
+ if (this.closed) return void 0;
2698
+ const named = runtime ? this.clients.findLast((c) => c.runtime === runtime) : void 0;
2699
+ return named ?? this.embedder ?? this.clients.at(-1);
2700
+ }
2701
+ available(runtime) {
2702
+ return this.pick(runtime) !== void 0;
2703
+ }
2704
+ async connect(request) {
2705
+ const entry = this.pick(request.runtime);
2706
+ if (!entry) throw new HttpError(503, NO_CDP_HOST);
2707
+ let conn;
2708
+ try {
2709
+ conn = await entry.hook(request.runtime ? { runtime: request.runtime } : {});
2710
+ } catch (e) {
2711
+ throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
2712
+ }
2713
+ if (this.closed || !(entry === this.embedder || this.clients.includes(entry))) {
2714
+ conn.close();
2715
+ throw new HttpError(503, NO_CDP_HOST);
2716
+ }
2717
+ entry.open.add(conn);
2718
+ const close = conn.close.bind(conn);
2719
+ conn.close = () => {
2720
+ if (entry.open.delete(conn)) close();
2721
+ };
2722
+ return conn;
2723
+ }
2724
+ close() {
2725
+ this.closed = true;
2726
+ for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
2727
+ }
2728
+ };
2729
+ function closeAll(entry) {
2730
+ for (const conn of [...entry.open]) {
2731
+ conn.close();
2732
+ conn.onclose?.("the CDP host is gone");
2733
+ }
2734
+ }
2735
+
2736
+ // src/commands.ts
2737
+ var NAME = /^[A-Za-z0-9._+-]+$/;
2738
+ var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
2739
+ var PACKAGE = /\$\{package\}/g;
2740
+ function inside(pkg, rel2) {
2741
+ if (typeof rel2 !== "string" || rel2 === "") return void 0;
2742
+ const clean = rel2.replace(/^\.\//, "");
2743
+ if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
2744
+ return `${pkg}/${clean}`;
2745
+ }
2746
+ function envOf(pkg, raw) {
2747
+ const env = {};
2748
+ if (!raw || typeof raw !== "object") return env;
2749
+ for (const [key, value] of Object.entries(raw)) {
2750
+ if (!ENV_KEY.test(key)) continue;
2751
+ if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
2752
+ else if (value === null) env[key] = null;
2753
+ }
2754
+ return env;
2755
+ }
2756
+ function envFields(merged) {
2757
+ const env = {};
2758
+ const unset = [];
2759
+ for (const [key, value] of Object.entries(merged)) {
2760
+ if (value === null) unset.push(key);
2761
+ else env[key] = value;
2762
+ }
2763
+ return {
2764
+ ...Object.keys(env).length > 0 ? { env } : {},
2765
+ ...unset.length > 0 ? { unset } : {}
2766
+ };
2767
+ }
2768
+ function argsOf(pkg, raw) {
2769
+ if (!Array.isArray(raw) || raw.length === 0) return void 0;
2770
+ if (!raw.every((arg) => typeof arg === "string")) return void 0;
2771
+ return raw.map((arg) => arg.replace(PACKAGE, pkg));
2772
+ }
2773
+ function abiOf(raw, fallback) {
2774
+ const abi = raw ?? fallback;
2775
+ return abi === "emscripten" || abi === "wasi" ? abi : void 0;
2776
+ }
2777
+ function commandOf(pkg, name2, raw, packageAbi, shared) {
2778
+ const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
2779
+ const script = inside(pkg, raw.script);
2780
+ if (script) {
2781
+ return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
2782
+ }
2783
+ const abi = abiOf(raw.abi, packageAbi);
2784
+ const wasm = inside(pkg, raw.wasm);
2785
+ const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
2786
+ if (!abi || !glue || !wasm) return void 0;
2787
+ const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
2788
+ const args = argsOf(pkg, raw.args);
2789
+ const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
2790
+ return {
2791
+ name: name2,
2792
+ abi,
2793
+ glue,
2794
+ wasm,
2795
+ argv0,
2796
+ ...args ? { args } : {},
2797
+ ...withEnv,
2798
+ ...imports ? { imports } : {}
2799
+ };
2800
+ }
2801
+ function commandsOf(pkg, manifest) {
2802
+ const slicc = manifest.slicc;
2803
+ if (!slicc || typeof slicc !== "object") return [];
2804
+ const packageAbi = abiOf(slicc.abi, "emscripten");
2805
+ const entries = slicc.commands;
2806
+ if (!packageAbi || !entries || typeof entries !== "object") return [];
2807
+ const shared = envOf(pkg, slicc.env);
2808
+ const out = [];
2809
+ for (const [name2, raw] of Object.entries(entries)) {
2810
+ if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
2811
+ continue;
2812
+ }
2813
+ const command = commandOf(pkg, name2, raw, packageAbi, shared);
2814
+ if (command) out.push(command);
2815
+ }
2816
+ return out;
2817
+ }
2818
+ async function withPackagePaths(fs, pkg, command) {
2819
+ if (!command.env) return command;
2820
+ const env = {};
2821
+ for (const [key, value] of Object.entries(command.env)) {
2822
+ const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
2823
+ env[key] = path && await fs.exists(path) ? path : value;
2824
+ }
2825
+ return { ...command, env };
2826
+ }
2827
+ async function packages(fs, modules) {
2828
+ const names = await fs.readdir(modules).catch(() => []);
2829
+ const dirs = [];
2830
+ for (const name2 of names) {
2831
+ if (name2.startsWith(".")) continue;
2832
+ if (!name2.startsWith("@")) {
2833
+ dirs.push(`${modules}/${name2}`);
2834
+ continue;
2835
+ }
2836
+ for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
2837
+ dirs.push(`${modules}/${name2}/${inner}`);
2838
+ }
2839
+ }
2840
+ return dirs;
2841
+ }
2842
+ async function pnpmGlobalRoots(fs, home) {
2843
+ const roots = [];
2844
+ const global = `${home}/global`;
2845
+ for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
2846
+ for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
2847
+ const dir = `${global}/${version}/${group}`;
2848
+ const st = await fs.lstat(dir).catch(() => void 0);
2849
+ if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
2850
+ roots.push(`${dir}/node_modules`);
1935
2851
  }
1936
2852
  }
1937
2853
  return roots;
@@ -2203,7 +3119,7 @@ var CONTINUED_STATUS = 65535;
2203
3119
  function waitStatus(code, termsig) {
2204
3120
  return termsig ? termsig & 127 : (code & 255) << 8;
2205
3121
  }
2206
- function concat(chunks2) {
3122
+ function concat2(chunks2) {
2207
3123
  const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
2208
3124
  let offset = 0;
2209
3125
  for (const chunk2 of chunks2) {
@@ -2360,7 +3276,7 @@ var ChildTable = class {
2360
3276
  if (!slots || !chunks2) return new Uint8Array(0);
2361
3277
  slots.delete(slot);
2362
3278
  if (slots.size === 0) this.leftovers.delete(pid);
2363
- return concat(chunks2);
3279
+ return concat2(chunks2);
2364
3280
  }
2365
3281
  };
2366
3282
 
@@ -2528,7 +3444,7 @@ function statOf(attr, path, mount) {
2528
3444
  ...mount.caps.ranges ? { ranged: true } : {}
2529
3445
  };
2530
3446
  }
2531
- function concat2(chunks2, total) {
3447
+ function concat3(chunks2, total) {
2532
3448
  if (chunks2.length === 1 && chunks2[0]?.length === total) return chunks2[0];
2533
3449
  const out = new Uint8Array(total);
2534
3450
  let at = 0;
@@ -2823,7 +3739,7 @@ var MountFs = class {
2823
3739
  } finally {
2824
3740
  await mount.conn.call({ op: "release", fh });
2825
3741
  }
2826
- return concat2(chunks2, total);
3742
+ return concat3(chunks2, total);
2827
3743
  }
2828
3744
  async writeFile(path, content) {
2829
3745
  const real = await this.follow(path, 0, true);
@@ -2863,6 +3779,7 @@ var MountFs = class {
2863
3779
  async pread(path, offset, length2) {
2864
3780
  const real = await this.follow(path);
2865
3781
  const found = this.at(real);
3782
+ if (!found && this.base.pread) return this.base.pread(real, offset, length2);
2866
3783
  if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
2867
3784
  const { mount, rel: rel2 } = found;
2868
3785
  if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
@@ -2887,7 +3804,7 @@ var MountFs = class {
2887
3804
  } finally {
2888
3805
  await mount.conn.call({ op: "release", fh });
2889
3806
  }
2890
- return concat2(chunks2, total);
3807
+ return concat3(chunks2, total);
2891
3808
  }
2892
3809
  fits(mount, real, end) {
2893
3810
  if (mount.maxFile > 0 && end > mount.maxFile) {
@@ -2900,6 +3817,7 @@ var MountFs = class {
2900
3817
  async pwrite(path, offset, bytes, transfer = false) {
2901
3818
  const real = await this.follow(path, 0, true);
2902
3819
  const found = this.at(real);
3820
+ if (!found && this.base.pwrite) return this.base.pwrite(real, offset, bytes);
2903
3821
  if (!found)
2904
3822
  return this.base.writeFile(
2905
3823
  real,
@@ -2931,6 +3849,7 @@ var MountFs = class {
2931
3849
  async truncate(path, size) {
2932
3850
  const real = await this.follow(path);
2933
3851
  const found = this.at(real);
3852
+ if (!found && this.base.truncate) return this.base.truncate(real, size);
2934
3853
  if (!found?.mount.caps.ranges) {
2935
3854
  return this.writeFile(real, resized(await this.readFileBuffer(real), size));
2936
3855
  }
@@ -3140,7 +4059,7 @@ async function storageUsage() {
3140
4059
  return { quota: estimate.quota, usage: estimate.usage ?? 0 };
3141
4060
  }
3142
4061
  var done = { ok: true, kind: "void" };
3143
- function json(value) {
4062
+ function json2(value) {
3144
4063
  return { ok: true, kind: "json", json: value };
3145
4064
  }
3146
4065
  async function run(entry, path, req) {
@@ -3152,15 +4071,15 @@ async function run(entry, path, req) {
3152
4071
  await fs.writeFile(path, req.body ?? new Uint8Array(0));
3153
4072
  return done;
3154
4073
  case "exists":
3155
- return json(await fs.exists(path));
4074
+ return json2(await fs.exists(path));
3156
4075
  case "stat":
3157
- return json(statJson(await fs.stat(path)));
4076
+ return json2(statJson(await fs.stat(path)));
3158
4077
  case "lstat":
3159
- return json(statJson(await fs.lstat(path)));
4078
+ return json2(statJson(await fs.lstat(path)));
3160
4079
  case "readdir":
3161
- return json(await fs.readdir(path));
4080
+ return json2(await fs.readdir(path));
3162
4081
  case "readdir-stat":
3163
- return json(await readdirStat(fs, path));
4082
+ return json2(await readdirStat(fs, path));
3164
4083
  case "mkdir":
3165
4084
  await fs.mkdir(path, { recursive: true });
3166
4085
  return done;
@@ -3186,7 +4105,7 @@ async function run(entry, path, req) {
3186
4105
  await fs.symlink(req.arg2 ?? "", path);
3187
4106
  return done;
3188
4107
  case "readlink":
3189
- return json(await fs.readlink(path));
4108
+ return json2(await fs.readlink(path));
3190
4109
  case "chmod":
3191
4110
  await fs.chmod(path, req.mode ?? 0);
3192
4111
  return done;
@@ -3217,7 +4136,7 @@ async function dispatchSyncFs(req) {
3217
4136
  const { fs, cwd } = entry;
3218
4137
  try {
3219
4138
  const path = fs.resolvePath(cwd, req.path);
3220
- if (req.op === "statfs") return json(await entry.statfs?.(path) ?? await storageUsage());
4139
+ if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
3221
4140
  if (req.op === "hold") {
3222
4141
  entry.hold?.(path, req.mode === 1);
3223
4142
  return done;
@@ -3390,7 +4309,7 @@ var HOST_OPS = [
3390
4309
  "async-cancel",
3391
4310
  "async-close"
3392
4311
  ];
3393
- var json2 = (value) => ({ ok: true, kind: "json", json: value });
4312
+ var json3 = (value) => ({ ok: true, kind: "json", json: value });
3394
4313
  var done2 = { ok: true, kind: "void" };
3395
4314
  var LockTable = class {
3396
4315
  held = /* @__PURE__ */ new Map();
@@ -3439,353 +4358,116 @@ var AsyncOps = class {
3439
4358
  }
3440
4359
  resolve(value) {
3441
4360
  const id = this.hold();
3442
- this.complete(id, json2(value));
3443
- return id;
3444
- }
3445
- hold() {
3446
- const id = this.next++;
3447
- this.results.set(id, void 0);
3448
- return id;
3449
- }
3450
- take(id) {
3451
- if (!this.results.has(id)) throw new KernelError("EINVAL");
3452
- const result = this.results.get(id);
3453
- if (!result) throw new KernelError("EAGAIN");
3454
- this.cancel(id);
3455
- return result;
3456
- }
3457
- cancel(id) {
3458
- this.results.delete(id);
3459
- const at = this.ready.indexOf(id);
3460
- if (at >= 0) this.ready.splice(at, 1);
3461
- }
3462
- async wait(timeoutMs, signal) {
3463
- for (; ; ) {
3464
- if (this.closed) return -1;
3465
- const id = this.ready.shift();
3466
- if (id !== void 0) return id;
3467
- if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
3468
- const woke = await this.sleep(timeoutMs, signal);
3469
- if (!woke) return 0;
3470
- }
3471
- }
3472
- close() {
3473
- this.closed = true;
3474
- this.wake();
3475
- }
3476
- sleep(timeoutMs, signal) {
3477
- return new Promise((resolve, reject) => {
3478
- let timer;
3479
- const cleanup = () => {
3480
- clearTimeout(timer);
3481
- this.waiters.delete(wake);
3482
- signal.removeEventListener("abort", abort);
3483
- };
3484
- const wake = () => {
3485
- cleanup();
3486
- resolve(true);
3487
- };
3488
- const abort = () => {
3489
- cleanup();
3490
- reject(new KernelError("EINTR"));
3491
- };
3492
- this.waiters.add(wake);
3493
- signal.addEventListener("abort", abort, { once: true });
3494
- if (timeoutMs !== void 0) {
3495
- timer = setTimeout(() => {
3496
- cleanup();
3497
- resolve(false);
3498
- }, timeoutMs);
3499
- }
3500
- });
3501
- }
3502
- complete(id, result) {
3503
- if (!this.results.has(id)) return;
3504
- this.results.set(id, result);
3505
- this.ready.push(id);
3506
- this.wake();
3507
- }
3508
- wake() {
3509
- for (const wake of [...this.waiters]) wake();
3510
- }
3511
- };
3512
- async function hostSyscall(req, ctx) {
3513
- switch (req.op) {
3514
- case "lock":
3515
- if (!ctx.locks) throw new KernelError("ENOSYS");
3516
- if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
3517
- throw new KernelError("EAGAIN");
3518
- return done2;
3519
- case "unlock":
3520
- ctx.locks?.unlock(ctx.pid, req.path);
3521
- return done2;
3522
- case "async-submit":
3523
- if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
3524
- return json2(ctx.ops.submit(req.req));
3525
- case "async-wait":
3526
- return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
3527
- case "async-take":
3528
- return ctx.ops.take(req.id);
3529
- case "async-resolve":
3530
- return json2(ctx.ops.resolve(req.value));
3531
- case "async-hold":
3532
- return json2(ctx.ops.hold());
3533
- case "async-cancel":
3534
- ctx.ops.cancel(req.id);
3535
- return done2;
3536
- case "async-close":
3537
- ctx.ops.close();
3538
- return done2;
3539
- }
3540
- }
3541
-
3542
- // src/kernel/net/loopback-names.ts
3543
- var HOST_LOOPBACK = "host.slicc.internal";
3544
- var PAGE_LOOPBACK = "kernel.localhost";
3545
- var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
3546
- function toHostLoopback(href) {
3547
- const url = new URL(href);
3548
- if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
3549
- url.hostname = "127.0.0.1";
3550
- return url.href;
3551
- }
3552
-
3553
- // src/kernel/net/http1.ts
3554
- var HttpError = class extends Error {
3555
- status;
3556
- constructor(status, message2) {
3557
- super(message2);
3558
- this.status = status;
3559
- }
3560
- };
3561
- var CR = 13;
3562
- var LF = 10;
3563
- var READ_SIZE = 64 * 1024;
3564
- var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
3565
- var REASON = {
3566
- 100: "Continue",
3567
- 200: "OK",
3568
- 400: "Bad Request",
3569
- 403: "Forbidden",
3570
- 408: "Request Timeout",
3571
- 411: "Length Required",
3572
- 413: "Content Too Large",
3573
- 417: "Expectation Failed",
3574
- 421: "Misdirected Request",
3575
- 431: "Request Header Fields Too Large",
3576
- 501: "Not Implemented",
3577
- 502: "Bad Gateway",
3578
- 503: "Service Unavailable",
3579
- 505: "HTTP Version Not Supported"
3580
- };
3581
- var Incoming = class {
3582
- source;
3583
- buf = new Uint8Array(0);
3584
- eof = false;
3585
- constructor(source) {
3586
- this.source = source;
3587
- }
3588
- get buffered() {
3589
- return this.buf.length;
3590
- }
3591
- async fill(signal) {
3592
- if (this.eof) return false;
3593
- const chunk2 = await this.source.read(READ_SIZE, signal);
3594
- if (chunk2.length === 0) {
3595
- this.eof = true;
3596
- return false;
3597
- }
3598
- const next = new Uint8Array(this.buf.length + chunk2.length);
3599
- next.set(this.buf);
3600
- next.set(chunk2, this.buf.length);
3601
- this.buf = next;
3602
- return true;
3603
- }
3604
- take(n) {
3605
- const out = this.buf.slice(0, n);
3606
- this.buf = this.buf.subarray(n);
3607
- return out;
3608
- }
3609
- async some(max, signal) {
3610
- if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
3611
- if (this.eof) return new Uint8Array(0);
3612
- const chunk2 = await this.source.read(max, signal);
3613
- if (chunk2.length === 0) this.eof = true;
3614
- return chunk2;
3615
- }
3616
- async head(limit, signal) {
3617
- let from = 0;
3618
- for (; ; ) {
3619
- const end = headEnd(this.buf, from);
3620
- if (end > 0) {
3621
- if (end > limit) throw new HttpError(431, "request head too large");
3622
- return this.take(end);
3623
- }
3624
- if (this.buf.length > limit) throw new HttpError(431, "request head too large");
3625
- from = Math.max(0, this.buf.length - 3);
3626
- if (!await this.fill(signal)) {
3627
- if (this.buf.length === 0) return null;
3628
- throw new HttpError(400, "connection closed inside a request head");
3629
- }
3630
- }
3631
- }
3632
- async line(limit, signal) {
3633
- let from = 0;
3634
- for (; ; ) {
3635
- const at = this.buf.indexOf(LF, from);
3636
- if (at >= 0) {
3637
- const line = this.take(at + 1);
3638
- const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
3639
- return text;
3640
- }
3641
- if (this.buf.length > limit) throw new HttpError(400, "line too long");
3642
- from = this.buf.length;
3643
- if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
3644
- }
3645
- }
3646
- async exactly(n, signal) {
3647
- while (this.buf.length < n) {
3648
- if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
3649
- }
3650
- return this.take(n);
4361
+ this.complete(id, json3(value));
4362
+ return id;
3651
4363
  }
3652
- };
3653
- function headEnd(buf, from) {
3654
- for (let i = from; i < buf.length; i++) {
3655
- if (buf[i] !== LF) continue;
3656
- if (buf[i + 1] === LF) return i + 2;
3657
- if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
4364
+ hold() {
4365
+ const id = this.next++;
4366
+ this.results.set(id, void 0);
4367
+ return id;
3658
4368
  }
3659
- return 0;
3660
- }
3661
- function latin1(bytes) {
3662
- let out = "";
3663
- for (let i = 0; i < bytes.length; i += 32768) {
3664
- out += String.fromCharCode(...bytes.subarray(i, i + 32768));
4369
+ take(id) {
4370
+ if (!this.results.has(id)) throw new KernelError("EINVAL");
4371
+ const result = this.results.get(id);
4372
+ if (!result) throw new KernelError("EAGAIN");
4373
+ this.cancel(id);
4374
+ return result;
3665
4375
  }
3666
- return out;
3667
- }
3668
- function latin1Bytes(text) {
3669
- const out = new Uint8Array(text.length);
3670
- for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
3671
- return out;
3672
- }
3673
- function parseRequestHead(bytes) {
3674
- const lines = latin1(bytes).split(/\r?\n/);
3675
- while (lines.length > 0 && lines[0] === "") lines.shift();
3676
- const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
3677
- if (!match) throw new HttpError(400, "malformed request line");
3678
- if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
3679
- return {
3680
- method: match[1],
3681
- target: match[2],
3682
- minor: Number(match[4]),
3683
- headers: parseFields(lines)
3684
- };
3685
- }
3686
- function parseFields(lines) {
3687
- const headers = [];
3688
- for (const line of lines) {
3689
- if (line === "") continue;
3690
- if (line.startsWith(" ") || line.startsWith(" ")) {
3691
- throw new HttpError(400, "obsolete line folding");
3692
- }
3693
- const colon = line.indexOf(":");
3694
- const name2 = colon > 0 ? line.slice(0, colon) : "";
3695
- if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
3696
- headers.push([name2, line.slice(colon + 1).trim()]);
4376
+ cancel(id) {
4377
+ this.results.delete(id);
4378
+ const at = this.ready.indexOf(id);
4379
+ if (at >= 0) this.ready.splice(at, 1);
3697
4380
  }
3698
- return headers;
3699
- }
3700
- function fieldValues(headers, name2) {
3701
- const lower = name2.toLowerCase();
3702
- return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
3703
- }
3704
- function fieldTokens(headers, name2) {
3705
- return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
3706
- }
3707
- function requestFraming(headers) {
3708
- const codings = fieldTokens(headers, "transfer-encoding");
3709
- const lengths = fieldValues(headers, "content-length");
3710
- if (codings.length > 0) {
3711
- if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
3712
- if (codings.length !== 1 || codings[0] !== "chunked") {
3713
- throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
4381
+ async wait(timeoutMs, signal) {
4382
+ for (; ; ) {
4383
+ if (this.closed) return -1;
4384
+ const id = this.ready.shift();
4385
+ if (id !== void 0) return id;
4386
+ if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
4387
+ const woke = await this.sleep(timeoutMs, signal);
4388
+ if (!woke) return 0;
3714
4389
  }
3715
- return { kind: "chunked" };
3716
- }
3717
- if (lengths.length === 0) return { kind: "none" };
3718
- const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
3719
- const [only] = distinct;
3720
- if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
3721
- throw new HttpError(400, "invalid Content-Length");
3722
4390
  }
3723
- const length2 = Number(only);
3724
- return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
3725
- }
3726
- var MAX_CHUNK_LINE = 4096;
3727
- var MAX_TRAILERS = 64 * 1024;
3728
- async function readBody(incoming, framing, cap, signal) {
3729
- if (framing.kind === "none") return void 0;
3730
- if (framing.kind === "length") {
3731
- if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
3732
- return incoming.exactly(framing.length, signal);
4391
+ close() {
4392
+ this.closed = true;
4393
+ this.wake();
3733
4394
  }
3734
- const chunks2 = [];
3735
- let total = 0;
3736
- for (; ; ) {
3737
- const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
3738
- const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
3739
- if (!size) throw new HttpError(400, "malformed chunk size");
3740
- const n = Number.parseInt(size[1], 16);
3741
- if (n === 0) break;
3742
- total += n;
3743
- if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
3744
- chunks2.push(await incoming.exactly(n, signal));
3745
- if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
4395
+ sleep(timeoutMs, signal) {
4396
+ return new Promise((resolve, reject) => {
4397
+ let timer;
4398
+ const cleanup = () => {
4399
+ clearTimeout(timer);
4400
+ this.waiters.delete(wake);
4401
+ signal.removeEventListener("abort", abort);
4402
+ };
4403
+ const wake = () => {
4404
+ cleanup();
4405
+ resolve(true);
4406
+ };
4407
+ const abort = () => {
4408
+ cleanup();
4409
+ reject(new KernelError("EINTR"));
4410
+ };
4411
+ this.waiters.add(wake);
4412
+ signal.addEventListener("abort", abort, { once: true });
4413
+ if (timeoutMs !== void 0) {
4414
+ timer = setTimeout(() => {
4415
+ cleanup();
4416
+ resolve(false);
4417
+ }, timeoutMs);
4418
+ }
4419
+ });
3746
4420
  }
3747
- let trailers = 0;
3748
- for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
3749
- trailers += line.length;
3750
- if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
3751
- line = await incoming.line(MAX_TRAILERS, signal);
4421
+ complete(id, result) {
4422
+ if (!this.results.has(id)) return;
4423
+ this.results.set(id, result);
4424
+ this.ready.push(id);
4425
+ this.wake();
3752
4426
  }
3753
- return concat3(chunks2, total);
3754
- }
3755
- function concat3(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
3756
- const out = new Uint8Array(total);
3757
- let at = 0;
3758
- for (const chunk2 of chunks2) {
3759
- out.set(chunk2, at);
3760
- at += chunk2.length;
4427
+ wake() {
4428
+ for (const wake of [...this.waiters]) wake();
3761
4429
  }
3762
- return out;
3763
- }
3764
- function safeValue(value) {
3765
- return !/[\r\n\0]/.test(value);
3766
- }
3767
- function responseHead(status, reason, headers) {
3768
- const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
3769
- let head = `HTTP/1.1 ${status} ${phrase}\r
3770
- `;
3771
- for (const [name2, value] of headers) {
3772
- if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
3773
- `;
4430
+ };
4431
+ async function hostSyscall(req, ctx) {
4432
+ switch (req.op) {
4433
+ case "lock":
4434
+ if (!ctx.locks) throw new KernelError("ENOSYS");
4435
+ if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
4436
+ throw new KernelError("EAGAIN");
4437
+ return done2;
4438
+ case "unlock":
4439
+ ctx.locks?.unlock(ctx.pid, req.path);
4440
+ return done2;
4441
+ case "async-submit":
4442
+ if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
4443
+ return json3(ctx.ops.submit(req.req));
4444
+ case "async-wait":
4445
+ return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
4446
+ case "async-take":
4447
+ return ctx.ops.take(req.id);
4448
+ case "async-resolve":
4449
+ return json3(ctx.ops.resolve(req.value));
4450
+ case "async-hold":
4451
+ return json3(ctx.ops.hold());
4452
+ case "async-cancel":
4453
+ ctx.ops.cancel(req.id);
4454
+ return done2;
4455
+ case "async-close":
4456
+ ctx.ops.close();
4457
+ return done2;
3774
4458
  }
3775
- return latin1Bytes(`${head}\r
3776
- `);
3777
4459
  }
3778
- function chunk(bytes) {
3779
- const size = latin1Bytes(`${bytes.length.toString(16)}\r
3780
- `);
3781
- const out = new Uint8Array(size.length + bytes.length + 2);
3782
- out.set(size);
3783
- out.set(bytes, size.length);
3784
- out[out.length - 2] = CR;
3785
- out[out.length - 1] = LF;
3786
- return out;
4460
+
4461
+ // src/kernel/net/loopback-names.ts
4462
+ var HOST_LOOPBACK = "host.slicc.internal";
4463
+ var PAGE_LOOPBACK = "kernel.localhost";
4464
+ var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
4465
+ function toHostLoopback(href) {
4466
+ const url = new URL(href);
4467
+ if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
4468
+ url.hostname = "127.0.0.1";
4469
+ return url.href;
3787
4470
  }
3788
- var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
3789
4471
 
3790
4472
  // src/kernel/net/proxy-service.ts
3791
4473
  var REALM_PROXY_PORT = 3128;
@@ -5378,23 +6060,23 @@ function terminalOf(ctx, fd) {
5378
6060
  if (!tty) throw new KernelError("ENOTTY");
5379
6061
  return tty;
5380
6062
  }
5381
- var json3 = (value) => ({ ok: true, kind: "json", json: value });
6063
+ var json4 = (value) => ({ ok: true, kind: "json", json: value });
5382
6064
  var done3 = { ok: true, kind: "void" };
5383
6065
  function ptySyscall(req, ctx) {
5384
6066
  switch (req.op) {
5385
6067
  case "pty-open": {
5386
6068
  if (!ctx.ptys) throw new KernelError("ENOENT");
5387
- return json3(ctx.fds.install(ctx.ptys.open().master, 3));
6069
+ return json4(ctx.fds.install(ctx.ptys.open().master, 3));
5388
6070
  }
5389
6071
  case "pty-slave-open": {
5390
6072
  if (!ctx.ptys) throw new KernelError("ENOENT");
5391
6073
  const pair = ctx.ptys.get(req.n);
5392
6074
  const fd = ctx.fds.install(pair.openSlave(), 3);
5393
6075
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
5394
- return json3(fd);
6076
+ return json4(fd);
5395
6077
  }
5396
6078
  case "pty-number":
5397
- return json3(masterOf(ctx, req.fd).index);
6079
+ return json4(masterOf(ctx, req.fd).index);
5398
6080
  case "pty-lock":
5399
6081
  masterOf(ctx, req.fd).locked = req.lock;
5400
6082
  return done3;
@@ -5406,7 +6088,7 @@ function ptySyscall(req, ctx) {
5406
6088
  return done3;
5407
6089
  }
5408
6090
  case "pty-list":
5409
- return json3(ctx.ptys?.numbers() ?? []);
6091
+ return json4(ctx.ptys?.numbers() ?? []);
5410
6092
  case "pty-packet":
5411
6093
  masterOf(ctx, req.fd).packet = req.on;
5412
6094
  return done3;
@@ -5484,7 +6166,7 @@ function socketAt(fds, fd) {
5484
6166
  if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
5485
6167
  return file;
5486
6168
  }
5487
- var ok = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
6169
+ var ok = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
5488
6170
  async function socketSyscall(req, proc) {
5489
6171
  const { fds, net } = proc;
5490
6172
  switch (req.op) {
@@ -7448,8 +8130,8 @@ function umountCall(target, flags) {
7448
8130
  var FSTAB_PATH = "/etc/fstab";
7449
8131
  var FSTAB_RETRIES = [1e3, 4e3, 16e3];
7450
8132
  var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
7451
- function unescape(field) {
7452
- return field.replace(
8133
+ function unescape(field2) {
8134
+ return field2.replace(
7453
8135
  /\\([0-7]{3})/g,
7454
8136
  (_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
7455
8137
  );
@@ -8106,7 +8788,7 @@ function importedMemory(bytes) {
8106
8788
  const count = u32();
8107
8789
  for (let i = 0; i < count; i++) {
8108
8790
  const module = name2();
8109
- const field = name2();
8791
+ const field2 = name2();
8110
8792
  const kind = bytes[at++];
8111
8793
  if (kind === 0) u32();
8112
8794
  else if (kind === 1) {
@@ -8114,7 +8796,7 @@ function importedMemory(bytes) {
8114
8796
  limits();
8115
8797
  } else if (kind === 2) {
8116
8798
  const l = limits();
8117
- return { module, name: field, ...l };
8799
+ return { module, name: field2, ...l };
8118
8800
  } else if (kind === 3) at += 2;
8119
8801
  else if (kind === 4) {
8120
8802
  at++;
@@ -8205,12 +8887,12 @@ function readImports(r, types) {
8205
8887
  const foreign = {};
8206
8888
  for (let i = r.u32(); i > 0; i--) {
8207
8889
  const module = r.name();
8208
- const field = r.name();
8890
+ const field2 = r.name();
8209
8891
  const kind = r.byte();
8210
8892
  if (kind !== 0) skipImport(r, kind);
8211
8893
  else {
8212
8894
  const type = types[r.u32()] ?? "other";
8213
- if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field] = type;
8895
+ if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
8214
8896
  }
8215
8897
  }
8216
8898
  return foreign;
@@ -8317,6 +8999,7 @@ var Launcher = class {
8317
8999
  locks = new LockTable();
8318
9000
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
8319
9001
  net = new LoopbackNet();
9002
+ cdp;
8320
9003
  ca;
8321
9004
  transport;
8322
9005
  watchers = new FsWatchers();
@@ -8353,7 +9036,8 @@ var Launcher = class {
8353
9036
  this.processMounts = options.processMounts ?? true;
8354
9037
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
8355
9038
  this.modulesDir = options.modules ?? "/node_modules";
8356
- this.env = { ...networkEnv(), ...options.env };
9039
+ this.cdp = new CdpHosts(options.cdp);
9040
+ this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
8357
9041
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
8358
9042
  this.transport = options.transport ?? missingTransport();
8359
9043
  enableNetwork(this.net, {
@@ -8361,6 +9045,7 @@ var Launcher = class {
8361
9045
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
8362
9046
  ca: this.ca
8363
9047
  });
9048
+ enableCdp(this.net, this.cdp);
8364
9049
  this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
8365
9050
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
8366
9051
  this.catalog = void 0;
@@ -8894,40 +9579,106 @@ function valid(name2) {
8894
9579
  throw new TypeError(`invalid name: ${name2}`);
8895
9580
  }
8896
9581
  }
9582
+ var CHUNK3 = 1024 * 1024;
8897
9583
  function bytesOf(data) {
8898
9584
  if (typeof data === "string") return new TextEncoder().encode(data);
8899
9585
  if (data instanceof Blob) return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));
8900
9586
  if (ArrayBuffer.isView(data)) {
8901
- return new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
9587
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
8902
9588
  }
8903
- return new Uint8Array(data).slice();
9589
+ return new Uint8Array(data);
8904
9590
  }
9591
+ var isParams = (data) => ["write", "seek", "truncate"].includes(data?.type) && !(data instanceof Blob);
9592
+ var Chunks = class {
9593
+ blobs;
9594
+ size;
9595
+ constructor(blobs = [], size = 0) {
9596
+ this.blobs = blobs;
9597
+ this.size = size;
9598
+ }
9599
+ pad(index, length2) {
9600
+ const blob = this.blobs[index] ?? new Blob([]);
9601
+ if (blob.size < length2)
9602
+ this.blobs[index] = new Blob([blob, new Uint8Array(length2 - blob.size)]);
9603
+ }
9604
+ grow(end) {
9605
+ if (end <= this.size) return;
9606
+ const last = Math.floor((end - 1) / CHUNK3);
9607
+ for (let i = 0; i < last; i++) this.pad(i, CHUNK3);
9608
+ this.pad(last, end - last * CHUNK3);
9609
+ this.size = end;
9610
+ }
9611
+ async write(at, bytes) {
9612
+ if (bytes.length === 0) return;
9613
+ const first = Math.floor(at / CHUNK3);
9614
+ for (let i = 0; i < first; i++) this.pad(i, CHUNK3);
9615
+ for (let done4 = 0; done4 < bytes.length; ) {
9616
+ const index = Math.floor((at + done4) / CHUNK3);
9617
+ const from = at + done4 - index * CHUNK3;
9618
+ const n = Math.min(CHUNK3 - from, bytes.length - done4);
9619
+ const view = bytes.subarray(done4, done4 + n);
9620
+ const part = view.buffer instanceof ArrayBuffer ? view : view.slice();
9621
+ const old = this.blobs[index];
9622
+ if (from === 0 && n >= (old?.size ?? 0)) this.blobs[index] = new Blob([part]);
9623
+ else {
9624
+ const merged = new Uint8Array(Math.max(old?.size ?? 0, from + n));
9625
+ if (old) merged.set(new Uint8Array(await old.arrayBuffer()));
9626
+ merged.set(part, from);
9627
+ this.blobs[index] = new Blob([merged]);
9628
+ }
9629
+ done4 += n;
9630
+ }
9631
+ this.size = Math.max(this.size, at + bytes.length);
9632
+ }
9633
+ truncate(size) {
9634
+ if (size >= this.size) {
9635
+ this.grow(size);
9636
+ return;
9637
+ }
9638
+ const keep = Math.ceil(size / CHUNK3);
9639
+ this.blobs.length = keep;
9640
+ if (keep > 0)
9641
+ this.blobs[keep - 1] = this.blobs[keep - 1].slice(0, size - (keep - 1) * CHUNK3);
9642
+ this.size = size;
9643
+ }
9644
+ };
8905
9645
  var MemoryFile = class {
8906
9646
  kind = "file";
8907
9647
  name;
8908
9648
  parent;
8909
- bytes = new Uint8Array(0);
9649
+ data = new Chunks();
8910
9650
  lastModified = Date.now();
8911
9651
  constructor(name2, parent) {
8912
9652
  this.name = name2;
8913
9653
  this.parent = parent;
8914
9654
  }
8915
9655
  async getFile() {
8916
- return new File([this.bytes], this.name, { lastModified: this.lastModified });
8917
- }
8918
- async createWritable() {
8919
- const chunks2 = [];
9656
+ return new File(this.data.blobs, this.name, { lastModified: this.lastModified });
9657
+ }
9658
+ async createWritable({ keepExistingData = false } = {}) {
9659
+ const next = keepExistingData ? new Chunks([...this.data.blobs], this.data.size) : new Chunks();
9660
+ let position = 0;
9661
+ const put = async (data) => {
9662
+ const bytes = await bytesOf(data);
9663
+ await next.write(position, bytes);
9664
+ position += bytes.length;
9665
+ };
8920
9666
  return {
8921
- write: async (data) => void chunks2.push(await bytesOf(data)),
9667
+ write: async (data) => {
9668
+ if (!isParams(data)) return put(data);
9669
+ if (data.type === "truncate") return next.truncate(data.size ?? 0);
9670
+ position = data.position ?? position;
9671
+ if (data.type === "write") await put(data.data);
9672
+ },
9673
+ seek: async (at) => {
9674
+ position = at;
9675
+ },
9676
+ truncate: async (size) => next.truncate(size),
8922
9677
  close: async () => {
8923
- const out = new Uint8Array(chunks2.reduce((n, chunk2) => n + chunk2.length, 0));
8924
- let at = 0;
8925
- for (const chunk2 of chunks2) {
8926
- out.set(chunk2, at);
8927
- at += chunk2.length;
8928
- }
8929
- this.bytes = out;
9678
+ this.data = next;
8930
9679
  this.lastModified = Date.now();
9680
+ },
9681
+ abort: async () => {
8931
9682
  }
8932
9683
  };
8933
9684
  }
@@ -9020,6 +9771,9 @@ var MemoryDirectory = class _MemoryDirectory {
9020
9771
  function memoryRoot() {
9021
9772
  return new MemoryDirectory("", null);
9022
9773
  }
9774
+ function isMemoryRoot(handle) {
9775
+ return handle instanceof MemoryDirectory;
9776
+ }
9023
9777
 
9024
9778
  // src/kernel/net/remote-transport.ts
9025
9779
  var TransportError = class extends Error {
@@ -9432,6 +10186,8 @@ var Channel = class {
9432
10186
  failure;
9433
10187
  pending = /* @__PURE__ */ new Map();
9434
10188
  watching = /* @__PURE__ */ new Map();
10189
+ cdpHooks = /* @__PURE__ */ new Map();
10190
+ cdpOpen = /* @__PURE__ */ new Set();
9435
10191
  protocol = PROTOCOL;
9436
10192
  nextId = 0;
9437
10193
  port;
@@ -9452,6 +10208,8 @@ var Channel = class {
9452
10208
  this.failure = error;
9453
10209
  this.greeted.reject(error);
9454
10210
  this.remote?.fail(error);
10211
+ for (const close of [...this.cdpOpen]) close();
10212
+ this.cdpOpen.clear();
9455
10213
  for (const call of this.pending.values()) call.reject(error);
9456
10214
  this.pending.clear();
9457
10215
  this.release();
@@ -9472,6 +10230,10 @@ var Channel = class {
9472
10230
  return this.request(call).done;
9473
10231
  }
9474
10232
  receive(data) {
10233
+ if (data.cdpOpen && data.port) {
10234
+ this.openCdp(data);
10235
+ return;
10236
+ }
9475
10237
  if (data.watch !== void 0) {
9476
10238
  this.watching.get(data.watch)?.(data.change);
9477
10239
  return;
@@ -9503,7 +10265,39 @@ var Channel = class {
9503
10265
  if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
9504
10266
  else call.resolve(reply.result);
9505
10267
  }
10268
+ openCdp({ cdpOpen: { registration, ...request }, port }) {
10269
+ const hook = registration === void 0 ? void 0 : this.cdpHooks.get(registration);
10270
+ if (!hook || this.failure) {
10271
+ port.postMessage({ error: "this client no longer serves CDP" });
10272
+ port.close();
10273
+ return;
10274
+ }
10275
+ const close = serveCdpPort(port, hook, request, () => this.cdpOpen.delete(close));
10276
+ this.cdpOpen.add(close);
10277
+ }
9506
10278
  };
10279
+ async function serveCdpOn(channel, hook, options) {
10280
+ await since(channel, 5, "CDP serving", async () => {
10281
+ });
10282
+ const req = {
10283
+ op: "serve-cdp",
10284
+ ...options.runtime ? { runtime: options.runtime } : {}
10285
+ };
10286
+ const { id, done: done4 } = channel.request(req);
10287
+ channel.cdpHooks.set(id, hook);
10288
+ try {
10289
+ await done4;
10290
+ } catch (error) {
10291
+ channel.cdpHooks.delete(id);
10292
+ throw error;
10293
+ }
10294
+ return {
10295
+ close: () => {
10296
+ if (!channel.cdpHooks.delete(id)) return;
10297
+ void channel.call({ op: "unserve-cdp", registration: id }).catch(() => void 0);
10298
+ }
10299
+ };
10300
+ }
9507
10301
  async function dialOn(channel, { port, host }) {
9508
10302
  const req = { op: "dial", port, ...host ? { host } : {} };
9509
10303
  return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
@@ -9689,6 +10483,7 @@ async function attachKernel(port, options = {}) {
9689
10483
  dial: (options2) => dialOn(channel, options2),
9690
10484
  loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
9691
10485
  fs: fsOn(channel),
10486
+ serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
9692
10487
  async close(o = {}) {
9693
10488
  if (channel.failure) return;
9694
10489
  await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
@@ -9750,7 +10545,7 @@ function textOf(on) {
9750
10545
  }
9751
10546
  async function createNodeKernel(options = {}) {
9752
10547
  const root = options.root ?? memoryRoot();
9753
- const fs = new OpfsFs(root);
10548
+ const fs = new OpfsFs(root, void 0, void 0, { ranged: isMemoryRoot(root) });
9754
10549
  await fs.reconcile();
9755
10550
  const live = /* @__PURE__ */ new Set();
9756
10551
  const pending = /* @__PURE__ */ new Set();
@@ -9770,7 +10565,8 @@ async function createNodeKernel(options = {}) {
9770
10565
  ...options.env ? { env: options.env } : {},
9771
10566
  ...transport ? { transport } : {},
9772
10567
  ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
9773
- ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {}
10568
+ ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
10569
+ ...options.cdp ? { cdp: options.cdp } : {}
9774
10570
  });
9775
10571
  await launcher.prepare();
9776
10572
  const guard = (work) => new Promise((resolve, reject) => {
@@ -9850,6 +10646,7 @@ async function createNodeKernel(options = {}) {
9850
10646
  for (const client of [...clients]) client.detach();
9851
10647
  for (const reject of [...pending]) reject(new Error(TERMINATED));
9852
10648
  for (const kill of [...live]) kill();
10649
+ launcher.cdp.close();
9853
10650
  launcher.unmountAll();
9854
10651
  }
9855
10652
  };