@ai-ecoverse/slicc-kernel 1.22.0 → 1.23.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:
@@ -1817,124 +1949,823 @@ var OpfsFs = class {
1817
1949
  }
1818
1950
  };
1819
1951
 
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;
1952
+ // src/kernel/net/http1.ts
1953
+ var HttpError = class extends Error {
1954
+ status;
1955
+ constructor(status, message2) {
1956
+ super(message2);
1957
+ this.status = status;
1837
1958
  }
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;
1959
+ };
1960
+ var CR = 13;
1961
+ var LF = 10;
1962
+ var READ_SIZE = 64 * 1024;
1963
+ var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
1964
+ var REASON = {
1965
+ 100: "Continue",
1966
+ 200: "OK",
1967
+ 400: "Bad Request",
1968
+ 403: "Forbidden",
1969
+ 404: "Not Found",
1970
+ 405: "Method Not Allowed",
1971
+ 408: "Request Timeout",
1972
+ 411: "Length Required",
1973
+ 413: "Content Too Large",
1974
+ 417: "Expectation Failed",
1975
+ 421: "Misdirected Request",
1976
+ 431: "Request Header Fields Too Large",
1977
+ 501: "Not Implemented",
1978
+ 502: "Bad Gateway",
1979
+ 503: "Service Unavailable",
1980
+ 504: "Gateway Timeout",
1981
+ 505: "HTTP Version Not Supported"
1982
+ };
1983
+ var Incoming = class {
1984
+ source;
1985
+ buf = new Uint8Array(0);
1986
+ eof = false;
1987
+ constructor(source) {
1988
+ this.source = source;
1846
1989
  }
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 };
1990
+ get buffered() {
1991
+ return this.buf.length;
1866
1992
  }
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;
1993
+ async fill(signal) {
1994
+ if (this.eof) return false;
1995
+ const chunk2 = await this.source.read(READ_SIZE, signal);
1996
+ if (chunk2.length === 0) {
1997
+ this.eof = true;
1998
+ return false;
1896
1999
  }
1897
- const command = commandOf(pkg, name2, raw, packageAbi, shared);
1898
- if (command) out.push(command);
2000
+ const next = new Uint8Array(this.buf.length + chunk2.length);
2001
+ next.set(this.buf);
2002
+ next.set(chunk2, this.buf.length);
2003
+ this.buf = next;
2004
+ return true;
1899
2005
  }
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;
2006
+ take(n) {
2007
+ const out = this.buf.slice(0, n);
2008
+ this.buf = this.buf.subarray(n);
2009
+ return out;
1908
2010
  }
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;
2011
+ async some(max, signal) {
2012
+ if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
2013
+ if (this.eof) return new Uint8Array(0);
2014
+ const chunk2 = await this.source.read(max, signal);
2015
+ if (chunk2.length === 0) this.eof = true;
2016
+ return chunk2;
2017
+ }
2018
+ async head(limit, signal) {
2019
+ let from = 0;
2020
+ for (; ; ) {
2021
+ const end = headEnd(this.buf, from);
2022
+ if (end > 0) {
2023
+ if (end > limit) throw new HttpError(431, "request head too large");
2024
+ return this.take(end);
2025
+ }
2026
+ if (this.buf.length > limit) throw new HttpError(431, "request head too large");
2027
+ from = Math.max(0, this.buf.length - 3);
2028
+ if (!await this.fill(signal)) {
2029
+ if (this.buf.length === 0) return null;
2030
+ throw new HttpError(400, "connection closed inside a request head");
2031
+ }
1919
2032
  }
1920
- for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
1921
- dirs.push(`${modules}/${name2}/${inner}`);
2033
+ }
2034
+ async line(limit, signal) {
2035
+ let from = 0;
2036
+ for (; ; ) {
2037
+ const at = this.buf.indexOf(LF, from);
2038
+ if (at >= 0) {
2039
+ const line = this.take(at + 1);
2040
+ const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
2041
+ return text;
2042
+ }
2043
+ if (this.buf.length > limit) throw new HttpError(400, "line too long");
2044
+ from = this.buf.length;
2045
+ if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
1922
2046
  }
1923
2047
  }
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`);
2048
+ async exactly(n, signal) {
2049
+ while (this.buf.length < n) {
2050
+ if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
1935
2051
  }
2052
+ return this.take(n);
1936
2053
  }
1937
- return roots;
2054
+ };
2055
+ function headEnd(buf, from) {
2056
+ for (let i = from; i < buf.length; i++) {
2057
+ if (buf[i] !== LF) continue;
2058
+ if (buf[i + 1] === LF) return i + 2;
2059
+ if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
2060
+ }
2061
+ return 0;
2062
+ }
2063
+ function latin1(bytes) {
2064
+ let out = "";
2065
+ for (let i = 0; i < bytes.length; i += 32768) {
2066
+ out += String.fromCharCode(...bytes.subarray(i, i + 32768));
2067
+ }
2068
+ return out;
2069
+ }
2070
+ function latin1Bytes(text) {
2071
+ const out = new Uint8Array(text.length);
2072
+ for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
2073
+ return out;
2074
+ }
2075
+ function parseRequestHead(bytes) {
2076
+ const lines = latin1(bytes).split(/\r?\n/);
2077
+ while (lines.length > 0 && lines[0] === "") lines.shift();
2078
+ const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
2079
+ if (!match) throw new HttpError(400, "malformed request line");
2080
+ if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
2081
+ return {
2082
+ method: match[1],
2083
+ target: match[2],
2084
+ minor: Number(match[4]),
2085
+ headers: parseFields(lines)
2086
+ };
2087
+ }
2088
+ function parseFields(lines) {
2089
+ const headers = [];
2090
+ for (const line of lines) {
2091
+ if (line === "") continue;
2092
+ if (line.startsWith(" ") || line.startsWith(" ")) {
2093
+ throw new HttpError(400, "obsolete line folding");
2094
+ }
2095
+ const colon = line.indexOf(":");
2096
+ const name2 = colon > 0 ? line.slice(0, colon) : "";
2097
+ if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
2098
+ headers.push([name2, line.slice(colon + 1).trim()]);
2099
+ }
2100
+ return headers;
2101
+ }
2102
+ function fieldValues(headers, name2) {
2103
+ const lower = name2.toLowerCase();
2104
+ return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
2105
+ }
2106
+ function fieldTokens(headers, name2) {
2107
+ return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
2108
+ }
2109
+ function requestFraming(headers) {
2110
+ const codings = fieldTokens(headers, "transfer-encoding");
2111
+ const lengths = fieldValues(headers, "content-length");
2112
+ if (codings.length > 0) {
2113
+ if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
2114
+ if (codings.length !== 1 || codings[0] !== "chunked") {
2115
+ throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
2116
+ }
2117
+ return { kind: "chunked" };
2118
+ }
2119
+ if (lengths.length === 0) return { kind: "none" };
2120
+ const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
2121
+ const [only] = distinct;
2122
+ if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
2123
+ throw new HttpError(400, "invalid Content-Length");
2124
+ }
2125
+ const length2 = Number(only);
2126
+ return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
2127
+ }
2128
+ var MAX_CHUNK_LINE = 4096;
2129
+ var MAX_TRAILERS = 64 * 1024;
2130
+ async function readBody(incoming, framing, cap, signal) {
2131
+ if (framing.kind === "none") return void 0;
2132
+ if (framing.kind === "length") {
2133
+ if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
2134
+ return incoming.exactly(framing.length, signal);
2135
+ }
2136
+ const chunks2 = [];
2137
+ let total = 0;
2138
+ for (; ; ) {
2139
+ const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
2140
+ const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
2141
+ if (!size) throw new HttpError(400, "malformed chunk size");
2142
+ const n = Number.parseInt(size[1], 16);
2143
+ if (n === 0) break;
2144
+ total += n;
2145
+ if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
2146
+ chunks2.push(await incoming.exactly(n, signal));
2147
+ if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
2148
+ }
2149
+ let trailers = 0;
2150
+ for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
2151
+ trailers += line.length;
2152
+ if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
2153
+ line = await incoming.line(MAX_TRAILERS, signal);
2154
+ }
2155
+ return concat(chunks2, total);
2156
+ }
2157
+ function concat(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
2158
+ const out = new Uint8Array(total);
2159
+ let at = 0;
2160
+ for (const chunk2 of chunks2) {
2161
+ out.set(chunk2, at);
2162
+ at += chunk2.length;
2163
+ }
2164
+ return out;
2165
+ }
2166
+ function safeValue(value) {
2167
+ return !/[\r\n\0]/.test(value);
2168
+ }
2169
+ function responseHead(status, reason, headers) {
2170
+ const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
2171
+ let head = `HTTP/1.1 ${status} ${phrase}\r
2172
+ `;
2173
+ for (const [name2, value] of headers) {
2174
+ if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
2175
+ `;
2176
+ }
2177
+ return latin1Bytes(`${head}\r
2178
+ `);
2179
+ }
2180
+ function chunk(bytes) {
2181
+ const size = latin1Bytes(`${bytes.length.toString(16)}\r
2182
+ `);
2183
+ const out = new Uint8Array(size.length + bytes.length + 2);
2184
+ out.set(size);
2185
+ out.set(bytes, size.length);
2186
+ out[out.length - 2] = CR;
2187
+ out[out.length - 1] = LF;
2188
+ return out;
2189
+ }
2190
+ var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
2191
+
2192
+ // src/cdp/types.ts
2193
+ var CDP_PORT = 9222;
2194
+ var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
2195
+
2196
+ // src/cdp/websocket.ts
2197
+ var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
2198
+ var MAX_MESSAGE = 256 * 1024 * 1024;
2199
+ var CLOSE = {
2200
+ normal: 1e3,
2201
+ protocol: 1002,
2202
+ unsupported: 1003,
2203
+ invalid: 1007,
2204
+ tooBig: 1009,
2205
+ error: 1011
2206
+ };
2207
+ var OP_TEXT = 1;
2208
+ var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
2209
+ var WsError = class extends Error {
2210
+ code;
2211
+ constructor(code, message2) {
2212
+ super(message2);
2213
+ this.code = code;
2214
+ }
2215
+ };
2216
+ function field(headers, name2) {
2217
+ return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
2218
+ }
2219
+ async function acceptKey(headers) {
2220
+ const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
2221
+ if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
2222
+ throw new HttpError(400, "expected a WebSocket upgrade");
2223
+ }
2224
+ if (field(headers, "sec-websocket-version") !== "13") {
2225
+ throw new HttpError(400, "Sec-WebSocket-Version must be 13");
2226
+ }
2227
+ const key = field(headers, "sec-websocket-key") ?? "";
2228
+ if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
2229
+ const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
2230
+ return btoa(String.fromCharCode(...new Uint8Array(digest)));
2231
+ }
2232
+ function isUpgrade(headers) {
2233
+ return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
2234
+ }
2235
+ function frame(opcode, payload) {
2236
+ const n = payload.length;
2237
+ const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
2238
+ const out = new Uint8Array(size + n);
2239
+ const view = new DataView(out.buffer);
2240
+ out[0] = 128 | opcode;
2241
+ if (n < 126) out[1] = n;
2242
+ else if (n < 65536) {
2243
+ out[1] = 126;
2244
+ view.setUint16(2, n);
2245
+ } else {
2246
+ out[1] = 127;
2247
+ view.setBigUint64(2, BigInt(n));
2248
+ }
2249
+ out.set(payload, size);
2250
+ return out;
2251
+ }
2252
+ function closeFrame(code, reason = "") {
2253
+ let bytes = new TextEncoder().encode(reason);
2254
+ if (bytes.length > 123) bytes = bytes.subarray(0, 123);
2255
+ const payload = new Uint8Array(2 + bytes.length);
2256
+ new DataView(payload.buffer).setUint16(0, code);
2257
+ payload.set(bytes, 2);
2258
+ return frame(OP.close, payload);
2259
+ }
2260
+ function pongFrame(payload) {
2261
+ return frame(OP.pong, payload);
2262
+ }
2263
+ async function fill(incoming, out, signal) {
2264
+ for (let at = 0; at < out.length; ) {
2265
+ const piece = await incoming.some(out.length - at, signal);
2266
+ if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
2267
+ out.set(piece, at);
2268
+ at += piece.length;
2269
+ }
2270
+ }
2271
+ var fatal = new TextDecoder("utf-8", { fatal: true });
2272
+ function decode(bytes) {
2273
+ try {
2274
+ return fatal.decode(bytes);
2275
+ } catch {
2276
+ throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
2277
+ }
2278
+ }
2279
+ var WsReader = class {
2280
+ incoming;
2281
+ max;
2282
+ parts = [];
2283
+ size = 0;
2284
+ continuing = false;
2285
+ constructor(incoming, max = MAX_MESSAGE) {
2286
+ this.incoming = incoming;
2287
+ this.max = max;
2288
+ }
2289
+ async next(signal) {
2290
+ for (; ; ) {
2291
+ const first = await this.incoming.some(1, signal);
2292
+ if (first.length === 0) return null;
2293
+ const head = new Uint8Array(2);
2294
+ head[0] = first[0];
2295
+ await fill(this.incoming, head.subarray(1), signal);
2296
+ const fin = (head[0] & 128) !== 0;
2297
+ const opcode = head[0] & 15;
2298
+ if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
2299
+ if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
2300
+ const length2 = await this.length(head[1] & 127, signal);
2301
+ const control = opcode >= 8;
2302
+ if (control && (length2 > 125 || !fin)) {
2303
+ throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
2304
+ }
2305
+ if (!control && this.size + length2 > this.max) {
2306
+ throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
2307
+ }
2308
+ const mask = new Uint8Array(4);
2309
+ await fill(this.incoming, mask, signal);
2310
+ const payload = new Uint8Array(length2);
2311
+ await fill(this.incoming, payload, signal);
2312
+ for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
2313
+ const event = this.take(opcode, fin, payload);
2314
+ if (event) return event;
2315
+ }
2316
+ }
2317
+ async length(short, signal) {
2318
+ if (short < 126) return short;
2319
+ const ext = new Uint8Array(short === 126 ? 2 : 8);
2320
+ await fill(this.incoming, ext, signal);
2321
+ const view = new DataView(ext.buffer);
2322
+ if (short === 126) return view.getUint16(0);
2323
+ const long = view.getBigUint64(0);
2324
+ return long > BigInt(this.max) ? this.max + 1 : Number(long);
2325
+ }
2326
+ take(opcode, fin, payload) {
2327
+ switch (opcode) {
2328
+ case OP.ping:
2329
+ return { kind: "ping", payload };
2330
+ case OP.pong:
2331
+ return void 0;
2332
+ case OP.close: {
2333
+ if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
2334
+ const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
2335
+ return { kind: "close", code, reason: decode(payload.subarray(2)) };
2336
+ }
2337
+ case OP.text:
2338
+ case OP.continuation: {
2339
+ if (opcode === OP.continuation !== this.continuing) {
2340
+ throw new WsError(CLOSE.protocol, "unexpected continuation frame");
2341
+ }
2342
+ this.parts.push(payload);
2343
+ this.size += payload.length;
2344
+ this.continuing = !fin;
2345
+ if (!fin) return void 0;
2346
+ const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
2347
+ this.parts = [];
2348
+ this.size = 0;
2349
+ return { kind: "text", text: decode(whole) };
2350
+ }
2351
+ case OP.binary:
2352
+ throw new WsError(CLOSE.unsupported, "CDP messages are text");
2353
+ default:
2354
+ throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
2355
+ }
2356
+ }
2357
+ };
2358
+ function switchingHead(accept2) {
2359
+ return latin1Bytes(
2360
+ `HTTP/1.1 101 Switching Protocols\r
2361
+ Upgrade: websocket\r
2362
+ Connection: Upgrade\r
2363
+ Sec-WebSocket-Accept: ${accept2}\r
2364
+ \r
2365
+ `
2366
+ );
2367
+ }
2368
+
2369
+ // src/cdp/facade.ts
2370
+ var MAX_HEAD = 64 * 1024;
2371
+ var LIST_TIMEOUT = 1e4;
2372
+ function respond(status, body, type = "text/plain; charset=utf-8") {
2373
+ const bytes = new TextEncoder().encode(body);
2374
+ const head = latin1Bytes(
2375
+ `HTTP/1.1 ${status} ${REASON[status]}\r
2376
+ Content-Type: ${type}\r
2377
+ Content-Length: ${bytes.length}\r
2378
+ Connection: close\r
2379
+ \r
2380
+ `
2381
+ );
2382
+ const out = new Uint8Array(head.length + bytes.length);
2383
+ out.set(head);
2384
+ out.set(bytes, head.length);
2385
+ return out;
2386
+ }
2387
+ function json(value) {
2388
+ return respond(200, `${JSON.stringify(value, null, 2)}
2389
+ `, "application/json; charset=UTF-8");
2390
+ }
2391
+ var CdpFacade = class {
2392
+ options;
2393
+ listener;
2394
+ stop = new AbortController();
2395
+ connections = /* @__PURE__ */ new Set();
2396
+ closed;
2397
+ constructor(options) {
2398
+ this.options = options;
2399
+ this.listener = options.net.listen({
2400
+ family: "inet",
2401
+ host: "127.0.0.1",
2402
+ port: options.port ?? CDP_PORT
2403
+ });
2404
+ this.closed = this.acceptLoop();
2405
+ }
2406
+ close() {
2407
+ if (this.stop.signal.aborted) return;
2408
+ this.stop.abort();
2409
+ this.listener.close();
2410
+ for (const conn of this.connections) conn.close();
2411
+ }
2412
+ async acceptLoop() {
2413
+ const served = /* @__PURE__ */ new Set();
2414
+ for (; ; ) {
2415
+ let conn;
2416
+ try {
2417
+ conn = await this.listener.accept(this.stop.signal);
2418
+ } catch {
2419
+ break;
2420
+ }
2421
+ const done4 = this.serve(conn).finally(() => served.delete(done4));
2422
+ served.add(done4);
2423
+ }
2424
+ await Promise.allSettled([...served]);
2425
+ }
2426
+ async serve(conn) {
2427
+ this.connections.add(conn);
2428
+ const incoming = new Incoming(conn);
2429
+ try {
2430
+ const head = await incoming.head(MAX_HEAD, this.stop.signal);
2431
+ if (head) await this.route(conn, incoming, parseRequestHead(head));
2432
+ } catch (e) {
2433
+ if (e instanceof HttpError) await this.write(conn, respond(e.status, `${e.message}
2434
+ `));
2435
+ } finally {
2436
+ this.connections.delete(conn);
2437
+ conn.close();
2438
+ }
2439
+ }
2440
+ async write(conn, bytes) {
2441
+ try {
2442
+ await conn.write(bytes, this.stop.signal);
2443
+ } catch {
2444
+ }
2445
+ }
2446
+ async route(conn, incoming, req) {
2447
+ const url = new URL(req.target, "http://127.0.0.1");
2448
+ const runtime = url.searchParams.get("runtime") ?? void 0;
2449
+ const host = req.headers.find(([n]) => n.toLowerCase() === "host")?.[1] ?? "127.0.0.1";
2450
+ if (req.method !== "GET") throw new HttpError(405, `${req.method} is not supported here`);
2451
+ const { hosts } = this.options;
2452
+ if (url.pathname === hosts.path && isUpgrade(req.headers)) {
2453
+ return this.upgrade(conn, incoming, req, runtime);
2454
+ }
2455
+ const path = url.pathname.replace(/\/+$/, "");
2456
+ if (path !== "/json/version" && path !== "/json/list" && path !== "/json") {
2457
+ throw new HttpError(404, "not found: try /json/version");
2458
+ }
2459
+ if (!this.options.hosts.available(runtime)) {
2460
+ throw new HttpError(503, NO_CDP_HOST);
2461
+ }
2462
+ if (path === "/json/version") {
2463
+ await this.write(
2464
+ conn,
2465
+ json({
2466
+ Browser: "slicc-kernel",
2467
+ "Protocol-Version": "1.3",
2468
+ webSocketDebuggerUrl: `ws://${host}${hosts.path}${url.search}`
2469
+ })
2470
+ );
2471
+ return;
2472
+ }
2473
+ await this.write(conn, json(await this.list(runtime)));
2474
+ }
2475
+ async list(runtime) {
2476
+ const conn = await this.host(runtime);
2477
+ try {
2478
+ const reply = await new Promise(
2479
+ (resolve, reject) => {
2480
+ const timer = setTimeout(
2481
+ () => reject(new HttpError(504, "the CDP host did not answer Target.getTargets")),
2482
+ this.options.listTimeout ?? LIST_TIMEOUT
2483
+ );
2484
+ conn.onclose = (reason) => {
2485
+ clearTimeout(timer);
2486
+ reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
2487
+ };
2488
+ conn.onmessage = (message2) => {
2489
+ const parsed = JSON.parse(message2);
2490
+ if (parsed.id !== 1) return;
2491
+ clearTimeout(timer);
2492
+ resolve(parsed);
2493
+ };
2494
+ conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
2495
+ }
2496
+ );
2497
+ if (!reply.result) {
2498
+ throw new HttpError(502, `Target.getTargets failed: ${JSON.stringify(reply.error)}`);
2499
+ }
2500
+ return (reply.result.targetInfos ?? []).map((t) => ({
2501
+ description: "",
2502
+ id: t.targetId,
2503
+ title: t.title,
2504
+ type: t.type,
2505
+ url: t.url
2506
+ }));
2507
+ } finally {
2508
+ conn.close();
2509
+ }
2510
+ }
2511
+ host(runtime) {
2512
+ return this.options.hosts.connect(runtime ? { runtime } : {});
2513
+ }
2514
+ async upgrade(conn, incoming, req, runtime) {
2515
+ const accept2 = await acceptKey(req.headers);
2516
+ const host = await this.host(runtime);
2517
+ await new Relay(conn, incoming, host, this.options.maxMessage ?? MAX_MESSAGE, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
2518
+ }
2519
+ };
2520
+ var Relay = class {
2521
+ writes = Promise.resolve();
2522
+ ended = false;
2523
+ conn;
2524
+ incoming;
2525
+ host;
2526
+ max;
2527
+ stop;
2528
+ constructor(conn, incoming, host, max, stop) {
2529
+ this.conn = conn;
2530
+ this.incoming = incoming;
2531
+ this.host = host;
2532
+ this.max = max;
2533
+ this.stop = stop;
2534
+ }
2535
+ send(bytes) {
2536
+ this.writes = this.writes.then(() => this.conn.write(bytes, this.stop)).catch(() => void 0);
2537
+ return this.writes;
2538
+ }
2539
+ end(code, reason) {
2540
+ if (this.ended) return this.writes;
2541
+ this.ended = true;
2542
+ return this.send(closeFrame(code, reason));
2543
+ }
2544
+ async start(head) {
2545
+ await this.send(head);
2546
+ const hostGone = new Promise((resolve) => {
2547
+ this.host.onclose = (reason) => {
2548
+ void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
2549
+ };
2550
+ });
2551
+ this.host.onmessage = (message2) => {
2552
+ if (this.ended) return;
2553
+ const bytes = new TextEncoder().encode(message2);
2554
+ if (bytes.length > this.max)
2555
+ void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
2556
+ else void this.send(frame(OP_TEXT, bytes));
2557
+ };
2558
+ await Promise.race([this.pump(), hostGone]);
2559
+ await this.writes;
2560
+ }
2561
+ async pump() {
2562
+ const reader = new WsReader(this.incoming, this.max);
2563
+ try {
2564
+ for (; ; ) {
2565
+ const event = await reader.next(this.stop);
2566
+ if (!event) return;
2567
+ if (event.kind === "text") this.host.send(event.text);
2568
+ else if (event.kind === "ping") void this.send(pongFrame(event.payload));
2569
+ else {
2570
+ await this.end(event.code === 1005 ? CLOSE.normal : event.code, "");
2571
+ return;
2572
+ }
2573
+ }
2574
+ } catch (e) {
2575
+ if (e instanceof WsError) await this.end(e.code, e.message);
2576
+ }
2577
+ }
2578
+ };
2579
+ function enableCdp(net, hosts) {
2580
+ let facade;
2581
+ net.kernelOnly(CDP_PORT, true);
2582
+ net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
2583
+ const started = new CdpFacade({ net, hosts });
2584
+ facade = started;
2585
+ void started.closed.then(() => {
2586
+ if (facade === started) facade = void 0;
2587
+ });
2588
+ });
2589
+ return () => facade;
2590
+ }
2591
+
2592
+ // src/cdp/hosts.ts
2593
+ var CdpHosts = class {
2594
+ embedder;
2595
+ clients = [];
2596
+ closed = false;
2597
+ path = `/devtools/browser/${crypto.randomUUID()}`;
2598
+ url = `ws://127.0.0.1:${CDP_PORT}${this.path}`;
2599
+ constructor(hook) {
2600
+ this.embedder = hook ? { hook, runtime: void 0, open: /* @__PURE__ */ new Set() } : void 0;
2601
+ }
2602
+ register(hook, runtime) {
2603
+ const entry = { hook, runtime, open: /* @__PURE__ */ new Set() };
2604
+ this.clients.push(entry);
2605
+ return () => {
2606
+ const at = this.clients.indexOf(entry);
2607
+ if (at >= 0) this.clients.splice(at, 1);
2608
+ closeAll(entry);
2609
+ };
2610
+ }
2611
+ pick(runtime) {
2612
+ if (this.closed) return void 0;
2613
+ const named = runtime ? this.clients.findLast((c) => c.runtime === runtime) : void 0;
2614
+ return named ?? this.embedder ?? this.clients.at(-1);
2615
+ }
2616
+ available(runtime) {
2617
+ return this.pick(runtime) !== void 0;
2618
+ }
2619
+ async connect(request) {
2620
+ const entry = this.pick(request.runtime);
2621
+ if (!entry) throw new HttpError(503, NO_CDP_HOST);
2622
+ let conn;
2623
+ try {
2624
+ conn = await entry.hook(request.runtime ? { runtime: request.runtime } : {});
2625
+ } catch (e) {
2626
+ throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
2627
+ }
2628
+ if (this.closed || !(entry === this.embedder || this.clients.includes(entry))) {
2629
+ conn.close();
2630
+ throw new HttpError(503, NO_CDP_HOST);
2631
+ }
2632
+ entry.open.add(conn);
2633
+ const close = conn.close.bind(conn);
2634
+ conn.close = () => {
2635
+ if (entry.open.delete(conn)) close();
2636
+ };
2637
+ return conn;
2638
+ }
2639
+ close() {
2640
+ this.closed = true;
2641
+ for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
2642
+ }
2643
+ };
2644
+ function closeAll(entry) {
2645
+ for (const conn of [...entry.open]) {
2646
+ conn.close();
2647
+ conn.onclose?.("the CDP host is gone");
2648
+ }
2649
+ }
2650
+
2651
+ // src/commands.ts
2652
+ var NAME = /^[A-Za-z0-9._+-]+$/;
2653
+ var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
2654
+ var PACKAGE = /\$\{package\}/g;
2655
+ function inside(pkg, rel2) {
2656
+ if (typeof rel2 !== "string" || rel2 === "") return void 0;
2657
+ const clean = rel2.replace(/^\.\//, "");
2658
+ if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
2659
+ return `${pkg}/${clean}`;
2660
+ }
2661
+ function envOf(pkg, raw) {
2662
+ const env = {};
2663
+ if (!raw || typeof raw !== "object") return env;
2664
+ for (const [key, value] of Object.entries(raw)) {
2665
+ if (!ENV_KEY.test(key)) continue;
2666
+ if (typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
2667
+ else if (value === null) env[key] = null;
2668
+ }
2669
+ return env;
2670
+ }
2671
+ function envFields(merged) {
2672
+ const env = {};
2673
+ const unset = [];
2674
+ for (const [key, value] of Object.entries(merged)) {
2675
+ if (value === null) unset.push(key);
2676
+ else env[key] = value;
2677
+ }
2678
+ return {
2679
+ ...Object.keys(env).length > 0 ? { env } : {},
2680
+ ...unset.length > 0 ? { unset } : {}
2681
+ };
2682
+ }
2683
+ function argsOf(pkg, raw) {
2684
+ if (!Array.isArray(raw) || raw.length === 0) return void 0;
2685
+ if (!raw.every((arg) => typeof arg === "string")) return void 0;
2686
+ return raw.map((arg) => arg.replace(PACKAGE, pkg));
2687
+ }
2688
+ function abiOf(raw, fallback) {
2689
+ const abi = raw ?? fallback;
2690
+ return abi === "emscripten" || abi === "wasi" ? abi : void 0;
2691
+ }
2692
+ function commandOf(pkg, name2, raw, packageAbi, shared) {
2693
+ const withEnv = envFields({ ...shared, ...envOf(pkg, raw.env) });
2694
+ const script = inside(pkg, raw.script);
2695
+ if (script) {
2696
+ return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
2697
+ }
2698
+ const abi = abiOf(raw.abi, packageAbi);
2699
+ const wasm = inside(pkg, raw.wasm);
2700
+ const glue = abi === "wasi" ? wasm : inside(pkg, raw.glue);
2701
+ if (!abi || !glue || !wasm) return void 0;
2702
+ const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
2703
+ const args = argsOf(pkg, raw.args);
2704
+ const imports = abi === "wasi" ? inside(pkg, raw.imports) : void 0;
2705
+ return {
2706
+ name: name2,
2707
+ abi,
2708
+ glue,
2709
+ wasm,
2710
+ argv0,
2711
+ ...args ? { args } : {},
2712
+ ...withEnv,
2713
+ ...imports ? { imports } : {}
2714
+ };
2715
+ }
2716
+ function commandsOf(pkg, manifest) {
2717
+ const slicc = manifest.slicc;
2718
+ if (!slicc || typeof slicc !== "object") return [];
2719
+ const packageAbi = abiOf(slicc.abi, "emscripten");
2720
+ const entries = slicc.commands;
2721
+ if (!packageAbi || !entries || typeof entries !== "object") return [];
2722
+ const shared = envOf(pkg, slicc.env);
2723
+ const out = [];
2724
+ for (const [name2, raw] of Object.entries(entries)) {
2725
+ if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
2726
+ continue;
2727
+ }
2728
+ const command = commandOf(pkg, name2, raw, packageAbi, shared);
2729
+ if (command) out.push(command);
2730
+ }
2731
+ return out;
2732
+ }
2733
+ async function withPackagePaths(fs, pkg, command) {
2734
+ if (!command.env) return command;
2735
+ const env = {};
2736
+ for (const [key, value] of Object.entries(command.env)) {
2737
+ const path = !value.startsWith("/") && value.includes("/") && inside(pkg, value);
2738
+ env[key] = path && await fs.exists(path) ? path : value;
2739
+ }
2740
+ return { ...command, env };
2741
+ }
2742
+ async function packages(fs, modules) {
2743
+ const names = await fs.readdir(modules).catch(() => []);
2744
+ const dirs = [];
2745
+ for (const name2 of names) {
2746
+ if (name2.startsWith(".")) continue;
2747
+ if (!name2.startsWith("@")) {
2748
+ dirs.push(`${modules}/${name2}`);
2749
+ continue;
2750
+ }
2751
+ for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
2752
+ dirs.push(`${modules}/${name2}/${inner}`);
2753
+ }
2754
+ }
2755
+ return dirs;
2756
+ }
2757
+ async function pnpmGlobalRoots(fs, home) {
2758
+ const roots = [];
2759
+ const global = `${home}/global`;
2760
+ for (const version of (await fs.readdir(global).catch(() => [])).sort()) {
2761
+ for (const group of (await fs.readdir(`${global}/${version}`).catch(() => [])).sort()) {
2762
+ const dir = `${global}/${version}/${group}`;
2763
+ const st = await fs.lstat(dir).catch(() => void 0);
2764
+ if (st?.isDirectory && await fs.exists(`${dir}/node_modules`))
2765
+ roots.push(`${dir}/node_modules`);
2766
+ }
2767
+ }
2768
+ return roots;
1938
2769
  }
1939
2770
  async function packagesIn(fs, roots) {
1940
2771
  const dirs = [];
@@ -2203,7 +3034,7 @@ var CONTINUED_STATUS = 65535;
2203
3034
  function waitStatus(code, termsig) {
2204
3035
  return termsig ? termsig & 127 : (code & 255) << 8;
2205
3036
  }
2206
- function concat(chunks2) {
3037
+ function concat2(chunks2) {
2207
3038
  const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
2208
3039
  let offset = 0;
2209
3040
  for (const chunk2 of chunks2) {
@@ -2360,7 +3191,7 @@ var ChildTable = class {
2360
3191
  if (!slots || !chunks2) return new Uint8Array(0);
2361
3192
  slots.delete(slot);
2362
3193
  if (slots.size === 0) this.leftovers.delete(pid);
2363
- return concat(chunks2);
3194
+ return concat2(chunks2);
2364
3195
  }
2365
3196
  };
2366
3197
 
@@ -2528,7 +3359,7 @@ function statOf(attr, path, mount) {
2528
3359
  ...mount.caps.ranges ? { ranged: true } : {}
2529
3360
  };
2530
3361
  }
2531
- function concat2(chunks2, total) {
3362
+ function concat3(chunks2, total) {
2532
3363
  if (chunks2.length === 1 && chunks2[0]?.length === total) return chunks2[0];
2533
3364
  const out = new Uint8Array(total);
2534
3365
  let at = 0;
@@ -2557,6 +3388,7 @@ var MountTable = class {
2557
3388
  mounts = /* @__PURE__ */ new Map();
2558
3389
  devices = 0;
2559
3390
  pending = /* @__PURE__ */ new Set();
3391
+ notes = /* @__PURE__ */ new Map();
2560
3392
  deps;
2561
3393
  constructor(deps) {
2562
3394
  this.deps = deps;
@@ -2565,7 +3397,7 @@ var MountTable = class {
2565
3397
  return this.mounts.get(target);
2566
3398
  }
2567
3399
  list() {
2568
- return [...this.mounts.values()].map(
3400
+ const mounted = [...this.mounts.values()].map(
2569
3401
  ({ type, source, target, options, state, error, present }) => ({
2570
3402
  type,
2571
3403
  source,
@@ -2575,6 +3407,19 @@ var MountTable = class {
2575
3407
  ...error ? { error } : {}
2576
3408
  })
2577
3409
  );
3410
+ const noted = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
3411
+ return [...mounted, ...noted.map((note) => ({ ...note, options: { ...note.options } }))];
3412
+ }
3413
+ note(spec, state, error) {
3414
+ const target = normalizePath(spec.target);
3415
+ this.notes.set(target, {
3416
+ type: spec.type,
3417
+ source: spec.source,
3418
+ target,
3419
+ options: { ...spec.options },
3420
+ state,
3421
+ ...error ? { error } : {}
3422
+ });
2578
3423
  }
2579
3424
  locate(path) {
2580
3425
  let best;
@@ -2651,13 +3496,19 @@ var MountTable = class {
2651
3496
  };
2652
3497
  mounted = mount;
2653
3498
  this.mounts.set(target, mount);
3499
+ this.notes.delete(target);
2654
3500
  return this.list().find((m) => m.target === target);
2655
3501
  }
3502
+ unnote(target) {
3503
+ this.notes.delete(normalizePath(target));
3504
+ }
2656
3505
  umount(target, detach = false) {
2657
3506
  const at = normalizePath(target);
2658
3507
  const mount = this.mounts.get(at);
3508
+ if (!mount && this.notes.delete(at)) return;
2659
3509
  if (!mount) throw errnoError("EINVAL", `${at} is not mounted`);
2660
3510
  if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
3511
+ this.notes.delete(at);
2661
3512
  this.mounts.delete(at);
2662
3513
  mount.conn.close();
2663
3514
  mount.dispose();
@@ -2803,7 +3654,7 @@ var MountFs = class {
2803
3654
  } finally {
2804
3655
  await mount.conn.call({ op: "release", fh });
2805
3656
  }
2806
- return concat2(chunks2, total);
3657
+ return concat3(chunks2, total);
2807
3658
  }
2808
3659
  async writeFile(path, content) {
2809
3660
  const real = await this.follow(path, 0, true);
@@ -2867,7 +3718,7 @@ var MountFs = class {
2867
3718
  } finally {
2868
3719
  await mount.conn.call({ op: "release", fh });
2869
3720
  }
2870
- return concat2(chunks2, total);
3721
+ return concat3(chunks2, total);
2871
3722
  }
2872
3723
  fits(mount, real, end) {
2873
3724
  if (mount.maxFile > 0 && end > mount.maxFile) {
@@ -3120,7 +3971,7 @@ async function storageUsage() {
3120
3971
  return { quota: estimate.quota, usage: estimate.usage ?? 0 };
3121
3972
  }
3122
3973
  var done = { ok: true, kind: "void" };
3123
- function json(value) {
3974
+ function json2(value) {
3124
3975
  return { ok: true, kind: "json", json: value };
3125
3976
  }
3126
3977
  async function run(entry, path, req) {
@@ -3132,15 +3983,15 @@ async function run(entry, path, req) {
3132
3983
  await fs.writeFile(path, req.body ?? new Uint8Array(0));
3133
3984
  return done;
3134
3985
  case "exists":
3135
- return json(await fs.exists(path));
3986
+ return json2(await fs.exists(path));
3136
3987
  case "stat":
3137
- return json(statJson(await fs.stat(path)));
3988
+ return json2(statJson(await fs.stat(path)));
3138
3989
  case "lstat":
3139
- return json(statJson(await fs.lstat(path)));
3990
+ return json2(statJson(await fs.lstat(path)));
3140
3991
  case "readdir":
3141
- return json(await fs.readdir(path));
3992
+ return json2(await fs.readdir(path));
3142
3993
  case "readdir-stat":
3143
- return json(await readdirStat(fs, path));
3994
+ return json2(await readdirStat(fs, path));
3144
3995
  case "mkdir":
3145
3996
  await fs.mkdir(path, { recursive: true });
3146
3997
  return done;
@@ -3166,7 +4017,7 @@ async function run(entry, path, req) {
3166
4017
  await fs.symlink(req.arg2 ?? "", path);
3167
4018
  return done;
3168
4019
  case "readlink":
3169
- return json(await fs.readlink(path));
4020
+ return json2(await fs.readlink(path));
3170
4021
  case "chmod":
3171
4022
  await fs.chmod(path, req.mode ?? 0);
3172
4023
  return done;
@@ -3197,7 +4048,7 @@ async function dispatchSyncFs(req) {
3197
4048
  const { fs, cwd } = entry;
3198
4049
  try {
3199
4050
  const path = fs.resolvePath(cwd, req.path);
3200
- if (req.op === "statfs") return json(await entry.statfs?.(path) ?? await storageUsage());
4051
+ if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
3201
4052
  if (req.op === "hold") {
3202
4053
  entry.hold?.(path, req.mode === 1);
3203
4054
  return done;
@@ -3370,7 +4221,7 @@ var HOST_OPS = [
3370
4221
  "async-cancel",
3371
4222
  "async-close"
3372
4223
  ];
3373
- var json2 = (value) => ({ ok: true, kind: "json", json: value });
4224
+ var json3 = (value) => ({ ok: true, kind: "json", json: value });
3374
4225
  var done2 = { ok: true, kind: "void" };
3375
4226
  var LockTable = class {
3376
4227
  held = /* @__PURE__ */ new Map();
@@ -3411,361 +4262,124 @@ var AsyncOps = class {
3411
4262
  }
3412
4263
  submit(req) {
3413
4264
  const id = this.hold();
3414
- this.run(req).then(
3415
- (result) => this.complete(id, result),
3416
- (err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
3417
- );
3418
- return id;
3419
- }
3420
- resolve(value) {
3421
- const id = this.hold();
3422
- this.complete(id, json2(value));
3423
- return id;
3424
- }
3425
- hold() {
3426
- const id = this.next++;
3427
- this.results.set(id, void 0);
3428
- return id;
3429
- }
3430
- take(id) {
3431
- if (!this.results.has(id)) throw new KernelError("EINVAL");
3432
- const result = this.results.get(id);
3433
- if (!result) throw new KernelError("EAGAIN");
3434
- this.cancel(id);
3435
- return result;
3436
- }
3437
- cancel(id) {
3438
- this.results.delete(id);
3439
- const at = this.ready.indexOf(id);
3440
- if (at >= 0) this.ready.splice(at, 1);
3441
- }
3442
- async wait(timeoutMs, signal) {
3443
- for (; ; ) {
3444
- if (this.closed) return -1;
3445
- const id = this.ready.shift();
3446
- if (id !== void 0) return id;
3447
- if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
3448
- const woke = await this.sleep(timeoutMs, signal);
3449
- if (!woke) return 0;
3450
- }
3451
- }
3452
- close() {
3453
- this.closed = true;
3454
- this.wake();
3455
- }
3456
- sleep(timeoutMs, signal) {
3457
- return new Promise((resolve, reject) => {
3458
- let timer;
3459
- const cleanup = () => {
3460
- clearTimeout(timer);
3461
- this.waiters.delete(wake);
3462
- signal.removeEventListener("abort", abort);
3463
- };
3464
- const wake = () => {
3465
- cleanup();
3466
- resolve(true);
3467
- };
3468
- const abort = () => {
3469
- cleanup();
3470
- reject(new KernelError("EINTR"));
3471
- };
3472
- this.waiters.add(wake);
3473
- signal.addEventListener("abort", abort, { once: true });
3474
- if (timeoutMs !== void 0) {
3475
- timer = setTimeout(() => {
3476
- cleanup();
3477
- resolve(false);
3478
- }, timeoutMs);
3479
- }
3480
- });
3481
- }
3482
- complete(id, result) {
3483
- if (!this.results.has(id)) return;
3484
- this.results.set(id, result);
3485
- this.ready.push(id);
3486
- this.wake();
3487
- }
3488
- wake() {
3489
- for (const wake of [...this.waiters]) wake();
3490
- }
3491
- };
3492
- async function hostSyscall(req, ctx) {
3493
- switch (req.op) {
3494
- case "lock":
3495
- if (!ctx.locks) throw new KernelError("ENOSYS");
3496
- if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
3497
- throw new KernelError("EAGAIN");
3498
- return done2;
3499
- case "unlock":
3500
- ctx.locks?.unlock(ctx.pid, req.path);
3501
- return done2;
3502
- case "async-submit":
3503
- if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
3504
- return json2(ctx.ops.submit(req.req));
3505
- case "async-wait":
3506
- return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
3507
- case "async-take":
3508
- return ctx.ops.take(req.id);
3509
- case "async-resolve":
3510
- return json2(ctx.ops.resolve(req.value));
3511
- case "async-hold":
3512
- return json2(ctx.ops.hold());
3513
- case "async-cancel":
3514
- ctx.ops.cancel(req.id);
3515
- return done2;
3516
- case "async-close":
3517
- ctx.ops.close();
3518
- return done2;
3519
- }
3520
- }
3521
-
3522
- // src/kernel/net/loopback-names.ts
3523
- var HOST_LOOPBACK = "host.slicc.internal";
3524
- var PAGE_LOOPBACK = "kernel.localhost";
3525
- var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
3526
- function toHostLoopback(href) {
3527
- const url = new URL(href);
3528
- if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
3529
- url.hostname = "127.0.0.1";
3530
- return url.href;
3531
- }
3532
-
3533
- // src/kernel/net/http1.ts
3534
- var HttpError = class extends Error {
3535
- status;
3536
- constructor(status, message2) {
3537
- super(message2);
3538
- this.status = status;
3539
- }
3540
- };
3541
- var CR = 13;
3542
- var LF = 10;
3543
- var READ_SIZE = 64 * 1024;
3544
- var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
3545
- var REASON = {
3546
- 100: "Continue",
3547
- 200: "OK",
3548
- 400: "Bad Request",
3549
- 403: "Forbidden",
3550
- 408: "Request Timeout",
3551
- 411: "Length Required",
3552
- 413: "Content Too Large",
3553
- 417: "Expectation Failed",
3554
- 421: "Misdirected Request",
3555
- 431: "Request Header Fields Too Large",
3556
- 501: "Not Implemented",
3557
- 502: "Bad Gateway",
3558
- 503: "Service Unavailable",
3559
- 505: "HTTP Version Not Supported"
3560
- };
3561
- var Incoming = class {
3562
- source;
3563
- buf = new Uint8Array(0);
3564
- eof = false;
3565
- constructor(source) {
3566
- this.source = source;
3567
- }
3568
- get buffered() {
3569
- return this.buf.length;
3570
- }
3571
- async fill(signal) {
3572
- if (this.eof) return false;
3573
- const chunk2 = await this.source.read(READ_SIZE, signal);
3574
- if (chunk2.length === 0) {
3575
- this.eof = true;
3576
- return false;
3577
- }
3578
- const next = new Uint8Array(this.buf.length + chunk2.length);
3579
- next.set(this.buf);
3580
- next.set(chunk2, this.buf.length);
3581
- this.buf = next;
3582
- return true;
3583
- }
3584
- take(n) {
3585
- const out = this.buf.slice(0, n);
3586
- this.buf = this.buf.subarray(n);
3587
- return out;
3588
- }
3589
- async some(max, signal) {
3590
- if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
3591
- if (this.eof) return new Uint8Array(0);
3592
- const chunk2 = await this.source.read(max, signal);
3593
- if (chunk2.length === 0) this.eof = true;
3594
- return chunk2;
3595
- }
3596
- async head(limit, signal) {
3597
- let from = 0;
3598
- for (; ; ) {
3599
- const end = headEnd(this.buf, from);
3600
- if (end > 0) {
3601
- if (end > limit) throw new HttpError(431, "request head too large");
3602
- return this.take(end);
3603
- }
3604
- if (this.buf.length > limit) throw new HttpError(431, "request head too large");
3605
- from = Math.max(0, this.buf.length - 3);
3606
- if (!await this.fill(signal)) {
3607
- if (this.buf.length === 0) return null;
3608
- throw new HttpError(400, "connection closed inside a request head");
3609
- }
3610
- }
3611
- }
3612
- async line(limit, signal) {
3613
- let from = 0;
3614
- for (; ; ) {
3615
- const at = this.buf.indexOf(LF, from);
3616
- if (at >= 0) {
3617
- const line = this.take(at + 1);
3618
- const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
3619
- return text;
3620
- }
3621
- if (this.buf.length > limit) throw new HttpError(400, "line too long");
3622
- from = this.buf.length;
3623
- if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
3624
- }
4265
+ this.run(req).then(
4266
+ (result) => this.complete(id, result),
4267
+ (err) => this.complete(id, { ok: false, errno: "EIO", message: String(err) })
4268
+ );
4269
+ return id;
3625
4270
  }
3626
- async exactly(n, signal) {
3627
- while (this.buf.length < n) {
3628
- if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
3629
- }
3630
- return this.take(n);
4271
+ resolve(value) {
4272
+ const id = this.hold();
4273
+ this.complete(id, json3(value));
4274
+ return id;
3631
4275
  }
3632
- };
3633
- function headEnd(buf, from) {
3634
- for (let i = from; i < buf.length; i++) {
3635
- if (buf[i] !== LF) continue;
3636
- if (buf[i + 1] === LF) return i + 2;
3637
- if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
4276
+ hold() {
4277
+ const id = this.next++;
4278
+ this.results.set(id, void 0);
4279
+ return id;
3638
4280
  }
3639
- return 0;
3640
- }
3641
- function latin1(bytes) {
3642
- let out = "";
3643
- for (let i = 0; i < bytes.length; i += 32768) {
3644
- out += String.fromCharCode(...bytes.subarray(i, i + 32768));
4281
+ take(id) {
4282
+ if (!this.results.has(id)) throw new KernelError("EINVAL");
4283
+ const result = this.results.get(id);
4284
+ if (!result) throw new KernelError("EAGAIN");
4285
+ this.cancel(id);
4286
+ return result;
3645
4287
  }
3646
- return out;
3647
- }
3648
- function latin1Bytes(text) {
3649
- const out = new Uint8Array(text.length);
3650
- for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
3651
- return out;
3652
- }
3653
- function parseRequestHead(bytes) {
3654
- const lines = latin1(bytes).split(/\r?\n/);
3655
- while (lines.length > 0 && lines[0] === "") lines.shift();
3656
- const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
3657
- if (!match) throw new HttpError(400, "malformed request line");
3658
- if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
3659
- return {
3660
- method: match[1],
3661
- target: match[2],
3662
- minor: Number(match[4]),
3663
- headers: parseFields(lines)
3664
- };
3665
- }
3666
- function parseFields(lines) {
3667
- const headers = [];
3668
- for (const line of lines) {
3669
- if (line === "") continue;
3670
- if (line.startsWith(" ") || line.startsWith(" ")) {
3671
- throw new HttpError(400, "obsolete line folding");
3672
- }
3673
- const colon = line.indexOf(":");
3674
- const name2 = colon > 0 ? line.slice(0, colon) : "";
3675
- if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
3676
- headers.push([name2, line.slice(colon + 1).trim()]);
4288
+ cancel(id) {
4289
+ this.results.delete(id);
4290
+ const at = this.ready.indexOf(id);
4291
+ if (at >= 0) this.ready.splice(at, 1);
3677
4292
  }
3678
- return headers;
3679
- }
3680
- function fieldValues(headers, name2) {
3681
- const lower = name2.toLowerCase();
3682
- return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
3683
- }
3684
- function fieldTokens(headers, name2) {
3685
- return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
3686
- }
3687
- function requestFraming(headers) {
3688
- const codings = fieldTokens(headers, "transfer-encoding");
3689
- const lengths = fieldValues(headers, "content-length");
3690
- if (codings.length > 0) {
3691
- if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
3692
- if (codings.length !== 1 || codings[0] !== "chunked") {
3693
- throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
4293
+ async wait(timeoutMs, signal) {
4294
+ for (; ; ) {
4295
+ if (this.closed) return -1;
4296
+ const id = this.ready.shift();
4297
+ if (id !== void 0) return id;
4298
+ if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
4299
+ const woke = await this.sleep(timeoutMs, signal);
4300
+ if (!woke) return 0;
3694
4301
  }
3695
- return { kind: "chunked" };
3696
- }
3697
- if (lengths.length === 0) return { kind: "none" };
3698
- const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
3699
- const [only] = distinct;
3700
- if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
3701
- throw new HttpError(400, "invalid Content-Length");
3702
4302
  }
3703
- const length2 = Number(only);
3704
- return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
3705
- }
3706
- var MAX_CHUNK_LINE = 4096;
3707
- var MAX_TRAILERS = 64 * 1024;
3708
- async function readBody(incoming, framing, cap, signal) {
3709
- if (framing.kind === "none") return void 0;
3710
- if (framing.kind === "length") {
3711
- if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
3712
- return incoming.exactly(framing.length, signal);
4303
+ close() {
4304
+ this.closed = true;
4305
+ this.wake();
3713
4306
  }
3714
- const chunks2 = [];
3715
- let total = 0;
3716
- for (; ; ) {
3717
- const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
3718
- const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
3719
- if (!size) throw new HttpError(400, "malformed chunk size");
3720
- const n = Number.parseInt(size[1], 16);
3721
- if (n === 0) break;
3722
- total += n;
3723
- if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
3724
- chunks2.push(await incoming.exactly(n, signal));
3725
- if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
4307
+ sleep(timeoutMs, signal) {
4308
+ return new Promise((resolve, reject) => {
4309
+ let timer;
4310
+ const cleanup = () => {
4311
+ clearTimeout(timer);
4312
+ this.waiters.delete(wake);
4313
+ signal.removeEventListener("abort", abort);
4314
+ };
4315
+ const wake = () => {
4316
+ cleanup();
4317
+ resolve(true);
4318
+ };
4319
+ const abort = () => {
4320
+ cleanup();
4321
+ reject(new KernelError("EINTR"));
4322
+ };
4323
+ this.waiters.add(wake);
4324
+ signal.addEventListener("abort", abort, { once: true });
4325
+ if (timeoutMs !== void 0) {
4326
+ timer = setTimeout(() => {
4327
+ cleanup();
4328
+ resolve(false);
4329
+ }, timeoutMs);
4330
+ }
4331
+ });
3726
4332
  }
3727
- let trailers = 0;
3728
- for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
3729
- trailers += line.length;
3730
- if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
3731
- line = await incoming.line(MAX_TRAILERS, signal);
4333
+ complete(id, result) {
4334
+ if (!this.results.has(id)) return;
4335
+ this.results.set(id, result);
4336
+ this.ready.push(id);
4337
+ this.wake();
3732
4338
  }
3733
- return concat3(chunks2, total);
3734
- }
3735
- function concat3(chunks2, total = chunks2.reduce((n, c) => n + c.length, 0)) {
3736
- const out = new Uint8Array(total);
3737
- let at = 0;
3738
- for (const chunk2 of chunks2) {
3739
- out.set(chunk2, at);
3740
- at += chunk2.length;
4339
+ wake() {
4340
+ for (const wake of [...this.waiters]) wake();
3741
4341
  }
3742
- return out;
3743
- }
3744
- function safeValue(value) {
3745
- return !/[\r\n\0]/.test(value);
3746
- }
3747
- function responseHead(status, reason, headers) {
3748
- const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
3749
- let head = `HTTP/1.1 ${status} ${phrase}\r
3750
- `;
3751
- for (const [name2, value] of headers) {
3752
- if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
3753
- `;
4342
+ };
4343
+ async function hostSyscall(req, ctx) {
4344
+ switch (req.op) {
4345
+ case "lock":
4346
+ if (!ctx.locks) throw new KernelError("ENOSYS");
4347
+ if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
4348
+ throw new KernelError("EAGAIN");
4349
+ return done2;
4350
+ case "unlock":
4351
+ ctx.locks?.unlock(ctx.pid, req.path);
4352
+ return done2;
4353
+ case "async-submit":
4354
+ if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
4355
+ return json3(ctx.ops.submit(req.req));
4356
+ case "async-wait":
4357
+ return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
4358
+ case "async-take":
4359
+ return ctx.ops.take(req.id);
4360
+ case "async-resolve":
4361
+ return json3(ctx.ops.resolve(req.value));
4362
+ case "async-hold":
4363
+ return json3(ctx.ops.hold());
4364
+ case "async-cancel":
4365
+ ctx.ops.cancel(req.id);
4366
+ return done2;
4367
+ case "async-close":
4368
+ ctx.ops.close();
4369
+ return done2;
3754
4370
  }
3755
- return latin1Bytes(`${head}\r
3756
- `);
3757
4371
  }
3758
- function chunk(bytes) {
3759
- const size = latin1Bytes(`${bytes.length.toString(16)}\r
3760
- `);
3761
- const out = new Uint8Array(size.length + bytes.length + 2);
3762
- out.set(size);
3763
- out.set(bytes, size.length);
3764
- out[out.length - 2] = CR;
3765
- out[out.length - 1] = LF;
3766
- return out;
4372
+
4373
+ // src/kernel/net/loopback-names.ts
4374
+ var HOST_LOOPBACK = "host.slicc.internal";
4375
+ var PAGE_LOOPBACK = "kernel.localhost";
4376
+ var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
4377
+ function toHostLoopback(href) {
4378
+ const url = new URL(href);
4379
+ if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
4380
+ url.hostname = "127.0.0.1";
4381
+ return url.href;
3767
4382
  }
3768
- var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
3769
4383
 
3770
4384
  // src/kernel/net/proxy-service.ts
3771
4385
  var REALM_PROXY_PORT = 3128;
@@ -5358,23 +5972,23 @@ function terminalOf(ctx, fd) {
5358
5972
  if (!tty) throw new KernelError("ENOTTY");
5359
5973
  return tty;
5360
5974
  }
5361
- var json3 = (value) => ({ ok: true, kind: "json", json: value });
5975
+ var json4 = (value) => ({ ok: true, kind: "json", json: value });
5362
5976
  var done3 = { ok: true, kind: "void" };
5363
5977
  function ptySyscall(req, ctx) {
5364
5978
  switch (req.op) {
5365
5979
  case "pty-open": {
5366
5980
  if (!ctx.ptys) throw new KernelError("ENOENT");
5367
- return json3(ctx.fds.install(ctx.ptys.open().master, 3));
5981
+ return json4(ctx.fds.install(ctx.ptys.open().master, 3));
5368
5982
  }
5369
5983
  case "pty-slave-open": {
5370
5984
  if (!ctx.ptys) throw new KernelError("ENOENT");
5371
5985
  const pair = ctx.ptys.get(req.n);
5372
5986
  const fd = ctx.fds.install(pair.openSlave(), 3);
5373
5987
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
5374
- return json3(fd);
5988
+ return json4(fd);
5375
5989
  }
5376
5990
  case "pty-number":
5377
- return json3(masterOf(ctx, req.fd).index);
5991
+ return json4(masterOf(ctx, req.fd).index);
5378
5992
  case "pty-lock":
5379
5993
  masterOf(ctx, req.fd).locked = req.lock;
5380
5994
  return done3;
@@ -5386,7 +6000,7 @@ function ptySyscall(req, ctx) {
5386
6000
  return done3;
5387
6001
  }
5388
6002
  case "pty-list":
5389
- return json3(ctx.ptys?.numbers() ?? []);
6003
+ return json4(ctx.ptys?.numbers() ?? []);
5390
6004
  case "pty-packet":
5391
6005
  masterOf(ctx, req.fd).packet = req.on;
5392
6006
  return done3;
@@ -5464,7 +6078,7 @@ function socketAt(fds, fd) {
5464
6078
  if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
5465
6079
  return file;
5466
6080
  }
5467
- var ok = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
6081
+ var ok = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
5468
6082
  async function socketSyscall(req, proc) {
5469
6083
  const { fds, net } = proc;
5470
6084
  switch (req.op) {
@@ -7428,8 +8042,8 @@ function umountCall(target, flags) {
7428
8042
  var FSTAB_PATH = "/etc/fstab";
7429
8043
  var FSTAB_RETRIES = [1e3, 4e3, 16e3];
7430
8044
  var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
7431
- function unescape(field) {
7432
- return field.replace(
8045
+ function unescape(field2) {
8046
+ return field2.replace(
7433
8047
  /\\([0-7]{3})/g,
7434
8048
  (_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
7435
8049
  );
@@ -7468,28 +8082,34 @@ function wait(ms, signal) {
7468
8082
  });
7469
8083
  }
7470
8084
  async function mountFstab(lines, mount, signal, retries = FSTAB_RETRIES, discard = () => {
8085
+ }, settled = () => {
7471
8086
  }) {
7472
8087
  return Promise.all(
7473
- lines.map(async ({ line, spec, invalid }) => {
7474
- let failed = invalid;
7475
- for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
7476
- try {
7477
- const entry = await mount(spec);
7478
- if (!signal.aborted) return { line, target: spec.target, entry };
7479
- discard(entry);
7480
- failed = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
7481
- break;
7482
- } catch (err) {
7483
- failed = err;
7484
- if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
7485
- await wait(retries[attempt], signal);
7486
- }
7487
- }
7488
- const code = codeOf3(failed);
7489
- return { line, target: spec.target, code, error: String(failed?.message ?? code) };
8088
+ lines.map(async (each) => {
8089
+ const result = await mountLine(each, mount, signal, retries, discard);
8090
+ settled(each, result);
8091
+ return result;
7490
8092
  })
7491
8093
  );
7492
8094
  }
8095
+ async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
8096
+ let failed = invalid;
8097
+ for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
8098
+ try {
8099
+ const entry = await mount(spec);
8100
+ if (!signal.aborted) return { line, target: spec.target, entry };
8101
+ discard(entry);
8102
+ failed = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
8103
+ break;
8104
+ } catch (err) {
8105
+ failed = err;
8106
+ if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
8107
+ await wait(retries[attempt], signal);
8108
+ }
8109
+ }
8110
+ const code = codeOf3(failed);
8111
+ return { line, target: spec.target, code, error: String(failed?.message ?? code) };
8112
+ }
7493
8113
 
7494
8114
  // src/mount/hostfs.ts
7495
8115
  var HOSTFS_IO = 16 * 1024 * 1024;
@@ -8080,7 +8700,7 @@ function importedMemory(bytes) {
8080
8700
  const count = u32();
8081
8701
  for (let i = 0; i < count; i++) {
8082
8702
  const module = name2();
8083
- const field = name2();
8703
+ const field2 = name2();
8084
8704
  const kind = bytes[at++];
8085
8705
  if (kind === 0) u32();
8086
8706
  else if (kind === 1) {
@@ -8088,7 +8708,7 @@ function importedMemory(bytes) {
8088
8708
  limits();
8089
8709
  } else if (kind === 2) {
8090
8710
  const l = limits();
8091
- return { module, name: field, ...l };
8711
+ return { module, name: field2, ...l };
8092
8712
  } else if (kind === 3) at += 2;
8093
8713
  else if (kind === 4) {
8094
8714
  at++;
@@ -8179,12 +8799,12 @@ function readImports(r, types) {
8179
8799
  const foreign = {};
8180
8800
  for (let i = r.u32(); i > 0; i--) {
8181
8801
  const module = r.name();
8182
- const field = r.name();
8802
+ const field2 = r.name();
8183
8803
  const kind = r.byte();
8184
8804
  if (kind !== 0) skipImport(r, kind);
8185
8805
  else {
8186
8806
  const type = types[r.u32()] ?? "other";
8187
- if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field] = type;
8807
+ if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
8188
8808
  }
8189
8809
  }
8190
8810
  return foreign;
@@ -8291,6 +8911,7 @@ var Launcher = class {
8291
8911
  locks = new LockTable();
8292
8912
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
8293
8913
  net = new LoopbackNet();
8914
+ cdp;
8294
8915
  ca;
8295
8916
  transport;
8296
8917
  watchers = new FsWatchers();
@@ -8301,6 +8922,7 @@ var Launcher = class {
8301
8922
  onMountPending;
8302
8923
  removable = /* @__PURE__ */ new Map();
8303
8924
  hostfs;
8925
+ fstabRetries;
8304
8926
  inserted = /* @__PURE__ */ new Map();
8305
8927
  openFiles = /* @__PURE__ */ new Set();
8306
8928
  held = /* @__PURE__ */ new Set();
@@ -8322,10 +8944,12 @@ var Launcher = class {
8322
8944
  this.media = options.media ?? memoryMedia();
8323
8945
  this.onMountPending = options.onMountPending;
8324
8946
  this.hostfs = options;
8947
+ this.fstabRetries = options.fstabRetries ?? FSTAB_RETRIES;
8325
8948
  this.processMounts = options.processMounts ?? true;
8326
8949
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
8327
8950
  this.modulesDir = options.modules ?? "/node_modules";
8328
- this.env = { ...networkEnv(), ...options.env };
8951
+ this.cdp = new CdpHosts(options.cdp);
8952
+ this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
8329
8953
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
8330
8954
  this.transport = options.transport ?? missingTransport();
8331
8955
  enableNetwork(this.net, {
@@ -8333,6 +8957,7 @@ var Launcher = class {
8333
8957
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
8334
8958
  ca: this.ca
8335
8959
  });
8960
+ enableCdp(this.net, this.cdp);
8336
8961
  this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
8337
8962
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
8338
8963
  this.catalog = void 0;
@@ -8755,12 +9380,22 @@ var Launcher = class {
8755
9380
  }
8756
9381
  async mountFstab() {
8757
9382
  const text = await this.base.readFile(FSTAB_PATH).catch(() => "");
9383
+ const lines = parseFstab(text);
9384
+ for (const { spec } of lines) this.mounts.note(spec, "pending");
8758
9385
  return mountFstab(
8759
- parseFstab(text),
8760
- (spec) => this.mount(spec),
9386
+ lines,
9387
+ (spec) => this.mount(spec).catch((err) => {
9388
+ this.mounts.note(spec, "pending", err?.message ?? String(err));
9389
+ throw err;
9390
+ }),
8761
9391
  this.booting.signal,
8762
- FSTAB_RETRIES,
8763
- (entry) => this.umount(entry.target, true)
9392
+ this.fstabRetries,
9393
+ (entry) => this.umount(entry.target, true),
9394
+ ({ spec }, result) => {
9395
+ if (!result.code || result.code === "ECANCELED" || this.booting.signal.aborted) {
9396
+ this.mounts.unnote(spec.target);
9397
+ } else this.mounts.note(spec, "failed", result.error);
9398
+ }
8764
9399
  );
8765
9400
  }
8766
9401
  environment(cwd, extra) {
@@ -9394,6 +10029,8 @@ var Channel = class {
9394
10029
  failure;
9395
10030
  pending = /* @__PURE__ */ new Map();
9396
10031
  watching = /* @__PURE__ */ new Map();
10032
+ cdpHooks = /* @__PURE__ */ new Map();
10033
+ cdpOpen = /* @__PURE__ */ new Set();
9397
10034
  protocol = PROTOCOL;
9398
10035
  nextId = 0;
9399
10036
  port;
@@ -9414,6 +10051,8 @@ var Channel = class {
9414
10051
  this.failure = error;
9415
10052
  this.greeted.reject(error);
9416
10053
  this.remote?.fail(error);
10054
+ for (const close of [...this.cdpOpen]) close();
10055
+ this.cdpOpen.clear();
9417
10056
  for (const call of this.pending.values()) call.reject(error);
9418
10057
  this.pending.clear();
9419
10058
  this.release();
@@ -9434,6 +10073,10 @@ var Channel = class {
9434
10073
  return this.request(call).done;
9435
10074
  }
9436
10075
  receive(data) {
10076
+ if (data.cdpOpen && data.port) {
10077
+ this.openCdp(data);
10078
+ return;
10079
+ }
9437
10080
  if (data.watch !== void 0) {
9438
10081
  this.watching.get(data.watch)?.(data.change);
9439
10082
  return;
@@ -9465,7 +10108,39 @@ var Channel = class {
9465
10108
  if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
9466
10109
  else call.resolve(reply.result);
9467
10110
  }
10111
+ openCdp({ cdpOpen: { registration, ...request }, port }) {
10112
+ const hook = registration === void 0 ? void 0 : this.cdpHooks.get(registration);
10113
+ if (!hook || this.failure) {
10114
+ port.postMessage({ error: "this client no longer serves CDP" });
10115
+ port.close();
10116
+ return;
10117
+ }
10118
+ const close = serveCdpPort(port, hook, request, () => this.cdpOpen.delete(close));
10119
+ this.cdpOpen.add(close);
10120
+ }
9468
10121
  };
10122
+ async function serveCdpOn(channel, hook, options) {
10123
+ await since(channel, 5, "CDP serving", async () => {
10124
+ });
10125
+ const req = {
10126
+ op: "serve-cdp",
10127
+ ...options.runtime ? { runtime: options.runtime } : {}
10128
+ };
10129
+ const { id, done: done4 } = channel.request(req);
10130
+ channel.cdpHooks.set(id, hook);
10131
+ try {
10132
+ await done4;
10133
+ } catch (error) {
10134
+ channel.cdpHooks.delete(id);
10135
+ throw error;
10136
+ }
10137
+ return {
10138
+ close: () => {
10139
+ if (!channel.cdpHooks.delete(id)) return;
10140
+ void channel.call({ op: "unserve-cdp", registration: id }).catch(() => void 0);
10141
+ }
10142
+ };
10143
+ }
9469
10144
  async function dialOn(channel, { port, host }) {
9470
10145
  const req = { op: "dial", port, ...host ? { host } : {} };
9471
10146
  return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
@@ -9651,6 +10326,7 @@ async function attachKernel(port, options = {}) {
9651
10326
  dial: (options2) => dialOn(channel, options2),
9652
10327
  loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
9653
10328
  fs: fsOn(channel),
10329
+ serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
9654
10330
  async close(o = {}) {
9655
10331
  if (channel.failure) return;
9656
10332
  await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
@@ -9731,7 +10407,9 @@ async function createNodeKernel(options = {}) {
9731
10407
  ...options.modules ? { modules: options.modules } : {},
9732
10408
  ...options.env ? { env: options.env } : {},
9733
10409
  ...transport ? { transport } : {},
9734
- ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {}
10410
+ ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
10411
+ ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
10412
+ ...options.cdp ? { cdp: options.cdp } : {}
9735
10413
  });
9736
10414
  await launcher.prepare();
9737
10415
  const guard = (work) => new Promise((resolve, reject) => {
@@ -9811,6 +10489,7 @@ async function createNodeKernel(options = {}) {
9811
10489
  for (const client of [...clients]) client.detach();
9812
10490
  for (const reject of [...pending]) reject(new Error(TERMINATED));
9813
10491
  for (const kill of [...live]) kill();
10492
+ launcher.cdp.close();
9814
10493
  launcher.unmountAll();
9815
10494
  }
9816
10495
  };