@ai-ecoverse/slicc-kernel 1.37.0 → 1.39.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
@@ -1190,7 +1190,153 @@ async function readWithin(reader, ms) {
1190
1190
  }
1191
1191
  }
1192
1192
 
1193
+ // src/kernel/net/ws-queue.ts
1194
+ var INBOUND_LIMIT = 16 * 1024 * 1024;
1195
+ var MESSAGE_LIMIT = 64 * 1024 * 1024;
1196
+ var TOO_BIG = 1009;
1197
+ function payloadSize(message2) {
1198
+ return typeof message2 === "string" ? new TextEncoder().encode(message2).length : message2.length;
1199
+ }
1200
+ var MessageQueue = class {
1201
+ items = [];
1202
+ limit;
1203
+ waiter;
1204
+ size = 0;
1205
+ ended = false;
1206
+ overflow;
1207
+ taken;
1208
+ closed;
1209
+ settle;
1210
+ constructor(overflow, limit = INBOUND_LIMIT, taken) {
1211
+ this.overflow = overflow;
1212
+ this.limit = limit;
1213
+ this.taken = taken;
1214
+ this.closed = new Promise((resolve) => {
1215
+ this.settle = resolve;
1216
+ });
1217
+ }
1218
+ push(message2) {
1219
+ if (this.ended) return;
1220
+ const size = payloadSize(message2);
1221
+ const huge = size > MESSAGE_LIMIT;
1222
+ if (huge || this.items.length > 0 && this.size + size > this.limit) {
1223
+ const reason = huge ? `a message is over ${MESSAGE_LIMIT} bytes` : `over ${this.limit} bytes of messages are waiting`;
1224
+ this.end({ code: TOO_BIG, reason });
1225
+ this.overflow();
1226
+ return;
1227
+ }
1228
+ this.size += size;
1229
+ this.items.push(message2);
1230
+ this.wake();
1231
+ }
1232
+ end(close) {
1233
+ if (this.ended) return;
1234
+ this.ended = true;
1235
+ this.settle(close);
1236
+ this.wake();
1237
+ }
1238
+ wake() {
1239
+ const waiter = this.waiter;
1240
+ this.waiter = void 0;
1241
+ waiter?.();
1242
+ }
1243
+ async *[Symbol.asyncIterator]() {
1244
+ for (; ; ) {
1245
+ const next = this.items.shift();
1246
+ if (next !== void 0) {
1247
+ const n = payloadSize(next);
1248
+ this.size -= n;
1249
+ this.taken?.(n);
1250
+ yield next;
1251
+ continue;
1252
+ }
1253
+ if (this.ended) return;
1254
+ await new Promise((resolve) => {
1255
+ this.waiter = resolve;
1256
+ });
1257
+ }
1258
+ }
1259
+ };
1260
+
1261
+ // src/websocket-transport.ts
1262
+ var SENT_HEADERS_DROP = /* @__PURE__ */ new Set([
1263
+ "host",
1264
+ "connection",
1265
+ "upgrade",
1266
+ "sec-websocket-key",
1267
+ "sec-websocket-version",
1268
+ "sec-websocket-extensions",
1269
+ "sec-websocket-protocol"
1270
+ ]);
1271
+ function headerRecord(headers) {
1272
+ const out = {};
1273
+ for (const [name2, value] of headers) {
1274
+ if (!SENT_HEADERS_DROP.has(name2.toLowerCase())) out[name2] = value;
1275
+ }
1276
+ return out;
1277
+ }
1278
+ function sendableClose(code) {
1279
+ return code === 1e3 || code !== void 0 && code >= 3e3 && code <= 4999 ? code : 1e3;
1280
+ }
1281
+ function blockedReason(url, e) {
1282
+ const name2 = e?.name;
1283
+ if (name2 === "SecurityError" && url.startsWith("ws:")) {
1284
+ return `${url}: ws:// is blocked from an https page; use wss:// or slicc-node`;
1285
+ }
1286
+ return `${url}: ${e instanceof Error ? e.message : String(e)}`;
1287
+ }
1288
+ function openWebSocket(Socket, request, withHeaders) {
1289
+ const { url, protocols, signal } = request;
1290
+ signal.throwIfAborted();
1291
+ let socket;
1292
+ try {
1293
+ socket = withHeaders ? new Socket(url, { protocols, headers: headerRecord(request.headers) }) : new Socket(url, protocols);
1294
+ } catch (e) {
1295
+ return Promise.reject(Object.assign(new Error(blockedReason(url, e)), { status: 502 }));
1296
+ }
1297
+ socket.binaryType = "arraybuffer";
1298
+ const queue = new MessageQueue(() => socket.close(1e3, "too many waiting messages"));
1299
+ return new Promise((resolve, reject) => {
1300
+ let open = false;
1301
+ const abort = () => {
1302
+ socket.close(1e3, "aborted");
1303
+ reject(signal.reason);
1304
+ };
1305
+ signal.addEventListener("abort", abort, { once: true });
1306
+ socket.addEventListener("open", () => {
1307
+ open = true;
1308
+ signal.removeEventListener("abort", abort);
1309
+ resolve({
1310
+ protocol: socket.protocol,
1311
+ get buffered() {
1312
+ return socket.bufferedAmount;
1313
+ },
1314
+ send: (data) => socket.send(data),
1315
+ messages: queue,
1316
+ close: (code, reason) => socket.close(sendableClose(code), reason),
1317
+ closed: queue.closed
1318
+ });
1319
+ });
1320
+ socket.addEventListener("message", (event) => {
1321
+ queue.push(typeof event.data === "string" ? event.data : new Uint8Array(event.data));
1322
+ });
1323
+ socket.addEventListener("close", (event) => {
1324
+ queue.end({ code: event.code, reason: event.reason });
1325
+ if (open) return;
1326
+ signal.removeEventListener("abort", abort);
1327
+ reject(
1328
+ Object.assign(new Error(`${url}: the WebSocket closed (${event.code}) before it opened`), {
1329
+ status: 502
1330
+ })
1331
+ );
1332
+ });
1333
+ });
1334
+ }
1335
+
1193
1336
  // src/transport.ts
1337
+ var SEND_BUFFER = 1024 * 1024;
1338
+ var CLOSE_WAIT_MS = 5e3;
1339
+ var NO_WEBSOCKET = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
1194
1340
  var MAX_REQUEST_BODY = 64 * 1024 * 1024;
1195
1341
  function joinHeaders(headers) {
1196
1342
  const out = new Headers();
@@ -1215,13 +1361,17 @@ async function* chunks(body, idleMs) {
1215
1361
  }
1216
1362
  function fetchTransport(options = {}) {
1217
1363
  const send = options.fetch ?? globalThis.fetch.bind(globalThis);
1364
+ const Socket = options.webSocket === true ? globalThis.WebSocket : options.webSocket || void 0;
1365
+ const headers = options.webSocketHeaders === true;
1218
1366
  return {
1219
1367
  traits: {
1220
1368
  manualRedirects: false,
1221
1369
  encodedBodies: false,
1222
1370
  maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
1223
- crossOrigin: "cors"
1371
+ crossOrigin: "cors",
1372
+ ...Socket ? { websocket: true } : {}
1224
1373
  },
1374
+ ...Socket ? { websocket: (request) => openWebSocket(Socket, request, headers) } : {},
1225
1375
  async fetch(request) {
1226
1376
  let response;
1227
1377
  try {
@@ -1243,15 +1393,15 @@ function fetchTransport(options = {}) {
1243
1393
  { status: 502 }
1244
1394
  );
1245
1395
  }
1246
- const headers = [];
1396
+ const headers2 = [];
1247
1397
  response.headers.forEach((value, name2) => {
1248
- headers.push([name2, value]);
1398
+ headers2.push([name2, value]);
1249
1399
  });
1250
1400
  const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
1251
1401
  return {
1252
1402
  status: response.status,
1253
1403
  statusText: response.statusText,
1254
- headers,
1404
+ headers: headers2,
1255
1405
  body,
1256
1406
  cancel: async () => {
1257
1407
  await body.return(void 0);
@@ -1261,7 +1411,7 @@ function fetchTransport(options = {}) {
1261
1411
  }
1262
1412
  };
1263
1413
  }
1264
- function serveTransport(peer, transport) {
1414
+ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
1265
1415
  const open = /* @__PURE__ */ new Map();
1266
1416
  const fail2 = (nid, e) => {
1267
1417
  open.delete(nid);
@@ -1302,6 +1452,96 @@ function serveTransport(peer, transport) {
1302
1452
  fail2(call.nid, e);
1303
1453
  }
1304
1454
  };
1455
+ const sockets = /* @__PURE__ */ new Map();
1456
+ const credit = /* @__PURE__ */ new Map();
1457
+ const refill = (nid, n, closing = false) => {
1458
+ const flow = credit.get(nid);
1459
+ if (!flow) return;
1460
+ flow.inflight -= n;
1461
+ if (closing && !flow.closing) {
1462
+ flow.closing = true;
1463
+ flow.deadline();
1464
+ }
1465
+ flow.wake?.();
1466
+ };
1467
+ const openSocket = async (call) => {
1468
+ const abort = new AbortController();
1469
+ open.set(call.nid, { abort });
1470
+ try {
1471
+ if (!transport.websocket) throw new Error(NO_WEBSOCKET);
1472
+ const socket = await transport.websocket({
1473
+ url: call.url,
1474
+ protocols: call.protocols,
1475
+ headers: call.headers,
1476
+ signal: abort.signal
1477
+ });
1478
+ if (!open.delete(call.nid)) {
1479
+ socket.close(1e3, "cancelled");
1480
+ return;
1481
+ }
1482
+ sockets.set(call.nid, socket);
1483
+ peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
1484
+ void relay(call.nid, socket);
1485
+ } catch (e) {
1486
+ fail2(call.nid, e);
1487
+ }
1488
+ };
1489
+ const relay = async (nid, socket) => {
1490
+ let ending;
1491
+ let late;
1492
+ const unanswered = new Promise((resolve) => {
1493
+ late = () => resolve("late");
1494
+ });
1495
+ const flow = {
1496
+ inflight: 0,
1497
+ closing: false,
1498
+ deadline: () => AbortSignal.timeout(closeWaitMs).addEventListener("abort", late, { once: true })
1499
+ };
1500
+ credit.set(nid, flow);
1501
+ const timedOut = { code: 1006, reason: "the far end did not answer the close" };
1502
+ let oversize = false;
1503
+ try {
1504
+ const messages = socket.messages[Symbol.asyncIterator]();
1505
+ for (; ; ) {
1506
+ const next = await Promise.race([messages.next(), unanswered]);
1507
+ if (next === "late" || next.done) break;
1508
+ const data = next.value;
1509
+ if (flow.closing) continue;
1510
+ if (payloadSize(data) > MESSAGE_LIMIT) {
1511
+ oversize = true;
1512
+ break;
1513
+ }
1514
+ if (typeof data === "string") peer.postMessage({ net: "ws-message", nid, data });
1515
+ else {
1516
+ const bytes = data.slice();
1517
+ peer.postMessage({ net: "ws-message", nid, data: bytes }, [bytes.buffer]);
1518
+ }
1519
+ flow.inflight += payloadSize(data);
1520
+ while (flow.inflight > INBOUND_LIMIT && !flow.closing) {
1521
+ await new Promise((resolve) => {
1522
+ flow.wake = resolve;
1523
+ });
1524
+ }
1525
+ }
1526
+ if (oversize) socket.close(1e3, "a message is too big");
1527
+ ending = oversize ? { code: 1009, reason: `a message is over ${MESSAGE_LIMIT} bytes` } : await Promise.race([socket.closed, unanswered.then(() => timedOut)]);
1528
+ } catch (e) {
1529
+ socket.close(1e3, "the relay failed");
1530
+ ending = { code: 1011, reason: e instanceof Error ? e.message : String(e) };
1531
+ }
1532
+ sockets.delete(nid);
1533
+ credit.delete(nid);
1534
+ peer.postMessage({ net: "ws-closed", nid, ...ending });
1535
+ };
1536
+ const sendOn = async (call) => {
1537
+ const socket = sockets.get(call.nid);
1538
+ if (!socket) return;
1539
+ socket.send(call.data);
1540
+ while (socket.buffered > SEND_BUFFER && sockets.has(call.nid)) {
1541
+ await new Promise((resolve) => setTimeout(resolve, 10));
1542
+ }
1543
+ peer.postMessage({ net: "ws-acked", nid: call.nid, n: payloadSize(call.data) });
1544
+ };
1305
1545
  const read = async (nid) => {
1306
1546
  const body = open.get(nid)?.body;
1307
1547
  if (!body) return;
@@ -1319,6 +1559,8 @@ function serveTransport(peer, transport) {
1319
1559
  }
1320
1560
  };
1321
1561
  const drop = (nid) => {
1562
+ sockets.get(nid)?.close(1e3, "cancelled");
1563
+ refill(nid, 0, true);
1322
1564
  const entry = open.get(nid);
1323
1565
  open.delete(nid);
1324
1566
  entry?.abort.abort();
@@ -1328,10 +1570,16 @@ function serveTransport(peer, transport) {
1328
1570
  answer(call) {
1329
1571
  if (call.net === "fetch") void start(call);
1330
1572
  else if (call.net === "read") void read(call.nid);
1573
+ else if (call.net === "ws-open") void openSocket(call);
1574
+ else if (call.net === "ws-send") void sendOn(call);
1575
+ else if (call.net === "ws-close") {
1576
+ sockets.get(call.nid)?.close(call.code, call.reason);
1577
+ refill(call.nid, 0, true);
1578
+ } else if (call.net === "ws-consumed") refill(call.nid, call.n);
1331
1579
  else drop(call.nid);
1332
1580
  },
1333
1581
  close() {
1334
- for (const nid of [...open.keys()]) drop(nid);
1582
+ for (const nid of [...open.keys(), ...sockets.keys()]) drop(nid);
1335
1583
  }
1336
1584
  };
1337
1585
  }
@@ -2417,6 +2665,10 @@ var Incoming = class {
2417
2665
  this.buf = this.buf.subarray(n);
2418
2666
  return out;
2419
2667
  }
2668
+ async peek(signal) {
2669
+ if (this.buf.length === 0) await this.fill(signal);
2670
+ return this.buf[0];
2671
+ }
2420
2672
  async some(max, signal) {
2421
2673
  if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
2422
2674
  if (this.eof) return new Uint8Array(0);
@@ -2614,7 +2866,15 @@ var CLOSE = {
2614
2866
  error: 1011
2615
2867
  };
2616
2868
  var OP_TEXT = 1;
2617
- var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
2869
+ var OP_BINARY = 2;
2870
+ var OP = {
2871
+ continuation: 0,
2872
+ text: OP_TEXT,
2873
+ binary: OP_BINARY,
2874
+ close: 8,
2875
+ ping: 9,
2876
+ pong: 10
2877
+ };
2618
2878
  var WsError = class extends Error {
2619
2879
  code;
2620
2880
  constructor(code, message2) {
@@ -2622,6 +2882,10 @@ var WsError = class extends Error {
2622
2882
  this.code = code;
2623
2883
  }
2624
2884
  };
2885
+ function sendableCode(code) {
2886
+ if (code >= 3e3 && code <= 4999) return true;
2887
+ return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006;
2888
+ }
2625
2889
  function field(headers, name2) {
2626
2890
  return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
2627
2891
  }
@@ -2660,7 +2924,11 @@ function frame(opcode, payload) {
2660
2924
  }
2661
2925
  function closeFrame(code, reason = "") {
2662
2926
  let bytes = new TextEncoder().encode(reason);
2663
- if (bytes.length > 123) bytes = bytes.subarray(0, 123);
2927
+ if (bytes.length > 123) {
2928
+ let end = 123;
2929
+ while ((bytes[end] & 192) === 128) end--;
2930
+ bytes = bytes.subarray(0, end);
2931
+ }
2664
2932
  const payload = new Uint8Array(2 + bytes.length);
2665
2933
  new DataView(payload.buffer).setUint16(0, code);
2666
2934
  payload.set(bytes, 2);
@@ -2691,9 +2959,12 @@ var WsReader = class {
2691
2959
  parts = [];
2692
2960
  size = 0;
2693
2961
  continuing = false;
2694
- constructor(incoming, max = MAX_MESSAGE) {
2962
+ binaryMessage = false;
2963
+ binary;
2964
+ constructor(incoming, max = MAX_MESSAGE, binary) {
2695
2965
  this.incoming = incoming;
2696
2966
  this.max = max;
2967
+ this.binary = binary;
2697
2968
  }
2698
2969
  async next(signal) {
2699
2970
  for (; ; ) {
@@ -2723,6 +2994,20 @@ var WsReader = class {
2723
2994
  if (event) return event;
2724
2995
  }
2725
2996
  }
2997
+ data(opcode, fin, payload) {
2998
+ if (opcode === OP.continuation !== this.continuing) {
2999
+ throw new WsError(CLOSE.protocol, "unexpected continuation frame");
3000
+ }
3001
+ if (opcode !== OP.continuation) this.binaryMessage = opcode === OP.binary;
3002
+ this.parts.push(payload);
3003
+ this.size += payload.length;
3004
+ this.continuing = !fin;
3005
+ if (!fin) return void 0;
3006
+ const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
3007
+ this.parts = [];
3008
+ this.size = 0;
3009
+ return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
3010
+ }
2726
3011
  async length(short, signal) {
2727
3012
  if (short < 126) return short;
2728
3013
  const ext = new Uint8Array(short === 126 ? 2 : 8);
@@ -2741,36 +3026,31 @@ var WsReader = class {
2741
3026
  case OP.close: {
2742
3027
  if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
2743
3028
  const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
2744
- return { kind: "close", code, reason: decode(payload.subarray(2)) };
2745
- }
2746
- case OP.text:
2747
- case OP.continuation: {
2748
- if (opcode === OP.continuation !== this.continuing) {
2749
- throw new WsError(CLOSE.protocol, "unexpected continuation frame");
3029
+ if (payload.length >= 2 && !sendableCode(code)) {
3030
+ throw new WsError(CLOSE.protocol, `close code ${code} may not be sent`);
2750
3031
  }
2751
- this.parts.push(payload);
2752
- this.size += payload.length;
2753
- this.continuing = !fin;
2754
- if (!fin) return void 0;
2755
- const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
2756
- this.parts = [];
2757
- this.size = 0;
2758
- return { kind: "text", text: decode(whole) };
3032
+ return { kind: "close", code, reason: decode(payload.subarray(2)) };
2759
3033
  }
2760
3034
  case OP.binary:
2761
- throw new WsError(CLOSE.unsupported, "CDP messages are text");
3035
+ if (!this.binary) throw new WsError(CLOSE.unsupported, "CDP messages are text");
3036
+ return this.data(opcode, fin, payload);
3037
+ case OP.text:
3038
+ case OP.continuation:
3039
+ return this.data(opcode, fin, payload);
2762
3040
  default:
2763
3041
  throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
2764
3042
  }
2765
3043
  }
2766
3044
  };
2767
- function switchingHead(accept2) {
3045
+ function switchingHead(accept2, protocol = "") {
3046
+ const chosen = protocol ? `Sec-WebSocket-Protocol: ${protocol}\r
3047
+ ` : "";
2768
3048
  return latin1Bytes(
2769
3049
  `HTTP/1.1 101 Switching Protocols\r
2770
3050
  Upgrade: websocket\r
2771
3051
  Connection: Upgrade\r
2772
3052
  Sec-WebSocket-Accept: ${accept2}\r
2773
- \r
3053
+ ${chosen}\r
2774
3054
  `
2775
3055
  );
2776
3056
  }
@@ -2778,7 +3058,7 @@ Sec-WebSocket-Accept: ${accept2}\r
2778
3058
  // src/cdp/facade.ts
2779
3059
  var MAX_HEAD = 64 * 1024;
2780
3060
  var LIST_TIMEOUT = 1e4;
2781
- var CLOSE_WAIT_MS = 1e3;
3061
+ var CLOSE_WAIT_MS2 = 1e3;
2782
3062
  function respond(status, body, type = "text/plain; charset=utf-8") {
2783
3063
  const bytes = new TextEncoder().encode(body);
2784
3064
  const head = latin1Bytes(
@@ -2926,7 +3206,7 @@ var CdpFacade = class {
2926
3206
  const host = await this.host(runtime);
2927
3207
  const limits = {
2928
3208
  max: this.options.maxMessage ?? MAX_MESSAGE,
2929
- closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS
3209
+ closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS2
2930
3210
  };
2931
3211
  await new Relay(conn, incoming, host, limits, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
2932
3212
  }
@@ -3394,13 +3674,193 @@ function withCommandDirs(fs, names) {
3394
3674
  };
3395
3675
  }
3396
3676
 
3677
+ // src/fs/unlinked.ts
3678
+ var UNLINKED_DIR = ".slicc-unlinked";
3679
+ function webLocks() {
3680
+ return globalThis.navigator?.locks;
3681
+ }
3682
+ var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3683
+ var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3684
+ var lockName = (instance) => `slicc-unlinked:${instance}`;
3685
+ var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
3686
+ var PathGate = class {
3687
+ tails = /* @__PURE__ */ new Map();
3688
+ async acquire(path) {
3689
+ const prev = this.tails.get(path) ?? Promise.resolve();
3690
+ const { promise, resolve } = Promise.withResolvers();
3691
+ const tail = prev.then(() => promise);
3692
+ this.tails.set(path, tail);
3693
+ await prev;
3694
+ return () => {
3695
+ resolve();
3696
+ if (this.tails.get(path) === tail) this.tails.delete(path);
3697
+ };
3698
+ }
3699
+ async run(path, op) {
3700
+ const release = await this.acquire(path);
3701
+ try {
3702
+ return await op();
3703
+ } finally {
3704
+ release();
3705
+ }
3706
+ }
3707
+ };
3708
+ var UnlinkedKeeper = class {
3709
+ fs;
3710
+ holders;
3711
+ rootOf;
3712
+ locks;
3713
+ busy;
3714
+ waiting = /* @__PURE__ */ new Set();
3715
+ instance = crypto.randomUUID();
3716
+ gate = new PathGate();
3717
+ queue = Promise.resolve();
3718
+ count = 0;
3719
+ constructor(fs, holders, rootOf, locks, busy = () => false) {
3720
+ this.fs = fs;
3721
+ this.holders = holders;
3722
+ this.rootOf = rootOf;
3723
+ this.locks = locks;
3724
+ this.busy = busy;
3725
+ void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
3726
+ }
3727
+ serial(op) {
3728
+ const next = this.queue.then(op);
3729
+ this.queue = next.catch(() => void 0);
3730
+ return next;
3731
+ }
3732
+ wrap() {
3733
+ const fs = this.fs;
3734
+ const keeper = this;
3735
+ const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
3736
+ return {
3737
+ ...fs,
3738
+ async rm(path, options) {
3739
+ const target = fs.resolvePath("/", path);
3740
+ const pinned = await keeper.pin(target);
3741
+ if (pinned.moved.has(target)) pinned.commit();
3742
+ else await keeper.settle(pinned, () => fs.rm(path, options));
3743
+ },
3744
+ async rename(from, to) {
3745
+ const pinned = await keeper.pin(fs.resolvePath("/", to));
3746
+ await keeper.settle(pinned, () => fs.rename(from, to));
3747
+ },
3748
+ readdir: async (path) => visible(await fs.readdir(path)),
3749
+ readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
3750
+ };
3751
+ }
3752
+ async settle(pinned, op) {
3753
+ try {
3754
+ await op();
3755
+ } catch (err) {
3756
+ await pinned.rollback();
3757
+ throw err;
3758
+ }
3759
+ pinned.commit();
3760
+ }
3761
+ async pin(target) {
3762
+ const held = /* @__PURE__ */ new Map();
3763
+ for (const holder of this.holders) {
3764
+ if (holder.owns(target)) continue;
3765
+ for (const path of holder.opens.keys()) {
3766
+ if (!within2(path, target) || holder.unlinked.has(path)) continue;
3767
+ held.set(path, [...held.get(path) ?? [], holder]);
3768
+ }
3769
+ }
3770
+ const paths = [...held.keys()].sort();
3771
+ const releases = [];
3772
+ for (const path of paths) releases.push(await this.gate.acquire(path));
3773
+ const hidden = /* @__PURE__ */ new Map();
3774
+ for (const path of paths) {
3775
+ const at = await this.hide(path);
3776
+ if (at !== void 0) hidden.set(path, at);
3777
+ }
3778
+ const done4 = () => {
3779
+ for (const release of releases) release();
3780
+ };
3781
+ return {
3782
+ moved: new Set(hidden.keys()),
3783
+ commit: () => {
3784
+ for (const [path, at] of hidden) {
3785
+ const holders = held.get(path);
3786
+ const file = this.keptFile(at, { refs: holders.length });
3787
+ for (const holder of holders) holder.unlinked.set(path, file);
3788
+ }
3789
+ done4();
3790
+ },
3791
+ rollback: async () => {
3792
+ for (const [path, at] of hidden) {
3793
+ await this.serial(async () => {
3794
+ await Promise.allSettled([this.fs.rename(at, path)]);
3795
+ await this.tidy(parentOf3(at));
3796
+ });
3797
+ }
3798
+ done4();
3799
+ }
3800
+ };
3801
+ }
3802
+ keptFile(hidden, entry) {
3803
+ return {
3804
+ hidden,
3805
+ release: () => {
3806
+ if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
3807
+ }
3808
+ };
3809
+ }
3810
+ hide(path) {
3811
+ return this.serial(async () => {
3812
+ const dir = dirOf(this.rootOf(path));
3813
+ const hidden = `${dir}/${this.instance}.${++this.count}`;
3814
+ try {
3815
+ await this.fs.mkdir(dir, { recursive: true });
3816
+ await this.fs.rename(path, hidden);
3817
+ return hidden;
3818
+ } catch {
3819
+ await this.tidy(dir);
3820
+ return void 0;
3821
+ }
3822
+ });
3823
+ }
3824
+ closed(path) {
3825
+ if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
3826
+ }
3827
+ async drop(hidden) {
3828
+ if (this.busy(hidden)) {
3829
+ this.waiting.add(hidden);
3830
+ return;
3831
+ }
3832
+ await this.fs.rm(hidden).catch(() => void 0);
3833
+ await this.tidy(parentOf3(hidden));
3834
+ }
3835
+ async tidy(dir) {
3836
+ const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
3837
+ if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
3838
+ }
3839
+ sweep(root) {
3840
+ const query = this.locks?.query?.bind(this.locks);
3841
+ if (!query) return Promise.resolve();
3842
+ return this.serial(async () => {
3843
+ const dir = dirOf(root);
3844
+ const names = await this.fs.readdir(dir).catch(() => void 0);
3845
+ if (!names) return;
3846
+ const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
3847
+ for (const name2 of names) {
3848
+ const owner = name2.slice(0, name2.lastIndexOf("."));
3849
+ if (owner === this.instance || held.has(lockName(owner))) continue;
3850
+ await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
3851
+ }
3852
+ await this.tidy(dir);
3853
+ });
3854
+ }
3855
+ };
3856
+
3397
3857
  // src/fs/watch.ts
3398
3858
  var WATCH_BATCH_LIMIT = 1e3;
3399
3859
  var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
3400
- var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3860
+ var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3401
3861
  function touches(watcher, changed) {
3402
3862
  return watcher.paths.some(
3403
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf3(changed) === watched)
3863
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
3404
3864
  );
3405
3865
  }
3406
3866
  var FsWatchers = class {
@@ -3782,7 +4242,7 @@ function parseSize(text) {
3782
4242
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
3783
4243
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
3784
4244
  }
3785
- var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
4245
+ var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3786
4246
  var HeldSet = class extends Set {
3787
4247
  idle;
3788
4248
  constructor(idle) {
@@ -3795,18 +4255,47 @@ var HeldSet = class extends Set {
3795
4255
  }
3796
4256
  delete(holds) {
3797
4257
  const removed = super.delete(holds);
3798
- for (const path of holds.keys()) this.idle(path);
4258
+ for (const path of /* @__PURE__ */ new Set([...holds.keys(), ...holds.opens.keys()])) this.idle(path);
4259
+ for (const kept of holds.unlinked.values()) kept.release();
4260
+ holds.unlinked.clear();
3799
4261
  return removed;
3800
4262
  }
3801
4263
  };
3802
4264
  function heldUnder(held, target) {
3803
- for (const paths of held) for (const path of paths.keys()) if (within2(path, target)) return true;
4265
+ for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
3804
4266
  return false;
3805
4267
  }
3806
4268
  var HeldPaths = class extends Map {
3807
4269
  revoked = /* @__PURE__ */ new Set();
4270
+ opens = /* @__PURE__ */ new Map();
4271
+ unlinked = /* @__PURE__ */ new Map();
4272
+ aliases = /* @__PURE__ */ new Map();
4273
+ owning;
3808
4274
  onIdle;
3809
- hold(path, on) {
4275
+ keyOf(path) {
4276
+ return this.aliases.get(path) ?? path;
4277
+ }
4278
+ keptAt(path) {
4279
+ return this.unlinked.get(this.keyOf(path));
4280
+ }
4281
+ holding(path) {
4282
+ return this.opens.has(this.keyOf(path));
4283
+ }
4284
+ hold(path, on, open = false, real) {
4285
+ if (open) {
4286
+ const key = real ?? this.keyOf(path);
4287
+ if (key !== path) this.aliases.set(path, key);
4288
+ const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
4289
+ if (count2 > 0) this.opens.set(key, count2);
4290
+ else {
4291
+ this.opens.delete(key);
4292
+ this.unlinked.get(key)?.release();
4293
+ this.unlinked.delete(key);
4294
+ for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
4295
+ this.onIdle?.(key);
4296
+ }
4297
+ return;
4298
+ }
3810
4299
  const count = (this.get(path) ?? 0) + (on ? 1 : -1);
3811
4300
  if (count > 0) this.set(path, count);
3812
4301
  else {
@@ -3815,23 +4304,40 @@ var HeldPaths = class extends Map {
3815
4304
  this.onIdle?.(path);
3816
4305
  }
3817
4306
  }
4307
+ async own(path, op) {
4308
+ this.owning = path;
4309
+ try {
4310
+ return await op();
4311
+ } finally {
4312
+ this.owning = void 0;
4313
+ }
4314
+ }
4315
+ owns(path) {
4316
+ return this.owning === path;
4317
+ }
3818
4318
  revoke(prefix) {
3819
- for (const path of this.keys()) if (within2(path, prefix)) this.revoked.add(path);
4319
+ for (const path of this.keys()) if (within3(path, prefix)) this.revoked.add(path);
3820
4320
  }
3821
4321
  renamed(from, to) {
3822
4322
  for (const [path, count] of [...this]) {
3823
- if (!within2(path, from)) continue;
4323
+ if (!within3(path, from)) continue;
3824
4324
  const moved2 = to + path.slice(from.length);
3825
4325
  this.delete(path);
3826
4326
  this.set(moved2, (this.get(moved2) ?? 0) + count);
3827
4327
  if (this.revoked.delete(path)) this.revoked.add(moved2);
3828
4328
  }
4329
+ for (const [path, count] of [...this.opens]) {
4330
+ if (!within3(path, from)) continue;
4331
+ const moved2 = to + path.slice(from.length);
4332
+ this.opens.delete(path);
4333
+ this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
4334
+ }
3829
4335
  }
3830
4336
  isRevoked(path) {
3831
4337
  return this.revoked.has(path);
3832
4338
  }
3833
4339
  };
3834
- var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
4340
+ var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3835
4341
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
3836
4342
  function statOf(attr, path, mount) {
3837
4343
  const mtime = new Date(attr.mtime);
@@ -3920,7 +4426,7 @@ var MountTable = class {
3920
4426
  locate(path) {
3921
4427
  let best;
3922
4428
  for (const mount of this.mounts.values()) {
3923
- if (within2(path, mount.target) && (!best || mount.target.length > best.target.length))
4429
+ if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
3924
4430
  best = mount;
3925
4431
  }
3926
4432
  if (!best) return void 0;
@@ -4059,7 +4565,7 @@ var MountTable = class {
4059
4565
  async commitUnder(prefix) {
4060
4566
  const at = normalizePath(prefix);
4061
4567
  await Promise.all(
4062
- [...this.sessions.keys()].filter((p) => within2(p, at)).map((p) => this.commit(p))
4568
+ [...this.sessions.keys()].filter((p) => within3(p, at)).map((p) => this.commit(p))
4063
4569
  );
4064
4570
  }
4065
4571
  async commitOverlapping(real, offset, length2) {
@@ -4093,10 +4599,10 @@ var MountTable = class {
4093
4599
  this.deps.changed?.(...full);
4094
4600
  }
4095
4601
  forget(mount, path) {
4096
- for (const key of [...mount.attrs.keys()]) if (within2(key, path)) mount.attrs.delete(key);
4602
+ for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
4097
4603
  mount.lists.delete(path);
4098
- mount.lists.delete(parentOf4(path));
4099
- for (const key of [...mount.lists.keys()]) if (within2(key, path)) mount.lists.delete(key);
4604
+ mount.lists.delete(parentOf5(path));
4605
+ for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
4100
4606
  }
4101
4607
  async getattr(mount, path, rel2) {
4102
4608
  if (mount.conn.failure) throw mount.conn.failure;
@@ -4161,7 +4667,7 @@ var MountFs = class {
4161
4667
  if (attr.kind !== "symlink") return real;
4162
4668
  if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
4163
4669
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
4164
- return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
4670
+ return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
4165
4671
  }
4166
4672
  async open(mount, rel2, write) {
4167
4673
  return await mount.conn.call({
@@ -4675,6 +5181,8 @@ async function ranged(fs, path, req) {
4675
5181
  else await ops.truncate(path, req.length ?? 0);
4676
5182
  return done;
4677
5183
  }
5184
+ var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
5185
+ var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
4678
5186
  async function dispatchSyncFs(req) {
4679
5187
  const entry = resolveSyncFsToken(req.token);
4680
5188
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -4683,11 +5191,22 @@ async function dispatchSyncFs(req) {
4683
5191
  const path = fs.resolvePath(cwd, req.path);
4684
5192
  if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
4685
5193
  if (req.op === "hold") {
4686
- entry.hold?.(path, req.mode === 1);
5194
+ const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
5195
+ const real = on && open ? await fs.realpath?.(path, true).catch(() => path) : void 0;
5196
+ entry.hold?.(path, on, open, real);
4687
5197
  return done;
4688
5198
  }
4689
5199
  if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
4690
- return await run(entry, path, req);
5200
+ if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
5201
+ const serve = async () => {
5202
+ const kept = entry.unlinked?.(path);
5203
+ return run(entry, kept && !await fs.exists(path) ? kept.hidden : path, req);
5204
+ };
5205
+ return await (entry.gate ? entry.gate(path, serve) : serve());
5206
+ }
5207
+ if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
5208
+ const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
5209
+ return await entry.own(target, () => run(entry, path, req));
4691
5210
  } catch (err) {
4692
5211
  return toErrno(err);
4693
5212
  }
@@ -5037,12 +5556,17 @@ function ownNames(hostname) {
5037
5556
  }
5038
5557
 
5039
5558
  // src/kernel/net/proxy-service.ts
5559
+ var SEND_BUFFER2 = 1024 * 1024;
5560
+ var TLS_HANDSHAKE = 22;
5561
+ var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
5562
+ var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
5040
5563
  var REALM_PROXY_PORT = 3128;
5041
5564
  var DEFAULT_LIMITS = {
5042
5565
  maxConnections: 64,
5043
5566
  maxHead: 64 * 1024,
5044
5567
  idleMs: 12e4,
5045
- bodyBudget: 128 * 1024 * 1024
5568
+ bodyBudget: 128 * 1024 * 1024,
5569
+ closeWaitMs: 5e3
5046
5570
  };
5047
5571
  var HOP_BY_HOP = /* @__PURE__ */ new Set([
5048
5572
  "connection",
@@ -5233,11 +5757,27 @@ function plainResponse(status, message2) {
5233
5757
  cancel: async () => void 0
5234
5758
  };
5235
5759
  }
5236
- function timedSource(conn, idleMs, stop) {
5760
+ function timedSource(conn, idle, stop) {
5237
5761
  return {
5238
- read: (max) => conn.read(max, AbortSignal.any([stop, AbortSignal.timeout(idleMs)]))
5762
+ read: (max, signal) => {
5763
+ const own = signal ? [stop, signal] : [stop];
5764
+ if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
5765
+ return conn.read(max, AbortSignal.any(own));
5766
+ }
5239
5767
  };
5240
5768
  }
5769
+ function webSocketTarget(req) {
5770
+ return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
5771
+ }
5772
+ function wireCode(code) {
5773
+ if (code === 1005) return 1e3;
5774
+ return sendableCode(code) ? code : 1011;
5775
+ }
5776
+ function quietly(ms) {
5777
+ return new Promise((resolve) => {
5778
+ AbortSignal.timeout(ms).addEventListener("abort", () => resolve(), { once: true });
5779
+ });
5780
+ }
5241
5781
  var RealmProxy = class {
5242
5782
  options;
5243
5783
  listener;
@@ -5246,6 +5786,7 @@ var RealmProxy = class {
5246
5786
  slots;
5247
5787
  bodies;
5248
5788
  connections = /* @__PURE__ */ new Set();
5789
+ warnedWebSocket = false;
5249
5790
  closed;
5250
5791
  port;
5251
5792
  constructor(options) {
@@ -5294,9 +5835,10 @@ var RealmProxy = class {
5294
5835
  }
5295
5836
  async serve(conn) {
5296
5837
  this.connections.add(conn);
5297
- const incoming = new Incoming(timedSource(conn, this.limits.idleMs, this.stop.signal));
5838
+ const idle = { ms: this.limits.idleMs };
5839
+ const incoming = new Incoming(timedSource(conn, idle, this.stop.signal));
5298
5840
  try {
5299
- await this.requests({ sink: conn, incoming, conn, socket: conn });
5841
+ await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
5300
5842
  } finally {
5301
5843
  this.connections.delete(conn);
5302
5844
  conn.close();
@@ -5323,7 +5865,11 @@ var RealmProxy = class {
5323
5865
  const req = parseRequestHead(head);
5324
5866
  if (req.method === "CONNECT") {
5325
5867
  if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
5326
- await this.connect(ctx.conn, incoming, req);
5868
+ await this.connect(ctx.conn, incoming, req, ctx.idle);
5869
+ return false;
5870
+ }
5871
+ if (req.method === "GET" && isUpgrade(req.headers)) {
5872
+ await this.websocket(ctx, req);
5327
5873
  return false;
5328
5874
  }
5329
5875
  const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
@@ -5418,33 +5964,149 @@ var RealmProxy = class {
5418
5964
  if (chunked2) await conn.write(LAST_CHUNK, signal);
5419
5965
  return keep;
5420
5966
  }
5421
- async connect(conn, incoming, req) {
5967
+ async connect(conn, incoming, req, idle) {
5422
5968
  const target = tunnelTarget(req);
5423
- const tunnel = this.options.tunnel;
5424
- if (!tunnel) {
5425
- throw new HttpError(
5426
- 501,
5427
- `CONNECT ${target.host}:${target.port}: no tunnels through this proxy`
5428
- );
5429
- }
5430
- await this.options.tunnelReady?.().catch((e) => {
5431
- throw new HttpError(
5432
- 501,
5433
- `CONNECT ${target.host}:${target.port}: ${e instanceof Error ? e.message : String(e)}`
5434
- );
5435
- });
5969
+ const refused = target.port === 443 ? await this.tunnelReady(target) : void 0;
5970
+ if (refused) throw refused;
5436
5971
  await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
5437
5972
  try {
5973
+ const first = await incoming.peek(this.stop.signal);
5974
+ if (first === void 0) return;
5975
+ if (first !== TLS_HANDSHAKE) {
5976
+ const origin = `http://${target.port === 80 ? target.host : `${target.host}:${target.port}`}`;
5977
+ await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
5978
+ return;
5979
+ }
5980
+ const tunnel = this.options.tunnel;
5981
+ if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
5438
5982
  await tunnel(
5439
5983
  conn,
5440
5984
  incoming,
5441
5985
  target,
5442
5986
  this.stop.signal,
5443
- (source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn })
5987
+ (source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
5444
5988
  );
5445
5989
  } catch {
5446
5990
  }
5447
5991
  }
5992
+ async tunnelReady(target) {
5993
+ const name2 = `CONNECT ${target.host}:${target.port}`;
5994
+ if (!this.options.tunnel) return new HttpError(501, `${name2}: no tunnels through this proxy`);
5995
+ try {
5996
+ await this.options.tunnelReady?.();
5997
+ return void 0;
5998
+ } catch (e) {
5999
+ return new HttpError(501, `${name2}: ${e instanceof Error ? e.message : String(e)}`);
6000
+ }
6001
+ }
6002
+ async websocket(ctx, req) {
6003
+ const href = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(webSocketTarget(req));
6004
+ const { transport } = this.options;
6005
+ if (!transport.traits.websocket || !transport.websocket) {
6006
+ if (!this.warnedWebSocket) {
6007
+ this.warnedWebSocket = true;
6008
+ console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
6009
+ }
6010
+ throw new HttpError(501, NO_WEBSOCKET2);
6011
+ }
6012
+ const accept2 = await acceptKey(req.headers);
6013
+ const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
6014
+ const hangup = new AbortController();
6015
+ const waiting = new AbortController();
6016
+ void watchHangup(ctx.socket, hangup, waiting.signal);
6017
+ let socket;
6018
+ try {
6019
+ socket = await transport.websocket({
6020
+ url: toHostLoopback(href).replace(/^http/, "ws"),
6021
+ protocols,
6022
+ headers: forwardRequestHeaders(req.headers),
6023
+ signal: AbortSignal.any([this.stop.signal, hangup.signal])
6024
+ });
6025
+ } catch (e) {
6026
+ waiting.abort();
6027
+ throw new HttpError(502, e instanceof Error ? e.message : String(e));
6028
+ }
6029
+ ctx.idle.ms = Number.POSITIVE_INFINITY;
6030
+ try {
6031
+ if (hangup.signal.aborted) throw hangup.signal.reason;
6032
+ if (socket.protocol && !(protocols.includes(socket.protocol) && TOKEN2.test(socket.protocol))) {
6033
+ throw new HttpError(502, "the far end chose a subprotocol the client did not offer");
6034
+ }
6035
+ await ctx.sink.write(switchingHead(accept2, socket.protocol), this.stop.signal);
6036
+ await this.bridge(ctx, socket, hangup.signal);
6037
+ } catch (e) {
6038
+ socket.close(1001, "the client went away");
6039
+ throw e;
6040
+ } finally {
6041
+ waiting.abort();
6042
+ }
6043
+ }
6044
+ async bridge(ctx, socket, hangup) {
6045
+ const stop = this.stop.signal;
6046
+ let chain = Promise.resolve();
6047
+ const toGuest = (bytes) => {
6048
+ chain = chain.then(() => ctx.sink.write(bytes, stop));
6049
+ return chain;
6050
+ };
6051
+ const done4 = new AbortController();
6052
+ const reading = AbortSignal.any([stop, done4.signal, hangup]);
6053
+ const stopping = () => socket.close(1001, "the proxy is stopping");
6054
+ stop.addEventListener("abort", stopping, { once: true });
6055
+ let guestDone = false;
6056
+ let closeSent = false;
6057
+ const outbound2 = (async () => {
6058
+ for await (const data of socket.messages) {
6059
+ if (guestDone) continue;
6060
+ const text = typeof data === "string";
6061
+ await toGuest(
6062
+ frame(text ? OP_TEXT : OP_BINARY, text ? new TextEncoder().encode(data) : data)
6063
+ );
6064
+ }
6065
+ const closed = await socket.closed;
6066
+ if (guestDone) return;
6067
+ closeSent = true;
6068
+ await toGuest(closeFrame(wireCode(closed.code), closed.reason));
6069
+ AbortSignal.timeout(this.limits.closeWaitMs).addEventListener("abort", () => done4.abort(), {
6070
+ once: true
6071
+ });
6072
+ })().catch(() => done4.abort());
6073
+ const reader = new WsReader(ctx.incoming, MESSAGE_LIMIT, true);
6074
+ try {
6075
+ for (; ; ) {
6076
+ const event = await reader.next(reading);
6077
+ if (!event) break;
6078
+ if (event.kind === "ping") {
6079
+ await toGuest(pongFrame(event.payload));
6080
+ continue;
6081
+ }
6082
+ if (event.kind === "close") {
6083
+ guestDone = true;
6084
+ if (closeSent) break;
6085
+ const code = wireCode(event.code);
6086
+ socket.close(code, event.reason);
6087
+ const reply = await Promise.race([
6088
+ socket.closed,
6089
+ quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
6090
+ ]);
6091
+ await toGuest(closeFrame(wireCode(reply.code), reply.reason));
6092
+ break;
6093
+ }
6094
+ while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
6095
+ socket.send(event.kind === "text" ? event.text : event.bytes);
6096
+ }
6097
+ } catch (e) {
6098
+ if (e instanceof WsError) {
6099
+ guestDone = true;
6100
+ socket.close(e.code, e.message);
6101
+ await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
6102
+ }
6103
+ } finally {
6104
+ guestDone = true;
6105
+ socket.close(1e3, "the client went away");
6106
+ stop.removeEventListener("abort", stopping);
6107
+ await Promise.race([outbound2, quietly(this.limits.closeWaitMs)]);
6108
+ }
6109
+ }
5448
6110
  };
5449
6111
 
5450
6112
  // src/kernel/net/x509.ts
@@ -6266,7 +6928,7 @@ function parsePrefix(text) {
6266
6928
  if (length2 < 0 || length2 > max) return void 0;
6267
6929
  return { ...parsed, length: length2 };
6268
6930
  }
6269
- function within3(prefix, addr) {
6931
+ function within4(prefix, addr) {
6270
6932
  if (prefix.v6 !== addr.v6) return false;
6271
6933
  const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
6272
6934
  return addr.bits >> shift === prefix.bits >> shift;
@@ -6306,9 +6968,9 @@ var Routes = class {
6306
6968
  if (!addr) return "unreachable";
6307
6969
  if (addr.v6 && addr.bits === 1n) return "loopback";
6308
6970
  if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
6309
- if (RESERVED.some((p) => within3(p, addr))) return "reserved";
6971
+ if (RESERVED.some((p) => within4(p, addr))) return "reserved";
6310
6972
  if (addr.v6 && !this.ipv6) return "unreachable";
6311
- if (this.exit || this.table.some((p) => within3(p, addr))) return "uplink";
6973
+ if (this.exit || this.table.some((p) => within4(p, addr))) return "uplink";
6312
6974
  return "unreachable";
6313
6975
  }
6314
6976
  };
@@ -7277,7 +7939,7 @@ function isMissing(err) {
7277
7939
  if (err?.code === "ENOENT") return true;
7278
7940
  return err instanceof Error && err.message.startsWith("ENOENT");
7279
7941
  }
7280
- function within4(path, root) {
7942
+ function within5(path, root) {
7281
7943
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
7282
7944
  }
7283
7945
  var VfsNode = class {
@@ -7482,7 +8144,7 @@ var VfsNodes = class {
7482
8144
  return run2;
7483
8145
  }
7484
8146
  async entryKey(path) {
7485
- const parent = parentOf5(path);
8147
+ const parent = parentOf6(path);
7486
8148
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
7487
8149
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
7488
8150
  }
@@ -7530,12 +8192,12 @@ var VfsNodes = class {
7530
8192
  return node;
7531
8193
  }
7532
8194
  holds(prefix) {
7533
- for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
8195
+ for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
7534
8196
  return false;
7535
8197
  }
7536
8198
  writes(prefix) {
7537
8199
  for (const [path, node] of this.byPath)
7538
- if (node.writers > 0 && within4(path, prefix)) return true;
8200
+ if (node.writers > 0 && within5(path, prefix)) return true;
7539
8201
  return false;
7540
8202
  }
7541
8203
  closed(node, writable = false) {
@@ -7549,24 +8211,24 @@ var VfsNodes = class {
7549
8211
  this.onClosed?.(node.path);
7550
8212
  }
7551
8213
  async flush(path) {
7552
- for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
8214
+ for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
7553
8215
  }
7554
8216
  async unlinking(path) {
7555
8217
  for (const [held, node] of [...this.byPath]) {
7556
- if (!within4(held, path)) continue;
8218
+ if (!within5(held, path)) continue;
7557
8219
  await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
7558
8220
  }
7559
8221
  }
7560
8222
  revoke(prefix) {
7561
8223
  for (const [path, node] of this.byPath) {
7562
- if (!within4(path, prefix)) continue;
8224
+ if (!within5(path, prefix)) continue;
7563
8225
  node.revoke();
7564
8226
  this.byPath.delete(path);
7565
8227
  }
7566
8228
  }
7567
8229
  unlinked(path) {
7568
8230
  for (const [held, node] of [...this.byPath]) {
7569
- if (!within4(held, path)) continue;
8231
+ if (!within5(held, path)) continue;
7570
8232
  node.orphaned = true;
7571
8233
  this.byPath.delete(held);
7572
8234
  }
@@ -7575,8 +8237,8 @@ var VfsNodes = class {
7575
8237
  if (from === to) return;
7576
8238
  const moved2 = [];
7577
8239
  for (const [p, node] of this.byPath) {
7578
- if (within4(p, from)) moved2.push(node);
7579
- else if (within4(p, to)) {
8240
+ if (within5(p, from)) moved2.push(node);
8241
+ else if (within5(p, to)) {
7580
8242
  node.orphaned = true;
7581
8243
  this.byPath.delete(p);
7582
8244
  }
@@ -7599,7 +8261,7 @@ async function present(fs, path, follow = false) {
7599
8261
  }
7600
8262
  }
7601
8263
  var MAX_LINKS5 = 40;
7602
- function parentOf5(path) {
8264
+ function parentOf6(path) {
7603
8265
  return path.slice(0, path.lastIndexOf("/")) || "/";
7604
8266
  }
7605
8267
  async function linkOf(fs, path) {
@@ -7612,7 +8274,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
7612
8274
  const part = queue.shift();
7613
8275
  if (part === ".") continue;
7614
8276
  if (part === "..") {
7615
- at = parentOf5(at);
8277
+ at = parentOf6(at);
7616
8278
  continue;
7617
8279
  }
7618
8280
  const next = at === "/" ? `/${part}` : `${at}/${part}`;
@@ -7636,7 +8298,7 @@ function joinName(dir, name2) {
7636
8298
  }
7637
8299
  async function realTarget(fs, path) {
7638
8300
  const budget = { hops: 0 };
7639
- let dir = await realDir(fs, parentOf5(path), budget);
8301
+ let dir = await realDir(fs, parentOf6(path), budget);
7640
8302
  let name2 = path.slice(path.lastIndexOf("/") + 1);
7641
8303
  for (; ; ) {
7642
8304
  const link2 = await linkOf(fs, joinName(dir, name2));
@@ -7644,13 +8306,13 @@ async function realTarget(fs, path) {
7644
8306
  if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
7645
8307
  budget.hops++;
7646
8308
  const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
7647
- dir = await realDir(fs, parentOf5(target), budget);
8309
+ dir = await realDir(fs, parentOf6(target), budget);
7648
8310
  name2 = target.slice(target.lastIndexOf("/") + 1);
7649
8311
  }
7650
8312
  return joinName(dir, name2);
7651
8313
  }
7652
8314
  async function targetDir(fs, path) {
7653
- return parentOf5(await realTarget(fs, path).catch(() => path));
8315
+ return parentOf6(await realTarget(fs, path).catch(() => path));
7654
8316
  }
7655
8317
  async function refuseReadonly(fs, path, flags, opts = {}) {
7656
8318
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -8039,7 +8701,7 @@ var WasmProcess = class {
8039
8701
  const fd = this.fds.install(heldFile(), 3);
8040
8702
  try {
8041
8703
  const nodes = this.nodes;
8042
- const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : await nodes.targetKey(req.path);
8704
+ const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : req.create ? await nodes.targetKey(req.path) : await this.keptOr(await nodes.targetKey(req.path));
8043
8705
  const open = { ...req, path };
8044
8706
  if (req.exclusive) {
8045
8707
  await nodes.createExclusive(path);
@@ -8195,6 +8857,11 @@ var WasmProcess = class {
8195
8857
  return { ok: true, kind: "json", json: await file.stat() };
8196
8858
  }
8197
8859
  }
8860
+ keptPath;
8861
+ async keptOr(path) {
8862
+ const kept = this.keptPath?.(path);
8863
+ return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
8864
+ }
8198
8865
  async promote(req) {
8199
8866
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8200
8867
  if (req.share !== void 0) {
@@ -8202,7 +8869,7 @@ var WasmProcess = class {
8202
8869
  return;
8203
8870
  }
8204
8871
  if (req.path === void 0) throw new KernelError("EINVAL");
8205
- const path = req.orphan ? req.path : await this.nodes.targetKey(req.path);
8872
+ const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path));
8206
8873
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
8207
8874
  const file = vfsFile(
8208
8875
  this.options.fs,
@@ -8534,7 +9201,42 @@ var Dying = class {
8534
9201
  return [typeof reported === "number" ? reported : CRASHED, void 0];
8535
9202
  }
8536
9203
  };
8537
- function processInit(opts, sab, umask) {
9204
+ function notifiers(header3) {
9205
+ return {
9206
+ onPending(sig) {
9207
+ Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
9208
+ Atomics.notify(header3, SAB_I_SIGNALS);
9209
+ },
9210
+ onAsync() {
9211
+ Atomics.add(header3, SAB_I_ASYNC, 1);
9212
+ Atomics.notify(header3, SAB_I_ASYNC);
9213
+ }
9214
+ };
9215
+ }
9216
+ function publishStops(process, header3) {
9217
+ process.onState((state) => {
9218
+ Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
9219
+ Atomics.notify(header3, SAB_I_STOP);
9220
+ });
9221
+ }
9222
+ function transferOf(opts, lane) {
9223
+ const transfer = opts.fork ? [opts.fork.memory.buffer] : [];
9224
+ return lane ? [...transfer, lane.port] : transfer;
9225
+ }
9226
+ function openLane(token, dispatch2) {
9227
+ const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
9228
+ const { port1, port2 } = new MessageChannel();
9229
+ const responder = attachSyncSabResponder(port1, sab, token, { dispatch: dispatch2 });
9230
+ port1.start();
9231
+ return {
9232
+ lane: { sab, port: port2 },
9233
+ dispose() {
9234
+ responder.dispose();
9235
+ port1.close();
9236
+ }
9237
+ };
9238
+ }
9239
+ function processInit(opts, sab, umask, lane) {
8538
9240
  const base = {
8539
9241
  type: WASM_PROCESS_INIT,
8540
9242
  pid: opts.shownPid ?? opts.pid,
@@ -8547,7 +9249,12 @@ function processInit(opts, sab, umask) {
8547
9249
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
8548
9250
  };
8549
9251
  if (opts.program.abi === "js") {
8550
- return { ...base, program: opts.program, fds: inheritedFds(opts.fds) };
9252
+ return {
9253
+ ...base,
9254
+ program: opts.program,
9255
+ fds: inheritedFds(opts.fds),
9256
+ lane
9257
+ };
8551
9258
  }
8552
9259
  return {
8553
9260
  ...base,
@@ -8561,6 +9268,19 @@ function inherited(opts) {
8561
9268
  ...opts.umask !== void 0 ? { umask: opts.umask } : {}
8562
9269
  };
8563
9270
  }
9271
+ function holdingToken(opts, holds) {
9272
+ return mintSyncFsToken({
9273
+ fs: opts.fs,
9274
+ cwd: opts.cwd,
9275
+ ...opts.statfs ? { statfs: opts.statfs } : {},
9276
+ hold: (path, on, open, real) => holds.hold(path, on, open, real),
9277
+ unlinked: (path) => holds.keptAt(path),
9278
+ gate: (path, op) => opts.unlinkGate && holds.holding(path) ? opts.unlinkGate.run(holds.keyOf(path), op) : op(),
9279
+ own: (path, op) => holds.own(path, op),
9280
+ revoked: (path) => holds.isRevoked(path),
9281
+ renamed: (from, to) => holds.renamed(from, to)
9282
+ });
9283
+ }
8564
9284
  function spawnWasmProcess(opts) {
8565
9285
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
8566
9286
  const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
@@ -8586,37 +9306,22 @@ function spawnWasmProcess(opts) {
8586
9306
  locks: opts.locks,
8587
9307
  onReap: opts.onReap,
8588
9308
  resolver: opts.resolver,
8589
- onPending: (sig) => {
8590
- Atomics.or(header3, SAB_I_SIGNALS, sigbit(sig));
8591
- Atomics.notify(header3, SAB_I_SIGNALS);
8592
- },
8593
- onAsync: () => {
8594
- Atomics.add(header3, SAB_I_ASYNC, 1);
8595
- Atomics.notify(header3, SAB_I_ASYNC);
8596
- },
9309
+ ...notifiers(header3),
8597
9310
  onTimer: (which) => void Atomics.or(header3, SAB_I_TIMERS, 1 << which),
8598
9311
  hasPending: () => Atomics.load(header3, SAB_I_SIGNALS) !== 0 || Atomics.load(header3, SAB_I_TIMERS) !== 0,
8599
9312
  raise: (sig) => signal(sig),
8600
9313
  ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
8601
9314
  });
8602
- process.onState((state) => {
8603
- Atomics.store(header3, SAB_I_STOP, state === "stopped" ? 1 : 0);
8604
- Atomics.notify(header3, SAB_I_STOP);
8605
- });
9315
+ publishStops(process, header3);
8606
9316
  const holds = new HeldPaths();
8607
9317
  opts.held?.add(holds);
8608
- const token = mintSyncFsToken({
8609
- fs: opts.fs,
8610
- cwd: opts.cwd,
8611
- ...opts.statfs ? { statfs: opts.statfs } : {},
8612
- hold: (path, on) => holds.hold(path, on),
8613
- revoked: (path) => holds.isRevoked(path),
8614
- renamed: (from, to) => holds.renamed(from, to)
8615
- });
9318
+ const token = holdingToken(opts, holds);
9319
+ process.keptPath = (path) => holds.keptAt(path)?.hidden;
8616
9320
  const worker = opts.createWorker();
8617
9321
  const dying = new Dying();
8618
9322
  const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
8619
9323
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
9324
+ const lane = opts.program.abi === "js" ? openLane(token, dispatch2) : void 0;
8620
9325
  const { promise: exited, resolve: settle } = Promise.withResolvers();
8621
9326
  let done4 = false;
8622
9327
  let endedBy;
@@ -8629,6 +9334,7 @@ function spawnWasmProcess(opts) {
8629
9334
  worker.removeEventListener("error", onError);
8630
9335
  for (const tid of [...threads.keys()]) endThread(tid);
8631
9336
  responder.dispose();
9337
+ lane?.dispose();
8632
9338
  revokeSyncFsToken(token);
8633
9339
  opts.held?.delete(holds);
8634
9340
  worker.terminate();
@@ -8699,8 +9405,10 @@ function spawnWasmProcess(opts) {
8699
9405
  };
8700
9406
  tw.postMessage(tinit);
8701
9407
  };
8702
- const init = processInit(opts, sab, process.umask);
8703
- worker.postMessage(init, opts.fork ? [opts.fork.memory.buffer] : []);
9408
+ worker.postMessage(
9409
+ processInit(opts, sab, process.umask, lane?.lane),
9410
+ transferOf(opts, lane?.lane)
9411
+ );
8704
9412
  const signal = (sig) => {
8705
9413
  if (process.signal(sig) !== "terminate") return;
8706
9414
  const code = sig === SIG.KILL ? 137 : 128 + sig;
@@ -10261,6 +10969,7 @@ var Launcher = class {
10261
10969
  deciding = /* @__PURE__ */ new Map();
10262
10970
  identities = { asked: 0, timedOut: 0 };
10263
10971
  nodes;
10972
+ unlinked;
10264
10973
  held = new HeldSet((path) => this.released(path));
10265
10974
  processMounts;
10266
10975
  booting = new AbortController();
@@ -10305,7 +11014,15 @@ var Launcher = class {
10305
11014
  const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
10306
11015
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
10307
11016
  this.openFiles.add(this.nodes);
10308
- this.fs = keepingOpen(fs, this.nodes);
11017
+ this.unlinked = new UnlinkedKeeper(
11018
+ fs,
11019
+ this.held,
11020
+ (path) => this.mountRoot(path),
11021
+ webLocks(),
11022
+ (path) => [...this.openFiles].some((nodes) => nodes.holds(path))
11023
+ );
11024
+ this.fs = keepingOpen(this.unlinked.wrap(), this.nodes);
11025
+ void this.unlinked.sweep("/");
10309
11026
  this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
10310
11027
  this.catalog = void 0;
10311
11028
  this.binfmts = void 0;
@@ -10515,6 +11232,7 @@ var Launcher = class {
10515
11232
  openFiles: this.openFiles,
10516
11233
  nodes: this.nodes,
10517
11234
  held: this.held,
11235
+ unlinkGate: this.unlinked.gate,
10518
11236
  statfs: (path) => this.mounts.statfs(path),
10519
11237
  mounts: () => this.mounts.list(),
10520
11238
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -10672,6 +11390,7 @@ var Launcher = class {
10672
11390
  }
10673
11391
  released(path) {
10674
11392
  const at = normalizePath(path);
11393
+ this.unlinked.closed(at);
10675
11394
  const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
10676
11395
  if (!open) void this.mounts.commit(at).catch(() => void 0);
10677
11396
  }
@@ -10767,10 +11486,23 @@ var Launcher = class {
10767
11486
  }
10768
11487
  };
10769
11488
  }
10770
- mount(spec) {
10771
- if (spec.type !== "fsa" || spec.source.startsWith("fsa:"))
10772
- return this.mounts.mount(spec, this.fs);
10773
- return this.mounts.mount({ ...spec, source: `fsa:${crypto.randomUUID()}` }, this.fs);
11489
+ async mount(spec) {
11490
+ const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
11491
+ const entry = await this.mounts.mount(
11492
+ fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
11493
+ this.fs
11494
+ );
11495
+ void this.unlinked.sweep(entry.target);
11496
+ return entry;
11497
+ }
11498
+ mountRoot(path) {
11499
+ let root = "/";
11500
+ for (const { target } of this.mounts.list()) {
11501
+ if (target.length > root.length && (path === target || path.startsWith(`${target}/`))) {
11502
+ root = target;
11503
+ }
11504
+ }
11505
+ return root;
10774
11506
  }
10775
11507
  async insert(target, handle, source) {
10776
11508
  const at = normalizePath(target);
@@ -10784,6 +11516,7 @@ var Launcher = class {
10784
11516
  await this.media.put(slot.id, handle);
10785
11517
  slot.medium.insert(handle);
10786
11518
  slot.served.invalidate(true);
11519
+ void this.unlinked.sweep(at);
10787
11520
  }
10788
11521
  umount(target, detach = false) {
10789
11522
  const at = normalizePath(target);
@@ -11153,12 +11886,15 @@ var RemoteTransport = class {
11153
11886
  port;
11154
11887
  waiting = /* @__PURE__ */ new Map();
11155
11888
  nextId = 0;
11889
+ sockets = /* @__PURE__ */ new Map();
11156
11890
  failure;
11157
11891
  constructor(port, traits) {
11158
11892
  this.port = port;
11159
11893
  this.traits = traits;
11160
11894
  }
11161
11895
  receive(reply) {
11896
+ const socket = this.sockets.get(reply.nid);
11897
+ if (socket && this.toSocket(reply, socket)) return;
11162
11898
  const waiter = this.waiting.get(reply.nid);
11163
11899
  if (!waiter) return;
11164
11900
  this.waiting.delete(reply.nid);
@@ -11166,10 +11902,22 @@ var RemoteTransport = class {
11166
11902
  waiter.reject(new TransportError(reply.message, reply.status, reply.code));
11167
11903
  } else waiter.resolve(reply);
11168
11904
  }
11905
+ toSocket(reply, socket) {
11906
+ if (reply.net === "ws-message") socket.queue.push(reply.data);
11907
+ else if (reply.net === "ws-acked") socket.acked(reply.n);
11908
+ else if (reply.net === "ws-closed") {
11909
+ this.sockets.delete(reply.nid);
11910
+ socket.queue.end({ code: reply.code, reason: reply.reason });
11911
+ } else return false;
11912
+ return true;
11913
+ }
11169
11914
  fail(error) {
11170
11915
  this.failure = error;
11171
11916
  for (const waiter of this.waiting.values()) waiter.reject(error);
11172
11917
  this.waiting.clear();
11918
+ for (const socket of this.sockets.values())
11919
+ socket.queue.end({ code: 1011, reason: error.message });
11920
+ this.sockets.clear();
11173
11921
  }
11174
11922
  ask(call) {
11175
11923
  if (this.failure) return Promise.reject(this.failure);
@@ -11248,6 +11996,46 @@ var RemoteTransport = class {
11248
11996
  }
11249
11997
  };
11250
11998
  }
11999
+ async websocket(request) {
12000
+ const nid = ++this.nextId;
12001
+ const { signal } = request;
12002
+ signal.throwIfAborted();
12003
+ const abort = () => this.cancel(nid);
12004
+ signal.addEventListener("abort", abort, { once: true });
12005
+ let opened2;
12006
+ try {
12007
+ opened2 = await this.ask({
12008
+ net: "ws-open",
12009
+ nid,
12010
+ url: request.url,
12011
+ protocols: request.protocols,
12012
+ headers: request.headers
12013
+ });
12014
+ } finally {
12015
+ signal.removeEventListener("abort", abort);
12016
+ }
12017
+ const close = (code, reason) => this.port.postMessage({ net: "ws-close", nid, code, reason });
12018
+ const queue = new MessageQueue(
12019
+ () => close(1e3, "a message is too big"),
12020
+ Number.POSITIVE_INFINITY,
12021
+ (n) => this.port.postMessage({ net: "ws-consumed", nid, n })
12022
+ );
12023
+ let unacked = 0;
12024
+ this.sockets.set(nid, { queue, acked: (n) => void (unacked -= n) });
12025
+ return {
12026
+ protocol: opened2.protocol,
12027
+ get buffered() {
12028
+ return unacked;
12029
+ },
12030
+ send: (data) => {
12031
+ unacked += payloadSize(data);
12032
+ this.port.postMessage({ net: "ws-send", nid, data });
12033
+ },
12034
+ messages: queue,
12035
+ close,
12036
+ closed: queue.closed
12037
+ };
12038
+ }
11251
12039
  };
11252
12040
 
11253
12041
  // src/serve.ts
@@ -11869,7 +12657,7 @@ function hostfsFetchOf(options) {
11869
12657
  };
11870
12658
  }
11871
12659
  function nodeTransport() {
11872
- const transport = fetchTransport();
12660
+ const transport = fetchTransport({ webSocket: true, webSocketHeaders: true });
11873
12661
  return { ...transport, traits: { ...transport.traits, crossOrigin: "any" } };
11874
12662
  }
11875
12663
  var TERMINATED = "the kernel is terminated";