@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.
@@ -224,8 +224,51 @@ function pinScript(url) {
224
224
  }
225
225
  }
226
226
 
227
+ // src/cdp/port.ts
228
+ function portCdpHook(ask, gone) {
229
+ return (request2) => new Promise((resolve, reject) => {
230
+ const { port1, port2 } = new MessageChannel();
231
+ let closed = false;
232
+ const conn = {
233
+ send: (m) => {
234
+ if (!closed) port1.postMessage({ m });
235
+ },
236
+ onmessage: null,
237
+ onclose: null,
238
+ close: () => {
239
+ if (closed) return;
240
+ closed = true;
241
+ port1.postMessage({ close: true });
242
+ port1.close();
243
+ }
244
+ };
245
+ port1.addEventListener("message", ({ data }) => {
246
+ if ("m" in data) conn.onmessage?.(data.m);
247
+ else if ("open" in data) resolve(conn);
248
+ else if ("error" in data) {
249
+ closed = true;
250
+ port1.close();
251
+ reject(new Error(data.error));
252
+ } else if (!closed) {
253
+ closed = true;
254
+ port1.close();
255
+ conn.onclose?.(data.close ?? void 0);
256
+ }
257
+ });
258
+ port1.start();
259
+ void gone?.then(() => {
260
+ if (closed) return;
261
+ closed = true;
262
+ port1.close();
263
+ reject(new Error("the CDP host is gone"));
264
+ conn.onclose?.("the CDP host is gone");
265
+ });
266
+ ask(request2, port2);
267
+ });
268
+ }
269
+
227
270
  // src/client/protocol.ts
228
- var PROTOCOL = [1, 4];
271
+ var PROTOCOL = [1, 5];
229
272
  var FS_METHODS = [
230
273
  "readFile",
231
274
  "writeFile",
@@ -887,7 +930,12 @@ function keyOf(addr) {
887
930
  var LoopbackNet = class {
888
931
  bound = /* @__PURE__ */ new Map();
889
932
  activators = /* @__PURE__ */ new Map();
933
+ internal = /* @__PURE__ */ new Set();
890
934
  nextEphemeral = EPHEMERAL_FIRST;
935
+ kernelOnly(port, reserve = false) {
936
+ if (reserve) this.internal.add(port);
937
+ return this.internal.has(port);
938
+ }
891
939
  activate(addr, start) {
892
940
  this.activators.set(keyOf(addr), start);
893
941
  }
@@ -948,6 +996,14 @@ var LOOPBACK_NAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "
948
996
  function dialSocket(net, port, host = "127.0.0.1") {
949
997
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new KernelError("EINVAL");
950
998
  const target = LOOPBACK_NAMES.has(host.toLowerCase()) ? "127.0.0.1" : host;
999
+ if (net.kernelOnly(port)) {
1000
+ throw Object.assign(
1001
+ new Error(`ECONNREFUSED: port ${port} only takes connections from inside the kernel`),
1002
+ {
1003
+ code: "ECONNREFUSED"
1004
+ }
1005
+ );
1006
+ }
951
1007
  return net.connect({ family: "inet", host: target, port });
952
1008
  }
953
1009
  var codeOf = (err) => {
@@ -1236,6 +1292,43 @@ function joinable(l, pgid, ours) {
1236
1292
  if (sid === void 0) throw fsError("ESRCH", `no process group ${pgid}`);
1237
1293
  if (!ours(sid)) throw fsError("EPERM", `process group ${pgid} is in another session`);
1238
1294
  }
1295
+ function cdpRegistry(send, gone) {
1296
+ const registered = /* @__PURE__ */ new Map();
1297
+ const drop = (id) => {
1298
+ registered.get(id)?.();
1299
+ return registered.delete(id);
1300
+ };
1301
+ return {
1302
+ serve(l, req) {
1303
+ const hook = portCdpHook(
1304
+ (request2, cdp) => send({ cdpOpen: { ...request2, registration: req.id }, port: cdp }, [
1305
+ cdp
1306
+ ]),
1307
+ gone
1308
+ );
1309
+ registered.set(req.id, l.cdp.register(hook, req.runtime));
1310
+ return req.id;
1311
+ },
1312
+ drop,
1313
+ close: () => {
1314
+ for (const id of [...registered.keys()]) drop(id);
1315
+ }
1316
+ };
1317
+ }
1318
+ function watchOp(l, req, watches, send) {
1319
+ if (req.op === "unwatch") {
1320
+ watches.get(req.watch)?.();
1321
+ watches.delete(req.watch);
1322
+ return { result: true };
1323
+ }
1324
+ const unwatch = l.watchers.watch(
1325
+ req.paths,
1326
+ { recursive: req.recursive },
1327
+ (change) => send({ watch: req.id, change })
1328
+ );
1329
+ watches.set(req.id, unwatch);
1330
+ return { result: req.id };
1331
+ }
1239
1332
  function serveClient(port, host) {
1240
1333
  let state = "new";
1241
1334
  const terminals = /* @__PURE__ */ new Map();
@@ -1248,6 +1341,7 @@ function serveClient(port, host) {
1248
1341
  if (state !== "closed") port.postMessage(message2, transfer);
1249
1342
  };
1250
1343
  const reply = (id, body, transfer) => send({ id, ...body }, transfer);
1344
+ const cdps = cdpRegistry(send, ended.promise);
1251
1345
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
1252
1346
  if (state === "closed") return;
1253
1347
  send({ bye });
@@ -1255,6 +1349,7 @@ function serveClient(port, host) {
1255
1349
  net?.close();
1256
1350
  for (const unwatch of watches.values()) unwatch();
1257
1351
  watches.clear();
1352
+ cdps.close();
1258
1353
  for (const session of terminals.values()) session.close();
1259
1354
  terminals.clear();
1260
1355
  if (kill) killGroups(host, [...groups]);
@@ -1349,19 +1444,9 @@ function serveClient(port, host) {
1349
1444
  throw new Error(`unknown file system call ${req.method}`);
1350
1445
  }
1351
1446
  return fsCall(l.fs, req.method, req.args);
1352
- case "watch": {
1353
- const unwatch = l.watchers.watch(
1354
- req.paths,
1355
- { recursive: req.recursive },
1356
- (change) => send({ watch: req.id, change })
1357
- );
1358
- watches.set(req.id, unwatch);
1359
- return { result: req.id };
1360
- }
1447
+ case "watch":
1361
1448
  case "unwatch":
1362
- watches.get(req.watch)?.();
1363
- watches.delete(req.watch);
1364
- return { result: true };
1449
+ return watchOp(l, req, watches, send);
1365
1450
  case "mount":
1366
1451
  return { result: await l.mount(req.spec) };
1367
1452
  case "umount":
@@ -1371,6 +1456,10 @@ function serveClient(port, host) {
1371
1456
  return { result: l.mounts.list() };
1372
1457
  case "dial":
1373
1458
  return dialFor(l, req, watches);
1459
+ case "serve-cdp":
1460
+ return { result: cdps.serve(l, req) };
1461
+ case "unserve-cdp":
1462
+ return { result: cdps.drop(req.registration) };
1374
1463
  case "detach":
1375
1464
  return { result: true };
1376
1465
  default:
@@ -1934,6 +2023,8 @@ var REASON = {
1934
2023
  200: "OK",
1935
2024
  400: "Bad Request",
1936
2025
  403: "Forbidden",
2026
+ 404: "Not Found",
2027
+ 405: "Method Not Allowed",
1937
2028
  408: "Request Timeout",
1938
2029
  411: "Length Required",
1939
2030
  413: "Content Too Large",
@@ -1943,6 +2034,7 @@ var REASON = {
1943
2034
  501: "Not Implemented",
1944
2035
  502: "Bad Gateway",
1945
2036
  503: "Service Unavailable",
2037
+ 504: "Gateway Timeout",
1946
2038
  505: "HTTP Version Not Supported"
1947
2039
  };
1948
2040
  var Incoming = class {
@@ -3388,6 +3480,465 @@ var RemoteTransport = class {
3388
3480
  }
3389
3481
  };
3390
3482
 
3483
+ // src/cdp/types.ts
3484
+ var CDP_PORT = 9222;
3485
+ var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
3486
+
3487
+ // src/cdp/websocket.ts
3488
+ var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
3489
+ var MAX_MESSAGE = 256 * 1024 * 1024;
3490
+ var CLOSE = {
3491
+ normal: 1e3,
3492
+ protocol: 1002,
3493
+ unsupported: 1003,
3494
+ invalid: 1007,
3495
+ tooBig: 1009,
3496
+ error: 1011
3497
+ };
3498
+ var OP_TEXT = 1;
3499
+ var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
3500
+ var WsError = class extends Error {
3501
+ code;
3502
+ constructor(code, message2) {
3503
+ super(message2);
3504
+ this.code = code;
3505
+ }
3506
+ };
3507
+ function field(headers, name2) {
3508
+ return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
3509
+ }
3510
+ async function acceptKey(headers) {
3511
+ const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
3512
+ if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
3513
+ throw new HttpError(400, "expected a WebSocket upgrade");
3514
+ }
3515
+ if (field(headers, "sec-websocket-version") !== "13") {
3516
+ throw new HttpError(400, "Sec-WebSocket-Version must be 13");
3517
+ }
3518
+ const key = field(headers, "sec-websocket-key") ?? "";
3519
+ if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
3520
+ const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
3521
+ return btoa(String.fromCharCode(...new Uint8Array(digest)));
3522
+ }
3523
+ function isUpgrade(headers) {
3524
+ return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
3525
+ }
3526
+ function frame(opcode, payload) {
3527
+ const n = payload.length;
3528
+ const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
3529
+ const out = new Uint8Array(size + n);
3530
+ const view = new DataView(out.buffer);
3531
+ out[0] = 128 | opcode;
3532
+ if (n < 126) out[1] = n;
3533
+ else if (n < 65536) {
3534
+ out[1] = 126;
3535
+ view.setUint16(2, n);
3536
+ } else {
3537
+ out[1] = 127;
3538
+ view.setBigUint64(2, BigInt(n));
3539
+ }
3540
+ out.set(payload, size);
3541
+ return out;
3542
+ }
3543
+ function closeFrame(code, reason = "") {
3544
+ let bytes = new TextEncoder().encode(reason);
3545
+ if (bytes.length > 123) bytes = bytes.subarray(0, 123);
3546
+ const payload = new Uint8Array(2 + bytes.length);
3547
+ new DataView(payload.buffer).setUint16(0, code);
3548
+ payload.set(bytes, 2);
3549
+ return frame(OP.close, payload);
3550
+ }
3551
+ function pongFrame(payload) {
3552
+ return frame(OP.pong, payload);
3553
+ }
3554
+ async function fill(incoming, out, signal) {
3555
+ for (let at = 0; at < out.length; ) {
3556
+ const piece = await incoming.some(out.length - at, signal);
3557
+ if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
3558
+ out.set(piece, at);
3559
+ at += piece.length;
3560
+ }
3561
+ }
3562
+ var fatal = new TextDecoder("utf-8", { fatal: true });
3563
+ function decode(bytes) {
3564
+ try {
3565
+ return fatal.decode(bytes);
3566
+ } catch {
3567
+ throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
3568
+ }
3569
+ }
3570
+ var WsReader = class {
3571
+ incoming;
3572
+ max;
3573
+ parts = [];
3574
+ size = 0;
3575
+ continuing = false;
3576
+ constructor(incoming, max = MAX_MESSAGE) {
3577
+ this.incoming = incoming;
3578
+ this.max = max;
3579
+ }
3580
+ async next(signal) {
3581
+ for (; ; ) {
3582
+ const first = await this.incoming.some(1, signal);
3583
+ if (first.length === 0) return null;
3584
+ const head = new Uint8Array(2);
3585
+ head[0] = first[0];
3586
+ await fill(this.incoming, head.subarray(1), signal);
3587
+ const fin = (head[0] & 128) !== 0;
3588
+ const opcode = head[0] & 15;
3589
+ if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
3590
+ if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
3591
+ const length2 = await this.length(head[1] & 127, signal);
3592
+ const control = opcode >= 8;
3593
+ if (control && (length2 > 125 || !fin)) {
3594
+ throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
3595
+ }
3596
+ if (!control && this.size + length2 > this.max) {
3597
+ throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
3598
+ }
3599
+ const mask = new Uint8Array(4);
3600
+ await fill(this.incoming, mask, signal);
3601
+ const payload = new Uint8Array(length2);
3602
+ await fill(this.incoming, payload, signal);
3603
+ for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
3604
+ const event = this.take(opcode, fin, payload);
3605
+ if (event) return event;
3606
+ }
3607
+ }
3608
+ async length(short, signal) {
3609
+ if (short < 126) return short;
3610
+ const ext = new Uint8Array(short === 126 ? 2 : 8);
3611
+ await fill(this.incoming, ext, signal);
3612
+ const view = new DataView(ext.buffer);
3613
+ if (short === 126) return view.getUint16(0);
3614
+ const long = view.getBigUint64(0);
3615
+ return long > BigInt(this.max) ? this.max + 1 : Number(long);
3616
+ }
3617
+ take(opcode, fin, payload) {
3618
+ switch (opcode) {
3619
+ case OP.ping:
3620
+ return { kind: "ping", payload };
3621
+ case OP.pong:
3622
+ return void 0;
3623
+ case OP.close: {
3624
+ if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
3625
+ const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
3626
+ return { kind: "close", code, reason: decode(payload.subarray(2)) };
3627
+ }
3628
+ case OP.text:
3629
+ case OP.continuation: {
3630
+ if (opcode === OP.continuation !== this.continuing) {
3631
+ throw new WsError(CLOSE.protocol, "unexpected continuation frame");
3632
+ }
3633
+ this.parts.push(payload);
3634
+ this.size += payload.length;
3635
+ this.continuing = !fin;
3636
+ if (!fin) return void 0;
3637
+ const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
3638
+ this.parts = [];
3639
+ this.size = 0;
3640
+ return { kind: "text", text: decode(whole) };
3641
+ }
3642
+ case OP.binary:
3643
+ throw new WsError(CLOSE.unsupported, "CDP messages are text");
3644
+ default:
3645
+ throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
3646
+ }
3647
+ }
3648
+ };
3649
+ function switchingHead(accept2) {
3650
+ return latin1Bytes(
3651
+ `HTTP/1.1 101 Switching Protocols\r
3652
+ Upgrade: websocket\r
3653
+ Connection: Upgrade\r
3654
+ Sec-WebSocket-Accept: ${accept2}\r
3655
+ \r
3656
+ `
3657
+ );
3658
+ }
3659
+
3660
+ // src/cdp/facade.ts
3661
+ var MAX_HEAD = 64 * 1024;
3662
+ var LIST_TIMEOUT = 1e4;
3663
+ function respond(status, body, type = "text/plain; charset=utf-8") {
3664
+ const bytes = new TextEncoder().encode(body);
3665
+ const head = latin1Bytes(
3666
+ `HTTP/1.1 ${status} ${REASON[status]}\r
3667
+ Content-Type: ${type}\r
3668
+ Content-Length: ${bytes.length}\r
3669
+ Connection: close\r
3670
+ \r
3671
+ `
3672
+ );
3673
+ const out = new Uint8Array(head.length + bytes.length);
3674
+ out.set(head);
3675
+ out.set(bytes, head.length);
3676
+ return out;
3677
+ }
3678
+ function json(value) {
3679
+ return respond(200, `${JSON.stringify(value, null, 2)}
3680
+ `, "application/json; charset=UTF-8");
3681
+ }
3682
+ var CdpFacade = class {
3683
+ options;
3684
+ listener;
3685
+ stop = new AbortController();
3686
+ connections = /* @__PURE__ */ new Set();
3687
+ closed;
3688
+ constructor(options) {
3689
+ this.options = options;
3690
+ this.listener = options.net.listen({
3691
+ family: "inet",
3692
+ host: "127.0.0.1",
3693
+ port: options.port ?? CDP_PORT
3694
+ });
3695
+ this.closed = this.acceptLoop();
3696
+ }
3697
+ close() {
3698
+ if (this.stop.signal.aborted) return;
3699
+ this.stop.abort();
3700
+ this.listener.close();
3701
+ for (const conn of this.connections) conn.close();
3702
+ }
3703
+ async acceptLoop() {
3704
+ const served = /* @__PURE__ */ new Set();
3705
+ for (; ; ) {
3706
+ let conn;
3707
+ try {
3708
+ conn = await this.listener.accept(this.stop.signal);
3709
+ } catch {
3710
+ break;
3711
+ }
3712
+ const done4 = this.serve(conn).finally(() => served.delete(done4));
3713
+ served.add(done4);
3714
+ }
3715
+ await Promise.allSettled([...served]);
3716
+ }
3717
+ async serve(conn) {
3718
+ this.connections.add(conn);
3719
+ const incoming = new Incoming(conn);
3720
+ try {
3721
+ const head = await incoming.head(MAX_HEAD, this.stop.signal);
3722
+ if (head) await this.route(conn, incoming, parseRequestHead(head));
3723
+ } catch (e) {
3724
+ if (e instanceof HttpError) await this.write(conn, respond(e.status, `${e.message}
3725
+ `));
3726
+ } finally {
3727
+ this.connections.delete(conn);
3728
+ conn.close();
3729
+ }
3730
+ }
3731
+ async write(conn, bytes) {
3732
+ try {
3733
+ await conn.write(bytes, this.stop.signal);
3734
+ } catch {
3735
+ }
3736
+ }
3737
+ async route(conn, incoming, req) {
3738
+ const url = new URL(req.target, "http://127.0.0.1");
3739
+ const runtime = url.searchParams.get("runtime") ?? void 0;
3740
+ const host = req.headers.find(([n]) => n.toLowerCase() === "host")?.[1] ?? "127.0.0.1";
3741
+ if (req.method !== "GET") throw new HttpError(405, `${req.method} is not supported here`);
3742
+ const { hosts } = this.options;
3743
+ if (url.pathname === hosts.path && isUpgrade(req.headers)) {
3744
+ return this.upgrade(conn, incoming, req, runtime);
3745
+ }
3746
+ const path = url.pathname.replace(/\/+$/, "");
3747
+ if (path !== "/json/version" && path !== "/json/list" && path !== "/json") {
3748
+ throw new HttpError(404, "not found: try /json/version");
3749
+ }
3750
+ if (!this.options.hosts.available(runtime)) {
3751
+ throw new HttpError(503, NO_CDP_HOST);
3752
+ }
3753
+ if (path === "/json/version") {
3754
+ await this.write(
3755
+ conn,
3756
+ json({
3757
+ Browser: "slicc-kernel",
3758
+ "Protocol-Version": "1.3",
3759
+ webSocketDebuggerUrl: `ws://${host}${hosts.path}${url.search}`
3760
+ })
3761
+ );
3762
+ return;
3763
+ }
3764
+ await this.write(conn, json(await this.list(runtime)));
3765
+ }
3766
+ async list(runtime) {
3767
+ const conn = await this.host(runtime);
3768
+ try {
3769
+ const reply = await new Promise(
3770
+ (resolve, reject) => {
3771
+ const timer = setTimeout(
3772
+ () => reject(new HttpError(504, "the CDP host did not answer Target.getTargets")),
3773
+ this.options.listTimeout ?? LIST_TIMEOUT
3774
+ );
3775
+ conn.onclose = (reason) => {
3776
+ clearTimeout(timer);
3777
+ reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
3778
+ };
3779
+ conn.onmessage = (message2) => {
3780
+ const parsed = JSON.parse(message2);
3781
+ if (parsed.id !== 1) return;
3782
+ clearTimeout(timer);
3783
+ resolve(parsed);
3784
+ };
3785
+ conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
3786
+ }
3787
+ );
3788
+ if (!reply.result) {
3789
+ throw new HttpError(502, `Target.getTargets failed: ${JSON.stringify(reply.error)}`);
3790
+ }
3791
+ return (reply.result.targetInfos ?? []).map((t) => ({
3792
+ description: "",
3793
+ id: t.targetId,
3794
+ title: t.title,
3795
+ type: t.type,
3796
+ url: t.url
3797
+ }));
3798
+ } finally {
3799
+ conn.close();
3800
+ }
3801
+ }
3802
+ host(runtime) {
3803
+ return this.options.hosts.connect(runtime ? { runtime } : {});
3804
+ }
3805
+ async upgrade(conn, incoming, req, runtime) {
3806
+ const accept2 = await acceptKey(req.headers);
3807
+ const host = await this.host(runtime);
3808
+ await new Relay(conn, incoming, host, this.options.maxMessage ?? MAX_MESSAGE, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
3809
+ }
3810
+ };
3811
+ var Relay = class {
3812
+ writes = Promise.resolve();
3813
+ ended = false;
3814
+ conn;
3815
+ incoming;
3816
+ host;
3817
+ max;
3818
+ stop;
3819
+ constructor(conn, incoming, host, max, stop) {
3820
+ this.conn = conn;
3821
+ this.incoming = incoming;
3822
+ this.host = host;
3823
+ this.max = max;
3824
+ this.stop = stop;
3825
+ }
3826
+ send(bytes) {
3827
+ this.writes = this.writes.then(() => this.conn.write(bytes, this.stop)).catch(() => void 0);
3828
+ return this.writes;
3829
+ }
3830
+ end(code, reason) {
3831
+ if (this.ended) return this.writes;
3832
+ this.ended = true;
3833
+ return this.send(closeFrame(code, reason));
3834
+ }
3835
+ async start(head) {
3836
+ await this.send(head);
3837
+ const hostGone = new Promise((resolve) => {
3838
+ this.host.onclose = (reason) => {
3839
+ void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
3840
+ };
3841
+ });
3842
+ this.host.onmessage = (message2) => {
3843
+ if (this.ended) return;
3844
+ const bytes = new TextEncoder().encode(message2);
3845
+ if (bytes.length > this.max)
3846
+ void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
3847
+ else void this.send(frame(OP_TEXT, bytes));
3848
+ };
3849
+ await Promise.race([this.pump(), hostGone]);
3850
+ await this.writes;
3851
+ }
3852
+ async pump() {
3853
+ const reader = new WsReader(this.incoming, this.max);
3854
+ try {
3855
+ for (; ; ) {
3856
+ const event = await reader.next(this.stop);
3857
+ if (!event) return;
3858
+ if (event.kind === "text") this.host.send(event.text);
3859
+ else if (event.kind === "ping") void this.send(pongFrame(event.payload));
3860
+ else {
3861
+ await this.end(event.code === 1005 ? CLOSE.normal : event.code, "");
3862
+ return;
3863
+ }
3864
+ }
3865
+ } catch (e) {
3866
+ if (e instanceof WsError) await this.end(e.code, e.message);
3867
+ }
3868
+ }
3869
+ };
3870
+ function enableCdp(net, hosts) {
3871
+ let facade;
3872
+ net.kernelOnly(CDP_PORT, true);
3873
+ net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
3874
+ const started = new CdpFacade({ net, hosts });
3875
+ facade = started;
3876
+ void started.closed.then(() => {
3877
+ if (facade === started) facade = void 0;
3878
+ });
3879
+ });
3880
+ return () => facade;
3881
+ }
3882
+
3883
+ // src/cdp/hosts.ts
3884
+ var CdpHosts = class {
3885
+ embedder;
3886
+ clients = [];
3887
+ closed = false;
3888
+ path = `/devtools/browser/${crypto.randomUUID()}`;
3889
+ url = `ws://127.0.0.1:${CDP_PORT}${this.path}`;
3890
+ constructor(hook) {
3891
+ this.embedder = hook ? { hook, runtime: void 0, open: /* @__PURE__ */ new Set() } : void 0;
3892
+ }
3893
+ register(hook, runtime) {
3894
+ const entry = { hook, runtime, open: /* @__PURE__ */ new Set() };
3895
+ this.clients.push(entry);
3896
+ return () => {
3897
+ const at = this.clients.indexOf(entry);
3898
+ if (at >= 0) this.clients.splice(at, 1);
3899
+ closeAll(entry);
3900
+ };
3901
+ }
3902
+ pick(runtime) {
3903
+ if (this.closed) return void 0;
3904
+ const named = runtime ? this.clients.findLast((c) => c.runtime === runtime) : void 0;
3905
+ return named ?? this.embedder ?? this.clients.at(-1);
3906
+ }
3907
+ available(runtime) {
3908
+ return this.pick(runtime) !== void 0;
3909
+ }
3910
+ async connect(request2) {
3911
+ const entry = this.pick(request2.runtime);
3912
+ if (!entry) throw new HttpError(503, NO_CDP_HOST);
3913
+ let conn;
3914
+ try {
3915
+ conn = await entry.hook(request2.runtime ? { runtime: request2.runtime } : {});
3916
+ } catch (e) {
3917
+ throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
3918
+ }
3919
+ if (this.closed || !(entry === this.embedder || this.clients.includes(entry))) {
3920
+ conn.close();
3921
+ throw new HttpError(503, NO_CDP_HOST);
3922
+ }
3923
+ entry.open.add(conn);
3924
+ const close = conn.close.bind(conn);
3925
+ conn.close = () => {
3926
+ if (entry.open.delete(conn)) close();
3927
+ };
3928
+ return conn;
3929
+ }
3930
+ close() {
3931
+ this.closed = true;
3932
+ for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
3933
+ }
3934
+ };
3935
+ function closeAll(entry) {
3936
+ for (const conn of [...entry.open]) {
3937
+ conn.close();
3938
+ conn.onclose?.("the CDP host is gone");
3939
+ }
3940
+ }
3941
+
3391
3942
  // src/commands.ts
3392
3943
  var NAME = /^[A-Za-z0-9._+-]+$/;
3393
3944
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -4711,7 +5262,7 @@ async function storageUsage() {
4711
5262
  return { quota: estimate.quota, usage: estimate.usage ?? 0 };
4712
5263
  }
4713
5264
  var done = { ok: true, kind: "void" };
4714
- function json(value) {
5265
+ function json2(value) {
4715
5266
  return { ok: true, kind: "json", json: value };
4716
5267
  }
4717
5268
  async function run(entry, path, req) {
@@ -4723,15 +5274,15 @@ async function run(entry, path, req) {
4723
5274
  await fs.writeFile(path, req.body ?? new Uint8Array(0));
4724
5275
  return done;
4725
5276
  case "exists":
4726
- return json(await fs.exists(path));
5277
+ return json2(await fs.exists(path));
4727
5278
  case "stat":
4728
- return json(statJson(await fs.stat(path)));
5279
+ return json2(statJson(await fs.stat(path)));
4729
5280
  case "lstat":
4730
- return json(statJson(await fs.lstat(path)));
5281
+ return json2(statJson(await fs.lstat(path)));
4731
5282
  case "readdir":
4732
- return json(await fs.readdir(path));
5283
+ return json2(await fs.readdir(path));
4733
5284
  case "readdir-stat":
4734
- return json(await readdirStat(fs, path));
5285
+ return json2(await readdirStat(fs, path));
4735
5286
  case "mkdir":
4736
5287
  await fs.mkdir(path, { recursive: true });
4737
5288
  return done;
@@ -4757,7 +5308,7 @@ async function run(entry, path, req) {
4757
5308
  await fs.symlink(req.arg2 ?? "", path);
4758
5309
  return done;
4759
5310
  case "readlink":
4760
- return json(await fs.readlink(path));
5311
+ return json2(await fs.readlink(path));
4761
5312
  case "chmod":
4762
5313
  await fs.chmod(path, req.mode ?? 0);
4763
5314
  return done;
@@ -4788,7 +5339,7 @@ async function dispatchSyncFs(req) {
4788
5339
  const { fs, cwd } = entry;
4789
5340
  try {
4790
5341
  const path = fs.resolvePath(cwd, req.path);
4791
- if (req.op === "statfs") return json(await entry.statfs?.(path) ?? await storageUsage());
5342
+ if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
4792
5343
  if (req.op === "hold") {
4793
5344
  entry.hold?.(path, req.mode === 1);
4794
5345
  return done;
@@ -4961,7 +5512,7 @@ var HOST_OPS = [
4961
5512
  "async-cancel",
4962
5513
  "async-close"
4963
5514
  ];
4964
- var json2 = (value) => ({ ok: true, kind: "json", json: value });
5515
+ var json3 = (value) => ({ ok: true, kind: "json", json: value });
4965
5516
  var done2 = { ok: true, kind: "void" };
4966
5517
  var LockTable = class {
4967
5518
  held = /* @__PURE__ */ new Map();
@@ -5010,7 +5561,7 @@ var AsyncOps = class {
5010
5561
  }
5011
5562
  resolve(value) {
5012
5563
  const id = this.hold();
5013
- this.complete(id, json2(value));
5564
+ this.complete(id, json3(value));
5014
5565
  return id;
5015
5566
  }
5016
5567
  hold() {
@@ -5092,15 +5643,15 @@ async function hostSyscall(req, ctx) {
5092
5643
  return done2;
5093
5644
  case "async-submit":
5094
5645
  if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
5095
- return json2(ctx.ops.submit(req.req));
5646
+ return json3(ctx.ops.submit(req.req));
5096
5647
  case "async-wait":
5097
- return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
5648
+ return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
5098
5649
  case "async-take":
5099
5650
  return ctx.ops.take(req.id);
5100
5651
  case "async-resolve":
5101
- return json2(ctx.ops.resolve(req.value));
5652
+ return json3(ctx.ops.resolve(req.value));
5102
5653
  case "async-hold":
5103
- return json2(ctx.ops.hold());
5654
+ return json3(ctx.ops.hold());
5104
5655
  case "async-cancel":
5105
5656
  ctx.ops.cancel(req.id);
5106
5657
  return done2;
@@ -5627,23 +6178,23 @@ function terminalOf(ctx, fd) {
5627
6178
  if (!tty) throw new KernelError("ENOTTY");
5628
6179
  return tty;
5629
6180
  }
5630
- var json3 = (value) => ({ ok: true, kind: "json", json: value });
6181
+ var json4 = (value) => ({ ok: true, kind: "json", json: value });
5631
6182
  var done3 = { ok: true, kind: "void" };
5632
6183
  function ptySyscall(req, ctx) {
5633
6184
  switch (req.op) {
5634
6185
  case "pty-open": {
5635
6186
  if (!ctx.ptys) throw new KernelError("ENOENT");
5636
- return json3(ctx.fds.install(ctx.ptys.open().master, 3));
6187
+ return json4(ctx.fds.install(ctx.ptys.open().master, 3));
5637
6188
  }
5638
6189
  case "pty-slave-open": {
5639
6190
  if (!ctx.ptys) throw new KernelError("ENOENT");
5640
6191
  const pair = ctx.ptys.get(req.n);
5641
6192
  const fd = ctx.fds.install(pair.openSlave(), 3);
5642
6193
  if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
5643
- return json3(fd);
6194
+ return json4(fd);
5644
6195
  }
5645
6196
  case "pty-number":
5646
- return json3(masterOf(ctx, req.fd).index);
6197
+ return json4(masterOf(ctx, req.fd).index);
5647
6198
  case "pty-lock":
5648
6199
  masterOf(ctx, req.fd).locked = req.lock;
5649
6200
  return done3;
@@ -5655,7 +6206,7 @@ function ptySyscall(req, ctx) {
5655
6206
  return done3;
5656
6207
  }
5657
6208
  case "pty-list":
5658
- return json3(ctx.ptys?.numbers() ?? []);
6209
+ return json4(ctx.ptys?.numbers() ?? []);
5659
6210
  case "pty-packet":
5660
6211
  masterOf(ctx, req.fd).packet = req.on;
5661
6212
  return done3;
@@ -5733,7 +6284,7 @@ function socketAt(fds, fd) {
5733
6284
  if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
5734
6285
  return file;
5735
6286
  }
5736
- var ok = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
6287
+ var ok = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
5737
6288
  async function socketSyscall(req, proc) {
5738
6289
  const { fds, net } = proc;
5739
6290
  switch (req.op) {
@@ -7697,8 +8248,8 @@ function umountCall(target, flags) {
7697
8248
  var FSTAB_PATH = "/etc/fstab";
7698
8249
  var FSTAB_RETRIES = [1e3, 4e3, 16e3];
7699
8250
  var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
7700
- function unescape(field) {
7701
- return field.replace(
8251
+ function unescape(field2) {
8252
+ return field2.replace(
7702
8253
  /\\([0-7]{3})/g,
7703
8254
  (_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
7704
8255
  );
@@ -8346,7 +8897,7 @@ function importedMemory(bytes) {
8346
8897
  const count = u32();
8347
8898
  for (let i = 0; i < count; i++) {
8348
8899
  const module = name2();
8349
- const field = name2();
8900
+ const field2 = name2();
8350
8901
  const kind = bytes[at++];
8351
8902
  if (kind === 0) u32();
8352
8903
  else if (kind === 1) {
@@ -8354,7 +8905,7 @@ function importedMemory(bytes) {
8354
8905
  limits();
8355
8906
  } else if (kind === 2) {
8356
8907
  const l = limits();
8357
- return { module, name: field, ...l };
8908
+ return { module, name: field2, ...l };
8358
8909
  } else if (kind === 3) at += 2;
8359
8910
  else if (kind === 4) {
8360
8911
  at++;
@@ -8445,12 +8996,12 @@ function readImports(r, types) {
8445
8996
  const foreign = {};
8446
8997
  for (let i = r.u32(); i > 0; i--) {
8447
8998
  const module = r.name();
8448
- const field = r.name();
8999
+ const field2 = r.name();
8449
9000
  const kind = r.byte();
8450
9001
  if (kind !== 0) skipImport(r, kind);
8451
9002
  else {
8452
9003
  const type = types[r.u32()] ?? "other";
8453
- if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field] = type;
9004
+ if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
8454
9005
  }
8455
9006
  }
8456
9007
  return foreign;
@@ -8557,6 +9108,7 @@ var Launcher = class {
8557
9108
  locks = new LockTable();
8558
9109
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
8559
9110
  net = new LoopbackNet();
9111
+ cdp;
8560
9112
  ca;
8561
9113
  transport;
8562
9114
  watchers = new FsWatchers();
@@ -8593,7 +9145,8 @@ var Launcher = class {
8593
9145
  this.processMounts = options.processMounts ?? true;
8594
9146
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
8595
9147
  this.modulesDir = options.modules ?? "/node_modules";
8596
- this.env = { ...networkEnv(), ...options.env };
9148
+ this.cdp = new CdpHosts(options.cdp);
9149
+ this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
8597
9150
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
8598
9151
  this.transport = options.transport ?? missingTransport();
8599
9152
  enableNetwork(this.net, {
@@ -8601,6 +9154,7 @@ var Launcher = class {
8601
9154
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
8602
9155
  ca: this.ca
8603
9156
  });
9157
+ enableCdp(this.net, this.cdp);
8604
9158
  this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
8605
9159
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
8606
9160
  this.catalog = void 0;
@@ -9159,6 +9713,14 @@ async function stores(deps, name2, media) {
9159
9713
  ...handles === false ? {} : { media: await deps.media?.(handles) }
9160
9714
  };
9161
9715
  }
9716
+ function cdpOption(req, port) {
9717
+ if (!req.cdp) return {};
9718
+ return {
9719
+ cdp: portCdpHook(
9720
+ (request2, cdp) => port.postMessage({ cdpOpen: request2, port: cdp }, [cdp])
9721
+ )
9722
+ };
9723
+ }
9162
9724
  function serveKernel(port, deps) {
9163
9725
  let launcher;
9164
9726
  let remote;
@@ -9230,6 +9792,7 @@ function serveKernel(port, deps) {
9230
9792
  ...media ? { media } : {},
9231
9793
  onMountPending: (medium) => port.postMessage({ medium }),
9232
9794
  ...req.hostfs ? { hostfs: askGrant } : {},
9795
+ ...cdpOption(req, port),
9233
9796
  ...mountPolicy(req.processMounts),
9234
9797
  caStore: caStore(deps.metadata ? caDb(req) : false)
9235
9798
  });