@ai-ecoverse/slicc-kernel 1.26.6 → 1.28.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.
@@ -883,7 +883,7 @@ function portCdpHook(ask, gone) {
883
883
  }
884
884
 
885
885
  // src/client/protocol.ts
886
- var PROTOCOL = [1, 5];
886
+ var PROTOCOL = [1, 7];
887
887
  var FS_METHODS = [
888
888
  "readFile",
889
889
  "writeFile",
@@ -1253,6 +1253,170 @@ var FdTable = class _FdTable {
1253
1253
  }
1254
1254
  };
1255
1255
 
1256
+ // src/process/wasi/wasi-abi.ts
1257
+ var E = {
1258
+ SUCCESS: 0,
1259
+ "2BIG": 1,
1260
+ ACCES: 2,
1261
+ ADDRINUSE: 3,
1262
+ ADDRNOTAVAIL: 4,
1263
+ AFNOSUPPORT: 5,
1264
+ AGAIN: 6,
1265
+ ALREADY: 7,
1266
+ BADF: 8,
1267
+ BADMSG: 9,
1268
+ BUSY: 10,
1269
+ CANCELED: 11,
1270
+ CHILD: 12,
1271
+ CONNABORTED: 13,
1272
+ CONNREFUSED: 14,
1273
+ CONNRESET: 15,
1274
+ DEADLK: 16,
1275
+ DESTADDRREQ: 17,
1276
+ DOM: 18,
1277
+ DQUOT: 19,
1278
+ EXIST: 20,
1279
+ FAULT: 21,
1280
+ FBIG: 22,
1281
+ HOSTUNREACH: 23,
1282
+ IDRM: 24,
1283
+ ILSEQ: 25,
1284
+ INPROGRESS: 26,
1285
+ INTR: 27,
1286
+ INVAL: 28,
1287
+ IO: 29,
1288
+ ISCONN: 30,
1289
+ ISDIR: 31,
1290
+ LOOP: 32,
1291
+ MFILE: 33,
1292
+ MLINK: 34,
1293
+ MSGSIZE: 35,
1294
+ MULTIHOP: 36,
1295
+ NAMETOOLONG: 37,
1296
+ NETDOWN: 38,
1297
+ NETRESET: 39,
1298
+ NETUNREACH: 40,
1299
+ NFILE: 41,
1300
+ NOBUFS: 42,
1301
+ NODEV: 43,
1302
+ NOENT: 44,
1303
+ NOEXEC: 45,
1304
+ NOLCK: 46,
1305
+ NOLINK: 47,
1306
+ NOMEM: 48,
1307
+ NOMSG: 49,
1308
+ NOPROTOOPT: 50,
1309
+ NOSPC: 51,
1310
+ NOSYS: 52,
1311
+ NOTCONN: 53,
1312
+ NOTDIR: 54,
1313
+ NOTEMPTY: 55,
1314
+ NOTRECOVERABLE: 56,
1315
+ NOTSOCK: 57,
1316
+ NOTSUP: 58,
1317
+ NOTTY: 59,
1318
+ NXIO: 60,
1319
+ OVERFLOW: 61,
1320
+ OWNERDEAD: 62,
1321
+ PERM: 63,
1322
+ PIPE: 64,
1323
+ PROTO: 65,
1324
+ PROTONOSUPPORT: 66,
1325
+ PROTOTYPE: 67,
1326
+ RANGE: 68,
1327
+ ROFS: 69,
1328
+ SPIPE: 70,
1329
+ SRCH: 71,
1330
+ STALE: 72,
1331
+ TIMEDOUT: 73,
1332
+ TXTBSY: 74,
1333
+ XDEV: 75,
1334
+ NOTCAPABLE: 76
1335
+ };
1336
+ var RIGHTS = {
1337
+ FD_DATASYNC: 1n << 0n,
1338
+ FD_READ: 1n << 1n,
1339
+ FD_SEEK: 1n << 2n,
1340
+ FD_FDSTAT_SET_FLAGS: 1n << 3n,
1341
+ FD_SYNC: 1n << 4n,
1342
+ FD_TELL: 1n << 5n,
1343
+ FD_WRITE: 1n << 6n,
1344
+ ALL: (1n << 30n) - 1n
1345
+ };
1346
+ var WASI_SIGNAL_TO_POSIX = Object.fromEntries(
1347
+ Array.from({ length: 30 }, (_, i) => [i + 1, i + 1 < 16 ? i + 1 : i + 2])
1348
+ );
1349
+
1350
+ // src/process/wasi-errno.ts
1351
+ var WASI_ERRNO = {
1352
+ ...Object.fromEntries(Object.entries(E).map(([name2, errno]) => [`E${name2}`, errno])),
1353
+ EWOULDBLOCK: E.AGAIN,
1354
+ ENOTSUP: 138,
1355
+ EOPNOTSUPP: 138,
1356
+ ENOMEDIUM: 148
1357
+ };
1358
+ function wasiErrno(code) {
1359
+ return WASI_ERRNO[code] ?? E.IO;
1360
+ }
1361
+
1362
+ // src/kernel/net/uplink-socket.ts
1363
+ var CHUNK = 64 * 1024;
1364
+ function hostPort(text) {
1365
+ const match = /^\[?([^\]]*?)\]?:(\d{1,5})$/.exec(text);
1366
+ const port = Number(match?.[2]);
1367
+ const host = match?.[1];
1368
+ if (!host || port > 65535) return void 0;
1369
+ return { host, port };
1370
+ }
1371
+ async function inbound(conn, rx) {
1372
+ for (; ; ) {
1373
+ while (!rx.writeReady) await rx.changed();
1374
+ if (rx.readersGone) return;
1375
+ const bytes = await conn.read();
1376
+ if (!bytes) return;
1377
+ await rx.write(bytes);
1378
+ }
1379
+ }
1380
+ async function outbound(conn, tx) {
1381
+ for (; ; ) {
1382
+ const bytes = await tx.read(CHUNK);
1383
+ if (bytes.length === 0) {
1384
+ conn.closeWrite();
1385
+ return;
1386
+ }
1387
+ for (let at = 0; at < bytes.length; ) {
1388
+ const n = await conn.write(bytes.subarray(at));
1389
+ if (!(n > 0)) throw new Error("the uplink wrote nothing");
1390
+ at += n;
1391
+ }
1392
+ }
1393
+ }
1394
+ async function released(rx, tx) {
1395
+ while (!(rx.readersGone && tx.writersGone)) await Promise.race([rx.changed(), tx.changed()]);
1396
+ }
1397
+ function uplinkLink(conn, onReset) {
1398
+ const rx = new KernelPipe();
1399
+ const tx = new KernelPipe();
1400
+ for (const pipe of [rx, tx]) {
1401
+ pipe.openRead();
1402
+ pipe.openWrite();
1403
+ }
1404
+ let open = true;
1405
+ const close = () => {
1406
+ if (!open) return;
1407
+ open = false;
1408
+ conn.close();
1409
+ };
1410
+ void inbound(conn, rx).catch(() => {
1411
+ onReset();
1412
+ close();
1413
+ tx.closeRead();
1414
+ }).finally(() => rx.closeWrite());
1415
+ void outbound(conn, tx).catch(close).finally(() => tx.closeRead());
1416
+ void released(rx, tx).then(close);
1417
+ return { rx, tx };
1418
+ }
1419
+
1256
1420
  // src/kernel/socket.ts
1257
1421
  var SOMAXCONN = 4096;
1258
1422
  var EPHEMERAL_FIRST = 32768;
@@ -1270,6 +1434,21 @@ var AF_INET = 2;
1270
1434
  var SHUT_RD = 0;
1271
1435
  var SHUT_WR = 1;
1272
1436
  var SHUT_RDWR = 2;
1437
+ var DIAL_TIMEOUT_MS = 3e4;
1438
+ var DIAL_ERRORS = [
1439
+ "ECONNREFUSED",
1440
+ "ENETUNREACH",
1441
+ "EHOSTUNREACH",
1442
+ "ETIMEDOUT",
1443
+ "ECONNRESET"
1444
+ ];
1445
+ function dialError(e, timedOut, closed) {
1446
+ if (timedOut) return new KernelError("ETIMEDOUT");
1447
+ if (closed) return new KernelError("ECONNABORTED");
1448
+ const code = e?.code;
1449
+ const known = DIAL_ERRORS.find((errno) => errno === code);
1450
+ return new KernelError(known ?? "EHOSTUNREACH");
1451
+ }
1273
1452
  function isLoopback(host) {
1274
1453
  return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
1275
1454
  }
@@ -1318,6 +1497,9 @@ var KernelSocket = class _KernelSocket {
1318
1497
  net;
1319
1498
  domain;
1320
1499
  state = "open";
1500
+ dialing;
1501
+ failed;
1502
+ reset = false;
1321
1503
  local;
1322
1504
  peer;
1323
1505
  link;
@@ -1325,7 +1507,7 @@ var KernelSocket = class _KernelSocket {
1325
1507
  writeShut = false;
1326
1508
  queue = [];
1327
1509
  backlog = 0;
1328
- bound = false;
1510
+ boundAt;
1329
1511
  changes = new Changes();
1330
1512
  options = /* @__PURE__ */ new Map();
1331
1513
  constructor(net, domain) {
@@ -1347,12 +1529,16 @@ var KernelSocket = class _KernelSocket {
1347
1529
  async read(max, signal) {
1348
1530
  const { rx } = this.connection();
1349
1531
  if (this.readShut || max <= 0) return new Uint8Array(0);
1350
- return pipeCall(() => rx.read(max, signal));
1532
+ const bytes = await pipeCall(() => rx.read(max, signal));
1533
+ if (bytes.length === 0 && this.reset) throw new KernelError("ECONNRESET");
1534
+ return bytes;
1351
1535
  }
1352
1536
  async peek(max, signal) {
1353
1537
  const { rx } = this.connection();
1354
1538
  if (this.readShut || max <= 0) return new Uint8Array(0);
1355
- return pipeCall(() => rx.peek(max, signal));
1539
+ const bytes = await pipeCall(() => rx.peek(max, signal));
1540
+ if (bytes.length === 0 && this.reset) throw new KernelError("ECONNRESET");
1541
+ return bytes;
1356
1542
  }
1357
1543
  async write(bytes, signal) {
1358
1544
  const { tx } = this.connection();
@@ -1363,6 +1549,8 @@ var KernelSocket = class _KernelSocket {
1363
1549
  if (this.state === "listening") {
1364
1550
  return { readable: this.queue.length > 0, writable: false, hangup: false };
1365
1551
  }
1552
+ if (this.state === "connecting") return { readable: false, writable: false, hangup: false };
1553
+ if (this.failed) return { readable: true, writable: true, hangup: true };
1366
1554
  if (this.state !== "connected" || !this.link) {
1367
1555
  return { readable: false, writable: false, hangup: true };
1368
1556
  }
@@ -1384,7 +1572,7 @@ var KernelSocket = class _KernelSocket {
1384
1572
  if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
1385
1573
  if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
1386
1574
  this.local = this.net.bind(this, addr);
1387
- this.bound = true;
1575
+ this.boundAt = this.local;
1388
1576
  }
1389
1577
  listen(backlog) {
1390
1578
  if (this.state === "connected") throw new KernelError("EISCONN");
@@ -1411,15 +1599,19 @@ var KernelSocket = class _KernelSocket {
1411
1599
  this.changes.wake();
1412
1600
  return true;
1413
1601
  }
1414
- connect(addr) {
1602
+ connect(addr, viaUplink = true) {
1415
1603
  if (this.state === "connected") throw new KernelError("EISCONN");
1604
+ if (this.state === "connecting") throw new KernelError("EALREADY");
1416
1605
  if (this.state !== "open") throw new KernelError("EINVAL");
1417
1606
  if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
1607
+ this.failed = void 0;
1608
+ const route = viaUplink && addr.family === "inet" ? this.net.uplinkFor(addr.host) : void 0;
1609
+ if (route) return this.dial(addr, route);
1418
1610
  const target = this.net.target(addr);
1419
1611
  if (!this.local) {
1420
1612
  if (addr.family === "inet") {
1421
1613
  this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
1422
- this.bound = true;
1614
+ this.boundAt = this.local;
1423
1615
  } else this.local = { family: "unix", path: "" };
1424
1616
  }
1425
1617
  const server = new _KernelSocket(this.net, this.domain);
@@ -1432,6 +1624,52 @@ var KernelSocket = class _KernelSocket {
1432
1624
  server.close();
1433
1625
  throw new KernelError("ECONNREFUSED");
1434
1626
  }
1627
+ return void 0;
1628
+ }
1629
+ dial(addr, route) {
1630
+ this.state = "connecting";
1631
+ const abort = new AbortController();
1632
+ this.dialing = abort;
1633
+ let timedOut = false;
1634
+ const timer = setTimeout(() => {
1635
+ timedOut = true;
1636
+ abort.abort(new KernelError("ETIMEDOUT"));
1637
+ }, route.timeoutMs ?? DIAL_TIMEOUT_MS);
1638
+ const abandoned = new Promise((_, reject) => {
1639
+ abort.signal.addEventListener("abort", () => reject(abort.signal.reason), { once: true });
1640
+ });
1641
+ const { host, port } = addr;
1642
+ const dialled = new Promise(
1643
+ (resolve) => resolve(route.uplink.dial({ network: "tcp", host, port, signal: abort.signal }))
1644
+ );
1645
+ dialled.then(
1646
+ (conn) => abort.signal.aborted && conn.close(),
1647
+ () => void 0
1648
+ );
1649
+ return Promise.race([dialled, abandoned]).then(
1650
+ (conn) => {
1651
+ if (abort.signal.aborted) throw dialError(abort.signal.reason, timedOut, true);
1652
+ this.local = { family: "inet", host: "0.0.0.0", port: 0, ...hostPort(conn.localAddr) };
1653
+ this.peer = { ...addr, ...hostPort(conn.remoteAddr) };
1654
+ this.dialing = void 0;
1655
+ this.attach(
1656
+ uplinkLink(conn, () => {
1657
+ this.reset = true;
1658
+ this.changes.wake();
1659
+ })
1660
+ );
1661
+ },
1662
+ (e) => {
1663
+ const error = dialError(e, timedOut, abort.signal.aborted);
1664
+ if (this.dialing === abort) {
1665
+ this.dialing = void 0;
1666
+ this.failed = error.code;
1667
+ this.state = "open";
1668
+ this.changes.wake();
1669
+ }
1670
+ throw error;
1671
+ }
1672
+ ).finally(() => clearTimeout(timer));
1435
1673
  }
1436
1674
  unlink() {
1437
1675
  const link2 = this.link;
@@ -1461,8 +1699,11 @@ var KernelSocket = class _KernelSocket {
1461
1699
  switch (name2) {
1462
1700
  case SO_TYPE:
1463
1701
  return SOCK_STREAM;
1464
- case SO_ERROR:
1465
- return 0;
1702
+ case SO_ERROR: {
1703
+ const failed = this.failed;
1704
+ this.failed = void 0;
1705
+ return failed ? wasiErrno(failed) : 0;
1706
+ }
1466
1707
  case SO_ACCEPTCONN:
1467
1708
  return this.state === "listening" ? 1 : 0;
1468
1709
  case SO_DOMAIN:
@@ -1480,6 +1721,8 @@ var KernelSocket = class _KernelSocket {
1480
1721
  close() {
1481
1722
  const was = this.state;
1482
1723
  this.state = "closed";
1724
+ this.dialing?.abort(new KernelError("ECONNABORTED"));
1725
+ this.dialing = void 0;
1483
1726
  if (was === "listening") {
1484
1727
  for (const pending of this.queue.splice(0)) pending.close();
1485
1728
  }
@@ -1489,8 +1732,8 @@ var KernelSocket = class _KernelSocket {
1489
1732
  this.readShut = true;
1490
1733
  this.writeShut = true;
1491
1734
  }
1492
- if (this.bound && this.local) this.net.release(this.local, this);
1493
- this.bound = false;
1735
+ if (this.boundAt) this.net.release(this.boundAt, this);
1736
+ this.boundAt = void 0;
1494
1737
  this.changes.wake();
1495
1738
  }
1496
1739
  static pair(net, domain) {
@@ -1513,6 +1756,15 @@ var LoopbackNet = class {
1513
1756
  activators = /* @__PURE__ */ new Map();
1514
1757
  internal = /* @__PURE__ */ new Set();
1515
1758
  nextEphemeral = EPHEMERAL_FIRST;
1759
+ route;
1760
+ useUplink(route) {
1761
+ this.route = route;
1762
+ }
1763
+ uplinkFor(host) {
1764
+ const route = this.route;
1765
+ if (!route) return void 0;
1766
+ return route.routes.classify(host) === "uplink" ? route : void 0;
1767
+ }
1516
1768
  kernelOnly(port, reserve = false) {
1517
1769
  if (reserve) this.internal.add(port);
1518
1770
  return this.internal.has(port);
@@ -1531,7 +1783,7 @@ var LoopbackNet = class {
1531
1783
  }
1532
1784
  connect(addr) {
1533
1785
  const socket = this.socket(addr.family);
1534
- socket.connect(addr);
1786
+ void socket.connect(addr, false);
1535
1787
  return socket;
1536
1788
  }
1537
1789
  bind(socket, addr) {
@@ -1572,7 +1824,7 @@ var LoopbackNet = class {
1572
1824
  };
1573
1825
 
1574
1826
  // src/kernel/dial.ts
1575
- var CHUNK = 64 * 1024;
1827
+ var CHUNK2 = 64 * 1024;
1576
1828
  var LOOPBACK_NAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
1577
1829
  function dialSocket(net, port, host = "127.0.0.1") {
1578
1830
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new KernelError("EINVAL");
@@ -1637,7 +1889,7 @@ function serveSocket(socket, port, onClose) {
1637
1889
  for (; ; ) {
1638
1890
  await turn();
1639
1891
  if (closed) return;
1640
- const chunk2 = await socket.read(CHUNK);
1892
+ const chunk2 = await socket.read(CHUNK2);
1641
1893
  if (chunk2.length === 0) break;
1642
1894
  credit--;
1643
1895
  send(chunk2, [chunk2.buffer]);
@@ -2086,6 +2338,7 @@ function serveClient(port, host) {
2086
2338
 
2087
2339
  // src/kernel/net/loopback-names.ts
2088
2340
  var HOST_LOOPBACK = "host.slicc.internal";
2341
+ var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
2089
2342
  var PAGE_LOOPBACK = "kernel.localhost";
2090
2343
  var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
2091
2344
  function toHostLoopback(href) {
@@ -3569,6 +3822,178 @@ var RemoteTransport = class {
3569
3822
  }
3570
3823
  };
3571
3824
 
3825
+ // src/kernel/net/routes.ts
3826
+ var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
3827
+ function ipv4Bytes(text) {
3828
+ const parts = text.split(".");
3829
+ if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
3830
+ const bytes = parts.map(Number);
3831
+ return bytes.every((b) => b <= 255) ? bytes : void 0;
3832
+ }
3833
+ function ipv6Groups(text) {
3834
+ if (!text.includes(":")) return void 0;
3835
+ let canonical;
3836
+ try {
3837
+ canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);
3838
+ } catch {
3839
+ return void 0;
3840
+ }
3841
+ const [head, tail] = canonical.split("::");
3842
+ const part = (s) => s ? s.split(":").map((h) => Number.parseInt(h, 16)) : [];
3843
+ const front = part(head);
3844
+ const back = part(tail);
3845
+ return tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
3846
+ }
3847
+ function toBits(values, width) {
3848
+ return values.reduce((acc, v) => acc << BigInt(width) | BigInt(v), 0n);
3849
+ }
3850
+ function addressBits(host) {
3851
+ const v4 = ipv4Bytes(host);
3852
+ if (v4) return { v6: false, bits: toBits(v4, 8) };
3853
+ const v6 = ipv6Groups(host);
3854
+ return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
3855
+ }
3856
+ function parsePrefix(text) {
3857
+ const parts = text.trim().split("/");
3858
+ if (parts.length > 2) return void 0;
3859
+ const [address = "", size] = parts;
3860
+ const parsed = addressBits(address);
3861
+ if (!parsed) return void 0;
3862
+ const max = parsed.v6 ? 128 : 32;
3863
+ const length2 = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
3864
+ if (length2 < 0 || length2 > max) return void 0;
3865
+ return { ...parsed, length: length2 };
3866
+ }
3867
+ function within2(prefix, addr) {
3868
+ if (prefix.v6 !== addr.v6) return false;
3869
+ const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
3870
+ return addr.bits >> shift === prefix.bits >> shift;
3871
+ }
3872
+ var RESERVED = [
3873
+ "0.0.0.0/8",
3874
+ "127.0.0.0/8",
3875
+ "169.254.0.0/16",
3876
+ "224.0.0.0/4",
3877
+ "240.0.0.0/4",
3878
+ "::/96",
3879
+ "::ffff:0:0/96",
3880
+ "fe80::/10",
3881
+ "ff00::/8"
3882
+ ].map((p) => parsePrefix(p));
3883
+ var Routes = class {
3884
+ table = [];
3885
+ exit = false;
3886
+ ipv6;
3887
+ constructor(ipv62 = false) {
3888
+ this.ipv6 = ipv62;
3889
+ }
3890
+ set(table) {
3891
+ const parsed = (table.prefixes ?? []).map(parsePrefix);
3892
+ const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
3893
+ if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
3894
+ this.table = parsed;
3895
+ this.exit = table.exit === true;
3896
+ }
3897
+ classify(host) {
3898
+ const name2 = host.toLowerCase().replace(/\.$/, "");
3899
+ if (name2 === "localhost" || name2.endsWith(".localhost") || name2 === "0.0.0.0") {
3900
+ return "loopback";
3901
+ }
3902
+ if (name2 === HOST_LOOPBACK || name2 === HOST_ADDRESS) return "host";
3903
+ const addr = addressBits(name2);
3904
+ if (!addr) return "unreachable";
3905
+ if (addr.v6 && addr.bits === 1n) return "loopback";
3906
+ if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
3907
+ if (RESERVED.some((p) => within2(p, addr))) return "reserved";
3908
+ if (addr.v6 && !this.ipv6) return "unreachable";
3909
+ if (this.exit || this.table.some((p) => within2(p, addr))) return "uplink";
3910
+ return "unreachable";
3911
+ }
3912
+ };
3913
+
3914
+ // src/kernel/net/uplink.ts
3915
+ function answerOf(result) {
3916
+ const answer = Array.isArray(result) ? { addresses: result } : result;
3917
+ const addresses = Array.isArray(answer?.addresses) ? answer.addresses.filter((a) => typeof a === "string") : [];
3918
+ return {
3919
+ addresses,
3920
+ ...typeof answer?.ttl === "number" && answer.ttl >= 0 ? { ttl: answer.ttl } : {}
3921
+ };
3922
+ }
3923
+ var NEVER = new Routes(true);
3924
+ var RemoteUplink = class {
3925
+ traits;
3926
+ routes;
3927
+ port;
3928
+ waiting = /* @__PURE__ */ new Map();
3929
+ nextId = 0;
3930
+ constructor(port, traits, routes) {
3931
+ this.port = port;
3932
+ this.traits = traits;
3933
+ this.routes = routes;
3934
+ }
3935
+ receive(reply) {
3936
+ const waiter = this.waiting.get(reply.uid);
3937
+ if (!waiter) return;
3938
+ this.waiting.delete(reply.uid);
3939
+ if (reply.uplink === "error") {
3940
+ waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));
3941
+ } else waiter.resolve(reply);
3942
+ }
3943
+ ask(call, signal, transfer) {
3944
+ const waiter = Promise.withResolvers();
3945
+ this.waiting.set(call.uid, waiter);
3946
+ const abort = () => {
3947
+ this.waiting.delete(call.uid);
3948
+ this.port.postMessage({ uplink: "cancel", uid: call.uid });
3949
+ waiter.reject(signal?.reason);
3950
+ };
3951
+ signal?.addEventListener("abort", abort, { once: true });
3952
+ const settled = () => signal?.removeEventListener("abort", abort);
3953
+ waiter.promise.then(settled, settled);
3954
+ this.port.postMessage(call, transfer);
3955
+ return waiter.promise;
3956
+ }
3957
+ async resolve(name2, family, signal) {
3958
+ const reply = await this.ask(
3959
+ { uplink: "resolve", uid: ++this.nextId, name: name2, family },
3960
+ signal
3961
+ );
3962
+ return answerOf(reply);
3963
+ }
3964
+ async dial(req) {
3965
+ req.signal.throwIfAborted();
3966
+ const { host, port } = req;
3967
+ const opened2 = await this.ask(
3968
+ { uplink: "dial", uid: ++this.nextId, host, port },
3969
+ req.signal
3970
+ );
3971
+ const { cid, localAddr, remoteAddr } = opened2;
3972
+ return {
3973
+ localAddr,
3974
+ remoteAddr,
3975
+ read: async () => {
3976
+ const got = await this.ask({
3977
+ uplink: "read",
3978
+ uid: ++this.nextId,
3979
+ cid
3980
+ });
3981
+ return got.bytes;
3982
+ },
3983
+ write: async (bytes) => {
3984
+ const copy = bytes.slice();
3985
+ const call = { uplink: "write", uid: ++this.nextId, cid, bytes: copy };
3986
+ const done4 = await this.ask(call, void 0, [
3987
+ copy.buffer
3988
+ ]);
3989
+ return done4.n;
3990
+ },
3991
+ closeWrite: () => this.port.postMessage({ uplink: "close-write", cid }),
3992
+ close: () => this.port.postMessage({ uplink: "close", cid })
3993
+ };
3994
+ }
3995
+ };
3996
+
3572
3997
  // src/cdp/types.ts
3573
3998
  var CDP_PORT = 9222;
3574
3999
  var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
@@ -4701,9 +5126,9 @@ function parseSize(text) {
4701
5126
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
4702
5127
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
4703
5128
  }
4704
- var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
5129
+ var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
4705
5130
  function heldUnder(held, target) {
4706
- for (const paths of held) for (const path of paths.keys()) if (within2(path, target)) return true;
5131
+ for (const paths of held) for (const path of paths.keys()) if (within3(path, target)) return true;
4707
5132
  return false;
4708
5133
  }
4709
5134
  var HeldPaths = class extends Map {
@@ -4717,11 +5142,11 @@ var HeldPaths = class extends Map {
4717
5142
  }
4718
5143
  }
4719
5144
  revoke(prefix) {
4720
- for (const path of this.keys()) if (within2(path, prefix)) this.revoked.add(path);
5145
+ for (const path of this.keys()) if (within3(path, prefix)) this.revoked.add(path);
4721
5146
  }
4722
5147
  renamed(from, to) {
4723
5148
  for (const [path, count] of [...this]) {
4724
- if (!within2(path, from)) continue;
5149
+ if (!within3(path, from)) continue;
4725
5150
  const moved2 = to + path.slice(from.length);
4726
5151
  this.delete(path);
4727
5152
  this.set(moved2, (this.get(moved2) ?? 0) + count);
@@ -4819,7 +5244,7 @@ var MountTable = class {
4819
5244
  locate(path) {
4820
5245
  let best;
4821
5246
  for (const mount of this.mounts.values()) {
4822
- if (within2(path, mount.target) && (!best || mount.target.length > best.target.length))
5247
+ if (within3(path, mount.target) && (!best || mount.target.length > best.target.length))
4823
5248
  best = mount;
4824
5249
  }
4825
5250
  if (!best) return void 0;
@@ -4931,10 +5356,10 @@ var MountTable = class {
4931
5356
  this.deps.changed?.(...full);
4932
5357
  }
4933
5358
  forget(mount, path) {
4934
- for (const key of [...mount.attrs.keys()]) if (within2(key, path)) mount.attrs.delete(key);
5359
+ for (const key of [...mount.attrs.keys()]) if (within3(key, path)) mount.attrs.delete(key);
4935
5360
  mount.lists.delete(path);
4936
5361
  mount.lists.delete(parentOf4(path));
4937
- for (const key of [...mount.lists.keys()]) if (within2(key, path)) mount.lists.delete(key);
5362
+ for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
4938
5363
  }
4939
5364
  async getattr(mount, path, rel2) {
4940
5365
  if (mount.conn.failure) throw mount.conn.failure;
@@ -5897,6 +6322,115 @@ var HttpHandles = class {
5897
6322
  }
5898
6323
  };
5899
6324
 
6325
+ // src/kernel/net/resolver.ts
6326
+ var RESOLVE_TTL_MAX = 60;
6327
+ var RESOLVE_TIMEOUT_MS = 1e4;
6328
+ var RESOLVE_CACHE_MAX = 256;
6329
+ var LOOPBACK_NAMES2 = /* @__PURE__ */ new Set(["localhost", "localhost.localdomain", "ip6-localhost", "::1"]);
6330
+ function hostsEntry(name2) {
6331
+ if (LOOPBACK_NAMES2.has(name2) || name2.endsWith(".localhost")) return "127.0.0.1";
6332
+ if (name2 === HOST_LOOPBACK) return HOST_LOOPBACK_ADDRESS.join(".");
6333
+ return ipv4Bytes(name2) ? name2 : void 0;
6334
+ }
6335
+ var Resolver = class {
6336
+ uplink;
6337
+ routes;
6338
+ now;
6339
+ timeoutMs;
6340
+ log;
6341
+ cache = /* @__PURE__ */ new Map();
6342
+ pending = /* @__PURE__ */ new Map();
6343
+ constructor(options) {
6344
+ this.uplink = options.uplink;
6345
+ this.routes = options.routes;
6346
+ this.now = options.now ?? Date.now;
6347
+ this.timeoutMs = options.timeoutMs ?? RESOLVE_TIMEOUT_MS;
6348
+ this.log = options.log ?? ((message2) => console.warn(message2));
6349
+ }
6350
+ async resolve(name2, family, blocking) {
6351
+ const key = name2.toLowerCase().replace(/\.$/, "");
6352
+ const local = hostsEntry(key);
6353
+ if (ipv6Groups(key)) {
6354
+ const v4 = family !== 6 && local ? [local] : [];
6355
+ return family === 4 ? v4 : [...v4, key];
6356
+ }
6357
+ if (local) {
6358
+ const six = LOOPBACK_NAMES2.has(key) || key.endsWith(".localhost") ? ["::1"] : [];
6359
+ return family === 6 ? six : family === 4 ? [local] : [local, ...six];
6360
+ }
6361
+ if (!this.uplink || key === "") return [];
6362
+ const asked = this.uplink.traits.ipv6 ? family : 4;
6363
+ if (family === 6 && asked !== 6) return [];
6364
+ const id = `${asked}:${key}`;
6365
+ const hit = this.cache.get(id);
6366
+ if (hit && hit.expires > this.now()) return hit.addresses;
6367
+ const signal = blocking?.();
6368
+ if (signal?.aborted) throw new KernelError("EINTR");
6369
+ let lookup = this.pending.get(id);
6370
+ if (!lookup) {
6371
+ const abort = new AbortController();
6372
+ lookup = { work: this.lookup(key, asked, id, abort.signal), abort, waiters: 0 };
6373
+ this.pending.set(id, lookup);
6374
+ void lookup.work.then(() => this.pending.delete(id));
6375
+ }
6376
+ return waitFor(lookup, signal);
6377
+ }
6378
+ async lookup(name2, family, id, abandoned) {
6379
+ const uplink = this.uplink;
6380
+ const timeout = AbortSignal.any([AbortSignal.timeout(this.timeoutMs), abandoned]);
6381
+ let answer;
6382
+ try {
6383
+ answer = answerOf(
6384
+ await Promise.race([
6385
+ uplink.resolve(name2, family, timeout),
6386
+ new Promise(
6387
+ (_, reject) => timeout.addEventListener("abort", () => reject(timeout.reason), { once: true })
6388
+ )
6389
+ ])
6390
+ );
6391
+ } catch {
6392
+ return [];
6393
+ }
6394
+ const addresses = answer.addresses.filter((a) => this.usable(name2, a, family));
6395
+ if (addresses.length > 0) this.remember(id, addresses, answer.ttl);
6396
+ return addresses;
6397
+ }
6398
+ usable(name2, address, family) {
6399
+ const v4 = ipv4Bytes(address) !== void 0;
6400
+ if (!v4 && (family === 4 || !ipv6Groups(address))) return false;
6401
+ if (v4 && family === 6) return false;
6402
+ const kind = this.routes.classify(address);
6403
+ if (kind !== "loopback" && kind !== "host" && kind !== "reserved") return true;
6404
+ this.log(`slicc-kernel: dropped the uplink's answer ${address} for ${name2}: it is ${kind}`);
6405
+ return false;
6406
+ }
6407
+ remember(id, addresses, ttl) {
6408
+ const seconds = Math.min(ttl ?? RESOLVE_TTL_MAX, RESOLVE_TTL_MAX);
6409
+ if (seconds <= 0) return;
6410
+ this.cache.delete(id);
6411
+ this.cache.set(id, { addresses, expires: this.now() + seconds * 1e3 });
6412
+ if (this.cache.size > RESOLVE_CACHE_MAX) {
6413
+ this.cache.delete(this.cache.keys().next().value);
6414
+ }
6415
+ }
6416
+ };
6417
+ function waitFor(lookup, signal) {
6418
+ lookup.waiters++;
6419
+ return new Promise((resolve, reject) => {
6420
+ const abort = () => {
6421
+ if (--lookup.waiters === 0) lookup.abort.abort(new KernelError("EINTR"));
6422
+ reject(new KernelError("EINTR"));
6423
+ };
6424
+ signal?.addEventListener("abort", abort, { once: true });
6425
+ void lookup.work.then((value) => {
6426
+ if (signal?.aborted) return;
6427
+ lookup.waiters--;
6428
+ signal?.removeEventListener("abort", abort);
6429
+ resolve(value);
6430
+ });
6431
+ });
6432
+ }
6433
+
5900
6434
  // src/kernel/tty.ts
5901
6435
  var ICRNL = 256;
5902
6436
  var IUTF8 = 16384;
@@ -6400,7 +6934,8 @@ var SOCKET_OPS = [
6400
6934
  "sock-shutdown",
6401
6935
  "sock-name",
6402
6936
  "sock-getopt",
6403
- "sock-setopt"
6937
+ "sock-setopt",
6938
+ "sock-resolve"
6404
6939
  ];
6405
6940
  function socketAt(fds, fd) {
6406
6941
  const file = fds.get(fd).file;
@@ -6431,10 +6966,15 @@ async function socketSyscall(req, proc) {
6431
6966
  return ok();
6432
6967
  case "sock-accept":
6433
6968
  return ok(await accept(proc, req.fd, req.nonblock));
6434
- case "sock-connect":
6435
- socketAt(fds, req.fd).connect(req.addr);
6436
- if (req.nonblock) throw new KernelError("EINPROGRESS");
6969
+ case "sock-connect": {
6970
+ const dialing = socketAt(fds, req.fd).connect(req.addr);
6971
+ if (req.nonblock) {
6972
+ dialing?.catch(() => void 0);
6973
+ throw new KernelError("EINPROGRESS");
6974
+ }
6975
+ if (dialing !== void 0) await interruptible(dialing, proc.blocking());
6437
6976
  return ok();
6977
+ }
6438
6978
  case "sock-shutdown":
6439
6979
  socketAt(fds, req.fd).shutdown(req.how);
6440
6980
  return ok();
@@ -6449,8 +6989,20 @@ async function socketSyscall(req, proc) {
6449
6989
  case "sock-setopt":
6450
6990
  socketAt(fds, req.fd).setOption(req.level, req.name, req.value);
6451
6991
  return ok();
6992
+ case "sock-resolve": {
6993
+ const addresses = await proc.resolve(req.name, req.family, () => proc.blocking());
6994
+ if (addresses.length === 0) throw new KernelError("ENOENT");
6995
+ return ok(addresses);
6996
+ }
6452
6997
  }
6453
6998
  }
6999
+ function interruptible(dialing, signal) {
7000
+ return new Promise((resolve, reject) => {
7001
+ const abort = () => reject(new KernelError("EINTR"));
7002
+ signal.addEventListener("abort", abort, { once: true });
7003
+ dialing.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
7004
+ });
7005
+ }
6454
7006
  function unnamed(domain) {
6455
7007
  return domain === "inet" ? { family: "inet", host: "0.0.0.0", port: 0 } : { family: "unix", path: "" };
6456
7008
  }
@@ -6700,7 +7252,7 @@ function isMissing(err) {
6700
7252
  if (err?.code === "ENOENT") return true;
6701
7253
  return err instanceof Error && err.message.startsWith("ENOENT");
6702
7254
  }
6703
- function within3(path, root) {
7255
+ function within4(path, root) {
6704
7256
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
6705
7257
  }
6706
7258
  var VfsNode = class {
@@ -6908,7 +7460,7 @@ var VfsNodes = class {
6908
7460
  return node;
6909
7461
  }
6910
7462
  holds(prefix) {
6911
- for (const path of this.byPath.keys()) if (within3(path, prefix)) return true;
7463
+ for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
6912
7464
  return false;
6913
7465
  }
6914
7466
  closed(node) {
@@ -6916,24 +7468,24 @@ var VfsNodes = class {
6916
7468
  if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
6917
7469
  }
6918
7470
  async flush(path) {
6919
- for (const [p, node] of this.byPath) if (within3(p, path)) await node.serial(() => node.flush());
7471
+ for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
6920
7472
  }
6921
7473
  async unlinking(path) {
6922
7474
  for (const [held, node] of [...this.byPath]) {
6923
- if (!within3(held, path)) continue;
7475
+ if (!within4(held, path)) continue;
6924
7476
  await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
6925
7477
  }
6926
7478
  }
6927
7479
  revoke(prefix) {
6928
7480
  for (const [path, node] of this.byPath) {
6929
- if (!within3(path, prefix)) continue;
7481
+ if (!within4(path, prefix)) continue;
6930
7482
  node.revoke();
6931
7483
  this.byPath.delete(path);
6932
7484
  }
6933
7485
  }
6934
7486
  unlinked(path) {
6935
7487
  for (const [held, node] of [...this.byPath]) {
6936
- if (!within3(held, path)) continue;
7488
+ if (!within4(held, path)) continue;
6937
7489
  node.orphaned = true;
6938
7490
  this.byPath.delete(held);
6939
7491
  }
@@ -6942,8 +7494,8 @@ var VfsNodes = class {
6942
7494
  if (from === to) return;
6943
7495
  const moved2 = [];
6944
7496
  for (const [p, node] of this.byPath) {
6945
- if (within3(p, from)) moved2.push(node);
6946
- else if (within3(p, to)) {
7497
+ if (within4(p, from)) moved2.push(node);
7498
+ else if (within4(p, to)) {
6947
7499
  node.orphaned = true;
6948
7500
  this.byPath.delete(p);
6949
7501
  }
@@ -7197,6 +7749,7 @@ function isWasmSyscall(req) {
7197
7749
  return typeof op === "string" && SYSCALL_OPS.has(op);
7198
7750
  }
7199
7751
  var MAX_READ = 1024 * 1024;
7752
+ var HOSTS_ONLY = new Resolver({ routes: new Routes() });
7200
7753
  var WasmProcess = class {
7201
7754
  exited = false;
7202
7755
  children;
@@ -7588,10 +8141,12 @@ var WasmProcess = class {
7588
8141
  }
7589
8142
  socketSyscall(req) {
7590
8143
  this.net ??= this.options.net ?? new LoopbackNet();
8144
+ const resolver = this.options.resolver ?? HOSTS_ONLY;
7591
8145
  return socketSyscall(req, {
7592
8146
  fds: this.fds,
7593
8147
  net: this.net,
7594
- blocking: () => this.blockingSignal()
8148
+ blocking: () => this.blockingSignal(),
8149
+ resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking)
7595
8150
  });
7596
8151
  }
7597
8152
  tty(fd) {
@@ -7786,6 +8341,7 @@ function spawnWasmProcess(opts) {
7786
8341
  http: opts.http,
7787
8342
  locks: opts.locks,
7788
8343
  onReap: opts.onReap,
8344
+ resolver: opts.resolver,
7789
8345
  onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
7790
8346
  onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
7791
8347
  hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
@@ -8867,7 +9423,7 @@ async function openHostfs(spec, hook, fetch2, timing = {}) {
8867
9423
  }
8868
9424
 
8869
9425
  // src/mount/chunked.ts
8870
- var CHUNK2 = 1024 * 1024;
9426
+ var CHUNK3 = 1024 * 1024;
8871
9427
  var ChunkedBytes = class {
8872
9428
  chunks = [];
8873
9429
  size = 0;
@@ -8876,9 +9432,9 @@ var ChunkedBytes = class {
8876
9432
  if (end <= offset) return new Uint8Array(0);
8877
9433
  const out = new Uint8Array(end - offset);
8878
9434
  for (let at = offset; at < end; ) {
8879
- const index = Math.floor(at / CHUNK2);
8880
- const from = at - index * CHUNK2;
8881
- const n = Math.min(CHUNK2 - from, end - at);
9435
+ const index = Math.floor(at / CHUNK3);
9436
+ const from = at - index * CHUNK3;
9437
+ const n = Math.min(CHUNK3 - from, end - at);
8882
9438
  const chunk2 = this.chunks[index];
8883
9439
  if (chunk2) out.set(chunk2.subarray(from, Math.min(from + n, chunk2.length)), at - offset);
8884
9440
  at += n;
@@ -8889,10 +9445,10 @@ var ChunkedBytes = class {
8889
9445
  const whole = owned && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
8890
9446
  for (let done4 = 0; done4 < bytes.length; ) {
8891
9447
  const at = offset + done4;
8892
- const index = Math.floor(at / CHUNK2);
8893
- const from = at - index * CHUNK2;
8894
- const n = Math.min(CHUNK2 - from, bytes.length - done4);
8895
- if (whole && n === CHUNK2 && bytes.length === CHUNK2) this.chunks[index] = bytes;
9448
+ const index = Math.floor(at / CHUNK3);
9449
+ const from = at - index * CHUNK3;
9450
+ const n = Math.min(CHUNK3 - from, bytes.length - done4);
9451
+ if (whole && n === CHUNK3 && bytes.length === CHUNK3) this.chunks[index] = bytes;
8896
9452
  else this.room(index, from + n).set(bytes.subarray(done4, done4 + n), from);
8897
9453
  done4 += n;
8898
9454
  }
@@ -8900,10 +9456,10 @@ var ChunkedBytes = class {
8900
9456
  }
8901
9457
  truncate(size) {
8902
9458
  if (size < this.size) {
8903
- const keep = Math.ceil(size / CHUNK2);
9459
+ const keep = Math.ceil(size / CHUNK3);
8904
9460
  this.chunks.length = Math.min(this.chunks.length, keep);
8905
9461
  const tail = this.chunks[keep - 1];
8906
- const from = size - (keep - 1) * CHUNK2;
9462
+ const from = size - (keep - 1) * CHUNK3;
8907
9463
  if (tail && from < tail.length) tail.fill(0, from);
8908
9464
  }
8909
9465
  this.size = size;
@@ -8911,7 +9467,7 @@ var ChunkedBytes = class {
8911
9467
  room(index, need) {
8912
9468
  const chunk2 = this.chunks[index];
8913
9469
  if (chunk2 && chunk2.length >= need) return chunk2;
8914
- const grown = new Uint8Array(Math.min(CHUNK2, Math.max(need, (chunk2?.length ?? 0) * 2)));
9470
+ const grown = new Uint8Array(Math.min(CHUNK3, Math.max(need, (chunk2?.length ?? 0) * 2)));
8915
9471
  if (chunk2) grown.set(chunk2);
8916
9472
  this.chunks[index] = grown;
8917
9473
  return grown;
@@ -9325,6 +9881,9 @@ var Launcher = class {
9325
9881
  cdp;
9326
9882
  ca;
9327
9883
  transport;
9884
+ uplink;
9885
+ routes;
9886
+ resolver;
9328
9887
  watchers = new FsWatchers();
9329
9888
  pnpmHome;
9330
9889
  mounts;
@@ -9364,6 +9923,11 @@ var Launcher = class {
9364
9923
  this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
9365
9924
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
9366
9925
  this.transport = options.transport ?? missingTransport();
9926
+ this.uplink = options.uplink;
9927
+ this.routes = new Routes(options.uplink?.traits.ipv6 === true);
9928
+ if (options.uplink?.routes) this.routes.set(options.uplink.routes);
9929
+ this.resolver = new Resolver({ uplink: options.uplink, routes: this.routes });
9930
+ if (options.uplink) this.net.useUplink({ uplink: options.uplink, routes: this.routes });
9367
9931
  enableNetwork(this.net, {
9368
9932
  transport: this.transport,
9369
9933
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
@@ -9378,6 +9942,9 @@ var Launcher = class {
9378
9942
  this.catalog = void 0;
9379
9943
  });
9380
9944
  }
9945
+ setRoutes(table) {
9946
+ this.routes.set(table);
9947
+ }
9381
9948
  commands() {
9382
9949
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
9383
9950
  return this.catalog;
@@ -9557,6 +10124,7 @@ var Launcher = class {
9557
10124
  net: this.net,
9558
10125
  http: new HttpHandles(this.transport),
9559
10126
  locks: this.locks,
10127
+ resolver: this.resolver,
9560
10128
  onReap: (child) => this.reaped(child),
9561
10129
  ...req.fork ? { fork: req.fork } : {},
9562
10130
  ...this.shownIds(req)
@@ -9959,9 +10527,22 @@ function cdpOption(req, port) {
9959
10527
  )
9960
10528
  };
9961
10529
  }
10530
+ function remotes(port, req) {
10531
+ return {
10532
+ ...req.transport ? { transport: new RemoteTransport(port, req.transport) } : {},
10533
+ ...req.uplink ? { uplink: new RemoteUplink(port, req.uplink.traits, req.uplink.routes) } : {}
10534
+ };
10535
+ }
10536
+ function answered(data, remote, uplink) {
10537
+ if (data.net !== void 0) remote?.receive(data);
10538
+ else if (typeof data.uplink === "string") uplink?.receive(data);
10539
+ else return false;
10540
+ return true;
10541
+ }
9962
10542
  function serveKernel(port, deps) {
9963
10543
  let launcher;
9964
10544
  let remote;
10545
+ let uplink;
9965
10546
  const terminals = /* @__PURE__ */ new Map();
9966
10547
  const reply = (id, body, transfer) => port.postMessage({ id, ...body }, transfer);
9967
10548
  const clients = /* @__PURE__ */ new Set();
@@ -10017,7 +10598,8 @@ function serveKernel(port, deps) {
10017
10598
  launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
10018
10599
  const name2 = req.metadata ?? META_DB;
10019
10600
  const { meta, media } = await stores(deps, name2, req.media);
10020
- remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
10601
+ const net = remotes(port, req);
10602
+ ({ transport: remote, uplink } = net);
10021
10603
  const ranged2 = await inOpfs(deps, root);
10022
10604
  const fs = new OpfsFs(root, meta, dirsChannel(name2), { ranged: ranged2 });
10023
10605
  await fs.reconcile();
@@ -10027,7 +10609,7 @@ function serveKernel(port, deps) {
10027
10609
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
10028
10610
  ...req.modules ? { modules: req.modules } : {},
10029
10611
  ...req.env ? { env: req.env } : {},
10030
- ...remote ? { transport: remote } : {},
10612
+ ...net,
10031
10613
  ...media ? { media } : {},
10032
10614
  onMountPending: (medium) => port.postMessage({ medium }),
10033
10615
  ...req.hostfs ? { hostfs: askGrant } : {},
@@ -10052,6 +10634,10 @@ function serveKernel(port, deps) {
10052
10634
  if (req.op === "init") return init(req);
10053
10635
  if (req.op === "open-terminal") return terminal(req);
10054
10636
  if (req.op === "connect") return connect();
10637
+ if (req.op === "routes") {
10638
+ (await ready2()).setRoutes(req.routes);
10639
+ return true;
10640
+ }
10055
10641
  if (req.op === "dial") return dial(req);
10056
10642
  if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
10057
10643
  return mountOp(req);
@@ -10100,10 +10686,7 @@ function serveKernel(port, deps) {
10100
10686
  policies.delete(answer.id);
10101
10687
  return;
10102
10688
  }
10103
- if (event.data.net !== void 0) {
10104
- remote?.receive(event.data);
10105
- return;
10106
- }
10689
+ if (answered(event.data, remote, uplink)) return;
10107
10690
  const req = event.data;
10108
10691
  handle(req).then(
10109
10692
  (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),