@ai-ecoverse/slicc-kernel 1.22.1 → 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,6 +1949,705 @@ var OpfsFs = class {
1817
1949
  }
1818
1950
  };
1819
1951
 
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;
1958
+ }
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;
1989
+ }
1990
+ get buffered() {
1991
+ return this.buf.length;
1992
+ }
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;
1999
+ }
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;
2005
+ }
2006
+ take(n) {
2007
+ const out = this.buf.slice(0, n);
2008
+ this.buf = this.buf.subarray(n);
2009
+ return out;
2010
+ }
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
+ }
2032
+ }
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");
2046
+ }
2047
+ }
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");
2051
+ }
2052
+ return this.take(n);
2053
+ }
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
+
1820
2651
  // src/commands.ts
1821
2652
  var NAME = /^[A-Za-z0-9._+-]+$/;
1822
2653
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -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;
@@ -2823,7 +3654,7 @@ var MountFs = class {
2823
3654
  } finally {
2824
3655
  await mount.conn.call({ op: "release", fh });
2825
3656
  }
2826
- return concat2(chunks2, total);
3657
+ return concat3(chunks2, total);
2827
3658
  }
2828
3659
  async writeFile(path, content) {
2829
3660
  const real = await this.follow(path, 0, true);
@@ -2887,7 +3718,7 @@ var MountFs = class {
2887
3718
  } finally {
2888
3719
  await mount.conn.call({ op: "release", fh });
2889
3720
  }
2890
- return concat2(chunks2, total);
3721
+ return concat3(chunks2, total);
2891
3722
  }
2892
3723
  fits(mount, real, end) {
2893
3724
  if (mount.maxFile > 0 && end > mount.maxFile) {
@@ -3140,7 +3971,7 @@ async function storageUsage() {
3140
3971
  return { quota: estimate.quota, usage: estimate.usage ?? 0 };
3141
3972
  }
3142
3973
  var done = { ok: true, kind: "void" };
3143
- function json(value) {
3974
+ function json2(value) {
3144
3975
  return { ok: true, kind: "json", json: value };
3145
3976
  }
3146
3977
  async function run(entry, path, req) {
@@ -3152,15 +3983,15 @@ async function run(entry, path, req) {
3152
3983
  await fs.writeFile(path, req.body ?? new Uint8Array(0));
3153
3984
  return done;
3154
3985
  case "exists":
3155
- return json(await fs.exists(path));
3986
+ return json2(await fs.exists(path));
3156
3987
  case "stat":
3157
- return json(statJson(await fs.stat(path)));
3988
+ return json2(statJson(await fs.stat(path)));
3158
3989
  case "lstat":
3159
- return json(statJson(await fs.lstat(path)));
3990
+ return json2(statJson(await fs.lstat(path)));
3160
3991
  case "readdir":
3161
- return json(await fs.readdir(path));
3992
+ return json2(await fs.readdir(path));
3162
3993
  case "readdir-stat":
3163
- return json(await readdirStat(fs, path));
3994
+ return json2(await readdirStat(fs, path));
3164
3995
  case "mkdir":
3165
3996
  await fs.mkdir(path, { recursive: true });
3166
3997
  return done;
@@ -3186,7 +4017,7 @@ async function run(entry, path, req) {
3186
4017
  await fs.symlink(req.arg2 ?? "", path);
3187
4018
  return done;
3188
4019
  case "readlink":
3189
- return json(await fs.readlink(path));
4020
+ return json2(await fs.readlink(path));
3190
4021
  case "chmod":
3191
4022
  await fs.chmod(path, req.mode ?? 0);
3192
4023
  return done;
@@ -3217,7 +4048,7 @@ async function dispatchSyncFs(req) {
3217
4048
  const { fs, cwd } = entry;
3218
4049
  try {
3219
4050
  const path = fs.resolvePath(cwd, req.path);
3220
- 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());
3221
4052
  if (req.op === "hold") {
3222
4053
  entry.hold?.(path, req.mode === 1);
3223
4054
  return done;
@@ -3390,7 +4221,7 @@ var HOST_OPS = [
3390
4221
  "async-cancel",
3391
4222
  "async-close"
3392
4223
  ];
3393
- var json2 = (value) => ({ ok: true, kind: "json", json: value });
4224
+ var json3 = (value) => ({ ok: true, kind: "json", json: value });
3394
4225
  var done2 = { ok: true, kind: "void" };
3395
4226
  var LockTable = class {
3396
4227
  held = /* @__PURE__ */ new Map();
@@ -3439,7 +4270,7 @@ var AsyncOps = class {
3439
4270
  }
3440
4271
  resolve(value) {
3441
4272
  const id = this.hold();
3442
- this.complete(id, json2(value));
4273
+ this.complete(id, json3(value));
3443
4274
  return id;
3444
4275
  }
3445
4276
  hold() {
@@ -3521,15 +4352,15 @@ async function hostSyscall(req, ctx) {
3521
4352
  return done2;
3522
4353
  case "async-submit":
3523
4354
  if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
3524
- return json2(ctx.ops.submit(req.req));
4355
+ return json3(ctx.ops.submit(req.req));
3525
4356
  case "async-wait":
3526
- return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
4357
+ return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
3527
4358
  case "async-take":
3528
4359
  return ctx.ops.take(req.id);
3529
4360
  case "async-resolve":
3530
- return json2(ctx.ops.resolve(req.value));
4361
+ return json3(ctx.ops.resolve(req.value));
3531
4362
  case "async-hold":
3532
- return json2(ctx.ops.hold());
4363
+ return json3(ctx.ops.hold());
3533
4364
  case "async-cancel":
3534
4365
  ctx.ops.cancel(req.id);
3535
4366
  return done2;
@@ -3550,243 +4381,6 @@ function toHostLoopback(href) {
3550
4381
  return url.href;
3551
4382
  }
3552
4383
 
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);
3651
- }
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;
3658
- }
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));
3665
- }
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()]);
3697
- }
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(", ")}`);
3714
- }
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
- }
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);
3733
- }
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");
3746
- }
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);
3752
- }
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;
3761
- }
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
- `;
3774
- }
3775
- return latin1Bytes(`${head}\r
3776
- `);
3777
- }
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;
3787
- }
3788
- var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
3789
-
3790
4384
  // src/kernel/net/proxy-service.ts
3791
4385
  var REALM_PROXY_PORT = 3128;
3792
4386
  var DEFAULT_LIMITS = {
@@ -5378,23 +5972,23 @@ function terminalOf(ctx, fd) {
5378
5972
  if (!tty) throw new KernelError("ENOTTY");
5379
5973
  return tty;
5380
5974
  }
5381
- var json3 = (value) => ({ ok: true, kind: "json", json: value });
5975
+ var json4 = (value) => ({ ok: true, kind: "json", json: value });
5382
5976
  var done3 = { ok: true, kind: "void" };
5383
5977
  function ptySyscall(req, ctx) {
5384
5978
  switch (req.op) {
5385
5979
  case "pty-open": {
5386
5980
  if (!ctx.ptys) throw new KernelError("ENOENT");
5387
- return json3(ctx.fds.install(ctx.ptys.open().master, 3));
5981
+ return json4(ctx.fds.install(ctx.ptys.open().master, 3));
5388
5982
  }
5389
5983
  case "pty-slave-open": {
5390
5984
  if (!ctx.ptys) throw new KernelError("ENOENT");
5391
5985
  const pair = ctx.ptys.get(req.n);
5392
5986
  const fd = ctx.fds.install(pair.openSlave(), 3);
5393
5987
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
5394
- return json3(fd);
5988
+ return json4(fd);
5395
5989
  }
5396
5990
  case "pty-number":
5397
- return json3(masterOf(ctx, req.fd).index);
5991
+ return json4(masterOf(ctx, req.fd).index);
5398
5992
  case "pty-lock":
5399
5993
  masterOf(ctx, req.fd).locked = req.lock;
5400
5994
  return done3;
@@ -5406,7 +6000,7 @@ function ptySyscall(req, ctx) {
5406
6000
  return done3;
5407
6001
  }
5408
6002
  case "pty-list":
5409
- return json3(ctx.ptys?.numbers() ?? []);
6003
+ return json4(ctx.ptys?.numbers() ?? []);
5410
6004
  case "pty-packet":
5411
6005
  masterOf(ctx, req.fd).packet = req.on;
5412
6006
  return done3;
@@ -5484,7 +6078,7 @@ function socketAt(fds, fd) {
5484
6078
  if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
5485
6079
  return file;
5486
6080
  }
5487
- 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 };
5488
6082
  async function socketSyscall(req, proc) {
5489
6083
  const { fds, net } = proc;
5490
6084
  switch (req.op) {
@@ -7448,8 +8042,8 @@ function umountCall(target, flags) {
7448
8042
  var FSTAB_PATH = "/etc/fstab";
7449
8043
  var FSTAB_RETRIES = [1e3, 4e3, 16e3];
7450
8044
  var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
7451
- function unescape(field) {
7452
- return field.replace(
8045
+ function unescape(field2) {
8046
+ return field2.replace(
7453
8047
  /\\([0-7]{3})/g,
7454
8048
  (_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
7455
8049
  );
@@ -8106,7 +8700,7 @@ function importedMemory(bytes) {
8106
8700
  const count = u32();
8107
8701
  for (let i = 0; i < count; i++) {
8108
8702
  const module = name2();
8109
- const field = name2();
8703
+ const field2 = name2();
8110
8704
  const kind = bytes[at++];
8111
8705
  if (kind === 0) u32();
8112
8706
  else if (kind === 1) {
@@ -8114,7 +8708,7 @@ function importedMemory(bytes) {
8114
8708
  limits();
8115
8709
  } else if (kind === 2) {
8116
8710
  const l = limits();
8117
- return { module, name: field, ...l };
8711
+ return { module, name: field2, ...l };
8118
8712
  } else if (kind === 3) at += 2;
8119
8713
  else if (kind === 4) {
8120
8714
  at++;
@@ -8205,12 +8799,12 @@ function readImports(r, types) {
8205
8799
  const foreign = {};
8206
8800
  for (let i = r.u32(); i > 0; i--) {
8207
8801
  const module = r.name();
8208
- const field = r.name();
8802
+ const field2 = r.name();
8209
8803
  const kind = r.byte();
8210
8804
  if (kind !== 0) skipImport(r, kind);
8211
8805
  else {
8212
8806
  const type = types[r.u32()] ?? "other";
8213
- if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field] = type;
8807
+ if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
8214
8808
  }
8215
8809
  }
8216
8810
  return foreign;
@@ -8317,6 +8911,7 @@ var Launcher = class {
8317
8911
  locks = new LockTable();
8318
8912
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
8319
8913
  net = new LoopbackNet();
8914
+ cdp;
8320
8915
  ca;
8321
8916
  transport;
8322
8917
  watchers = new FsWatchers();
@@ -8353,7 +8948,8 @@ var Launcher = class {
8353
8948
  this.processMounts = options.processMounts ?? true;
8354
8949
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
8355
8950
  this.modulesDir = options.modules ?? "/node_modules";
8356
- this.env = { ...networkEnv(), ...options.env };
8951
+ this.cdp = new CdpHosts(options.cdp);
8952
+ this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
8357
8953
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
8358
8954
  this.transport = options.transport ?? missingTransport();
8359
8955
  enableNetwork(this.net, {
@@ -8361,6 +8957,7 @@ var Launcher = class {
8361
8957
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
8362
8958
  ca: this.ca
8363
8959
  });
8960
+ enableCdp(this.net, this.cdp);
8364
8961
  this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
8365
8962
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
8366
8963
  this.catalog = void 0;
@@ -9432,6 +10029,8 @@ var Channel = class {
9432
10029
  failure;
9433
10030
  pending = /* @__PURE__ */ new Map();
9434
10031
  watching = /* @__PURE__ */ new Map();
10032
+ cdpHooks = /* @__PURE__ */ new Map();
10033
+ cdpOpen = /* @__PURE__ */ new Set();
9435
10034
  protocol = PROTOCOL;
9436
10035
  nextId = 0;
9437
10036
  port;
@@ -9452,6 +10051,8 @@ var Channel = class {
9452
10051
  this.failure = error;
9453
10052
  this.greeted.reject(error);
9454
10053
  this.remote?.fail(error);
10054
+ for (const close of [...this.cdpOpen]) close();
10055
+ this.cdpOpen.clear();
9455
10056
  for (const call of this.pending.values()) call.reject(error);
9456
10057
  this.pending.clear();
9457
10058
  this.release();
@@ -9472,6 +10073,10 @@ var Channel = class {
9472
10073
  return this.request(call).done;
9473
10074
  }
9474
10075
  receive(data) {
10076
+ if (data.cdpOpen && data.port) {
10077
+ this.openCdp(data);
10078
+ return;
10079
+ }
9475
10080
  if (data.watch !== void 0) {
9476
10081
  this.watching.get(data.watch)?.(data.change);
9477
10082
  return;
@@ -9503,7 +10108,39 @@ var Channel = class {
9503
10108
  if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
9504
10109
  else call.resolve(reply.result);
9505
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
+ }
9506
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
+ }
9507
10144
  async function dialOn(channel, { port, host }) {
9508
10145
  const req = { op: "dial", port, ...host ? { host } : {} };
9509
10146
  return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
@@ -9689,6 +10326,7 @@ async function attachKernel(port, options = {}) {
9689
10326
  dial: (options2) => dialOn(channel, options2),
9690
10327
  loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
9691
10328
  fs: fsOn(channel),
10329
+ serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
9692
10330
  async close(o = {}) {
9693
10331
  if (channel.failure) return;
9694
10332
  await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
@@ -9770,7 +10408,8 @@ async function createNodeKernel(options = {}) {
9770
10408
  ...options.env ? { env: options.env } : {},
9771
10409
  ...transport ? { transport } : {},
9772
10410
  ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
9773
- ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {}
10411
+ ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {},
10412
+ ...options.cdp ? { cdp: options.cdp } : {}
9774
10413
  });
9775
10414
  await launcher.prepare();
9776
10415
  const guard = (work) => new Promise((resolve, reject) => {
@@ -9850,6 +10489,7 @@ async function createNodeKernel(options = {}) {
9850
10489
  for (const client of [...clients]) client.detach();
9851
10490
  for (const reject of [...pending]) reject(new Error(TERMINATED));
9852
10491
  for (const kill of [...live]) kill();
10492
+ launcher.cdp.close();
9853
10493
  launcher.unmountAll();
9854
10494
  }
9855
10495
  };